mirror of
https://github.com/liabru/matter-js.git
synced 2025-01-15 16:38:43 -05:00
commit
e0e3164541
9 changed files with 471 additions and 127 deletions
|
@ -116,7 +116,6 @@ var compare = function(examples, isDev) {
|
||||||
ticks += 1;
|
ticks += 1;
|
||||||
|
|
||||||
var demoBuildInstance = demoBuild.example.instance;
|
var demoBuildInstance = demoBuild.example.instance;
|
||||||
runner.isFixed = demoBuildInstance.runner.isFixed = true;
|
|
||||||
runner.delta = demoBuildInstance.runner.delta = 1000 / 60;
|
runner.delta = demoBuildInstance.runner.delta = 1000 / 60;
|
||||||
|
|
||||||
window.Matter = MatterBuild;
|
window.Matter = MatterBuild;
|
||||||
|
|
|
@ -41,6 +41,22 @@ var demo = function(examples, isDev) {
|
||||||
document.title = 'Matter.js Demo' + (isDev ? ' ・ Dev' : '');
|
document.title = 'Matter.js Demo' + (isDev ? ' ・ Dev' : '');
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
|
// add delta control
|
||||||
|
Matter.Common.chainPathAfter(MatterTools, 'Gui.create', function() {
|
||||||
|
this.datGui.__folders["Engine"]
|
||||||
|
.add(demo, "delta", 0, 1000 / 55)
|
||||||
|
.onChange(function() {
|
||||||
|
var runner = demo.example.instance.runner;
|
||||||
|
runner.delta = demo.delta;
|
||||||
|
})
|
||||||
|
.step(0.001)
|
||||||
|
.listen();
|
||||||
|
});
|
||||||
|
|
||||||
|
Matter.after('Runner.create', function() {
|
||||||
|
demo.delta = this.delta;
|
||||||
|
});
|
||||||
|
|
||||||
// add compare button
|
// add compare button
|
||||||
var buttonSource = demo.dom.buttonSource,
|
var buttonSource = demo.dom.buttonSource,
|
||||||
buttonCompare = buttonSource.cloneNode(true);
|
buttonCompare = buttonSource.cloneNode(true);
|
||||||
|
|
|
@ -41,6 +41,7 @@ module.exports = {
|
||||||
stress2: require('./stress2.js'),
|
stress2: require('./stress2.js'),
|
||||||
stress3: require('./stress3.js'),
|
stress3: require('./stress3.js'),
|
||||||
stress4: require('./stress4.js'),
|
stress4: require('./stress4.js'),
|
||||||
|
substep: require('./substep.js'),
|
||||||
svg: require('./svg.js'),
|
svg: require('./svg.js'),
|
||||||
terrain: require('./terrain.js'),
|
terrain: require('./terrain.js'),
|
||||||
timescale: require('./timescale.js'),
|
timescale: require('./timescale.js'),
|
||||||
|
|
|
@ -34,9 +34,7 @@ Example.stress3 = function() {
|
||||||
Render.run(render);
|
Render.run(render);
|
||||||
|
|
||||||
// create runner
|
// create runner
|
||||||
var runner = Runner.create({
|
var runner = Runner.create();
|
||||||
isFixed: true
|
|
||||||
});
|
|
||||||
Runner.run(runner, engine);
|
Runner.run(runner, engine);
|
||||||
|
|
||||||
// add bodies
|
// add bodies
|
||||||
|
|
|
@ -35,9 +35,7 @@ Example.stress4 = function() {
|
||||||
Render.run(render);
|
Render.run(render);
|
||||||
|
|
||||||
// create runner
|
// create runner
|
||||||
var runner = Runner.create({
|
var runner = Runner.create();
|
||||||
isFixed: true
|
|
||||||
});
|
|
||||||
|
|
||||||
Runner.run(runner, engine);
|
Runner.run(runner, engine);
|
||||||
|
|
||||||
|
|
122
examples/substep.js
Normal file
122
examples/substep.js
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
var Example = Example || {};
|
||||||
|
|
||||||
|
Example.substep = function() {
|
||||||
|
var Engine = Matter.Engine,
|
||||||
|
Render = Matter.Render,
|
||||||
|
Runner = Matter.Runner,
|
||||||
|
Events = Matter.Events,
|
||||||
|
Composite = Matter.Composite,
|
||||||
|
MouseConstraint = Matter.MouseConstraint,
|
||||||
|
Mouse = Matter.Mouse,
|
||||||
|
Bodies = Matter.Bodies;
|
||||||
|
|
||||||
|
// create engine
|
||||||
|
var engine = Engine.create(),
|
||||||
|
world = engine.world;
|
||||||
|
|
||||||
|
// create renderer
|
||||||
|
var render = Render.create({
|
||||||
|
element: document.body,
|
||||||
|
engine: engine,
|
||||||
|
options: {
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
wireframes: false,
|
||||||
|
showDebug: true,
|
||||||
|
background: '#000',
|
||||||
|
pixelRatio: 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Render.run(render);
|
||||||
|
|
||||||
|
// create runner with higher precision timestep (requires >= v0.20.0 beta)
|
||||||
|
var runner = Runner.create({
|
||||||
|
// 600Hz delta = 1.666ms = 10upf @ 60fps (i.e. 10x default precision)
|
||||||
|
delta: 1000 / (60 * 10),
|
||||||
|
// 50fps minimum performance target (i.e. budget allows up to ~20ms execution per frame)
|
||||||
|
maxFrameTime: 1000 / 50
|
||||||
|
});
|
||||||
|
|
||||||
|
Runner.run(runner, engine);
|
||||||
|
|
||||||
|
// demo substepping using very thin bodies (than is typically recommended)
|
||||||
|
Composite.add(world, [
|
||||||
|
Bodies.rectangle(250, 250, 300, 1.25, {
|
||||||
|
frictionAir: 0,
|
||||||
|
friction: 0,
|
||||||
|
restitution: 0.9,
|
||||||
|
angle: 0.5,
|
||||||
|
render: {
|
||||||
|
lineWidth: 0.5,
|
||||||
|
fillStyle: '#f55a3c'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Bodies.circle(200, 200, 2.25, {
|
||||||
|
frictionAir: 0,
|
||||||
|
friction: 0,
|
||||||
|
restitution: 0.9,
|
||||||
|
angle: 0.5,
|
||||||
|
render: {
|
||||||
|
fillStyle: '#fff'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
// add bodies
|
||||||
|
Composite.add(world, [
|
||||||
|
Bodies.rectangle(400, 0, 800, 50, { isStatic: true, render: { visible: false } }),
|
||||||
|
Bodies.rectangle(400, 600, 800, 50, { isStatic: true, render: { visible: false } }),
|
||||||
|
Bodies.rectangle(800, 300, 50, 600, { isStatic: true, render: { visible: false } }),
|
||||||
|
Bodies.rectangle(0, 300, 50, 600, { isStatic: true, render: { visible: false } })
|
||||||
|
]);
|
||||||
|
|
||||||
|
// scene animation
|
||||||
|
Events.on(engine, 'afterUpdate', function(event) {
|
||||||
|
engine.gravity.scale = 0.00035;
|
||||||
|
engine.gravity.x = Math.cos(engine.timing.timestamp * 0.0005);
|
||||||
|
engine.gravity.y = Math.sin(engine.timing.timestamp * 0.0005);
|
||||||
|
});
|
||||||
|
|
||||||
|
// add mouse control
|
||||||
|
var mouse = Mouse.create(render.canvas),
|
||||||
|
mouseConstraint = MouseConstraint.create(engine, {
|
||||||
|
mouse: mouse,
|
||||||
|
constraint: {
|
||||||
|
stiffness: 0.2,
|
||||||
|
render: {
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Composite.add(world, mouseConstraint);
|
||||||
|
|
||||||
|
// keep the mouse in sync with rendering
|
||||||
|
render.mouse = mouse;
|
||||||
|
|
||||||
|
// fit the render viewport to the scene
|
||||||
|
Render.lookAt(render, {
|
||||||
|
min: { x: 0, y: 0 },
|
||||||
|
max: { x: 800, y: 600 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// context for MatterTools.Demo
|
||||||
|
return {
|
||||||
|
engine: engine,
|
||||||
|
runner: runner,
|
||||||
|
render: render,
|
||||||
|
canvas: render.canvas,
|
||||||
|
stop: function() {
|
||||||
|
Matter.Render.stop(render);
|
||||||
|
Matter.Runner.stop(runner);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Example.substep.title = 'High Substeps (Low Delta)';
|
||||||
|
Example.substep.for = '>=0.20.0';
|
||||||
|
|
||||||
|
if (typeof module !== 'undefined') {
|
||||||
|
module.exports = Example.substep;
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ var Body = require('../body/Body');
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
|
Engine._deltaMax = 1000 / 60;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new engine. The options parameter is an object that specifies any properties you wish to override the defaults.
|
* Creates a new engine. The options parameter is an object that specifies any properties you wish to override the defaults.
|
||||||
* All properties have default values, and many are pre-calculated automatically based on other properties.
|
* All properties have default values, and many are pre-calculated automatically based on other properties.
|
||||||
|
@ -51,7 +53,8 @@ var Body = require('../body/Body');
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
timeScale: 1,
|
timeScale: 1,
|
||||||
lastDelta: 0,
|
lastDelta: 0,
|
||||||
lastElapsed: 0
|
lastElapsed: 0,
|
||||||
|
lastUpdatesPerFrame: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -89,6 +92,13 @@ var Body = require('../body/Body');
|
||||||
timestamp = timing.timestamp,
|
timestamp = timing.timestamp,
|
||||||
i;
|
i;
|
||||||
|
|
||||||
|
// warn if high delta
|
||||||
|
if (delta > Engine._deltaMax) {
|
||||||
|
Common.warnOnce(
|
||||||
|
'Matter.Engine.update: delta argument is recommended to be less than or equal to', Engine._deltaMax.toFixed(3), 'ms.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
delta = typeof delta !== 'undefined' ? delta : Common._baseDelta;
|
delta = typeof delta !== 'undefined' ? delta : Common._baseDelta;
|
||||||
delta *= timing.timeScale;
|
delta *= timing.timeScale;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* The `Matter.Runner` module is an optional utility which provides a game loop,
|
* The `Matter.Runner` module is an optional utility that provides a game loop for running a `Matter.Engine` inside a browser environment.
|
||||||
* that handles continuously updating a `Matter.Engine` for you within a browser.
|
* A runner will continuously update a `Matter.Engine` whilst synchronising engine updates with the browser frame rate.
|
||||||
* It is intended for development and debugging purposes, but may also be suitable for simple games.
|
* This runner favours a smoother user experience over perfect time keeping.
|
||||||
* If you are using your own game loop instead, then you do not need the `Matter.Runner` module.
|
* This runner is optional and is used for development and debugging but could be useful as a starting point for implementing some games and experiences.
|
||||||
* Instead just call `Engine.update(engine, delta)` in your own loop.
|
* Alternatively see `Engine.update` to step the engine directly inside your own game loop implementation as may be needed inside other environments.
|
||||||
*
|
*
|
||||||
* See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples).
|
* See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples).
|
||||||
*
|
*
|
||||||
|
@ -20,73 +20,59 @@ var Common = require('./Common');
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
var _requestAnimationFrame,
|
Runner._maxFrameDelta = 1000 / 15;
|
||||||
_cancelAnimationFrame;
|
Runner._frameDeltaFallback = 1000 / 60;
|
||||||
|
Runner._timeBufferMargin = 1.5;
|
||||||
if (typeof window !== 'undefined') {
|
Runner._elapsedNextEstimate = 1;
|
||||||
_requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame
|
Runner._smoothingLowerBound = 0.1;
|
||||||
|| window.mozRequestAnimationFrame || window.msRequestAnimationFrame;
|
Runner._smoothingUpperBound = 0.9;
|
||||||
|
|
||||||
_cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame
|
|
||||||
|| window.webkitCancelAnimationFrame || window.msCancelAnimationFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_requestAnimationFrame) {
|
|
||||||
var _frameTimeout;
|
|
||||||
|
|
||||||
_requestAnimationFrame = function(callback){
|
|
||||||
_frameTimeout = setTimeout(function() {
|
|
||||||
callback(Common.now());
|
|
||||||
}, 1000 / 60);
|
|
||||||
};
|
|
||||||
|
|
||||||
_cancelAnimationFrame = function() {
|
|
||||||
clearTimeout(_frameTimeout);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Runner. The options parameter is an object that specifies any properties you wish to override the defaults.
|
* Creates a new Runner.
|
||||||
|
* See the properties section below for detailed information on what you can pass via the `options` object.
|
||||||
* @method create
|
* @method create
|
||||||
* @param {} options
|
* @param {} options
|
||||||
*/
|
*/
|
||||||
Runner.create = function(options) {
|
Runner.create = function(options) {
|
||||||
var defaults = {
|
var defaults = {
|
||||||
fps: 60,
|
delta: 1000 / 60,
|
||||||
deltaSampleSize: 60,
|
frameDelta: null,
|
||||||
counterTimestamp: 0,
|
frameDeltaSmoothing: true,
|
||||||
frameCounter: 0,
|
frameDeltaSnapping: true,
|
||||||
deltaHistory: [],
|
frameDeltaHistory: [],
|
||||||
timePrev: null,
|
frameDeltaHistorySize: 100,
|
||||||
frameRequestId: null,
|
frameRequestId: null,
|
||||||
isFixed: false,
|
timeBuffer: 0,
|
||||||
|
timeLastTick: null,
|
||||||
|
maxUpdates: null,
|
||||||
|
maxFrameTime: 1000 / 30,
|
||||||
|
lastUpdatesDeferred: 0,
|
||||||
enabled: true
|
enabled: true
|
||||||
};
|
};
|
||||||
|
|
||||||
var runner = Common.extend(defaults, options);
|
var runner = Common.extend(defaults, options);
|
||||||
|
|
||||||
runner.delta = runner.delta || 1000 / runner.fps;
|
// for temporary back compatibility only
|
||||||
runner.deltaMin = runner.deltaMin || 1000 / runner.fps;
|
runner.fps = 0;
|
||||||
runner.deltaMax = runner.deltaMax || 1000 / (runner.fps * 0.5);
|
|
||||||
runner.fps = 1000 / runner.delta;
|
|
||||||
|
|
||||||
return runner;
|
return runner;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Continuously ticks a `Matter.Engine` by calling `Runner.tick` on the `requestAnimationFrame` event.
|
* Runs a `Matter.Engine` whilst synchronising engine updates with the browser frame rate.
|
||||||
|
* See module and properties descriptions for more information on this runner.
|
||||||
|
* Alternatively see `Engine.update` to step the engine directly inside your own game loop implementation.
|
||||||
* @method run
|
* @method run
|
||||||
* @param {engine} engine
|
* @param {runner} runner
|
||||||
|
* @param {engine} [engine]
|
||||||
|
* @return {runner} runner
|
||||||
*/
|
*/
|
||||||
Runner.run = function(runner, engine) {
|
Runner.run = function(runner, engine) {
|
||||||
// create runner if engine is first argument
|
// initial time buffer for the first frame
|
||||||
if (typeof runner.positionIterations !== 'undefined') {
|
runner.timeBuffer = Runner._frameDeltaFallback;
|
||||||
engine = runner;
|
|
||||||
runner = Runner.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
(function run(time){
|
(function onFrame(time){
|
||||||
runner.frameRequestId = _requestAnimationFrame(run);
|
runner.frameRequestId = Runner._onNextFrame(runner, onFrame);
|
||||||
|
|
||||||
if (time && runner.enabled) {
|
if (time && runner.enabled) {
|
||||||
Runner.tick(runner, engine, time);
|
Runner.tick(runner, engine, time);
|
||||||
|
@ -97,84 +83,190 @@ var Common = require('./Common');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A game loop utility that updates the engine and renderer by one step (a 'tick').
|
* Performs a single runner tick as used inside `Runner.run`.
|
||||||
* Features delta smoothing, time correction and fixed or dynamic timing.
|
* See module and properties descriptions for more information on this runner.
|
||||||
* Consider just `Engine.update(engine, delta)` if you're using your own loop.
|
* Alternatively see `Engine.update` to step the engine directly inside your own game loop implementation.
|
||||||
* @method tick
|
* @method tick
|
||||||
* @param {runner} runner
|
* @param {runner} runner
|
||||||
* @param {engine} engine
|
* @param {engine} engine
|
||||||
* @param {number} time
|
* @param {number} time
|
||||||
*/
|
*/
|
||||||
Runner.tick = function(runner, engine, time) {
|
Runner.tick = function(runner, engine, time) {
|
||||||
var timing = engine.timing,
|
var tickStartTime = Common.now(),
|
||||||
delta;
|
engineDelta = runner.delta,
|
||||||
|
updateCount = 0;
|
||||||
|
|
||||||
if (runner.isFixed) {
|
// find frame delta time since last call
|
||||||
// fixed timestep
|
var frameDelta = time - runner.timeLastTick;
|
||||||
delta = runner.delta;
|
|
||||||
} else {
|
|
||||||
// dynamic timestep based on wall clock between calls
|
|
||||||
delta = (time - runner.timePrev) || runner.delta;
|
|
||||||
runner.timePrev = time;
|
|
||||||
|
|
||||||
// optimistically filter delta over a few frames, to improve stability
|
// fallback for unusable frame delta values (e.g. 0, NaN, on first frame or long pauses)
|
||||||
runner.deltaHistory.push(delta);
|
if (!frameDelta || !runner.timeLastTick || frameDelta > Math.max(Runner._maxFrameDelta, runner.maxFrameTime)) {
|
||||||
runner.deltaHistory = runner.deltaHistory.slice(-runner.deltaSampleSize);
|
// reuse last accepted frame delta else fallback
|
||||||
delta = Math.min.apply(null, runner.deltaHistory);
|
frameDelta = runner.frameDelta || Runner._frameDeltaFallback;
|
||||||
|
|
||||||
// limit delta
|
|
||||||
delta = delta < runner.deltaMin ? runner.deltaMin : delta;
|
|
||||||
delta = delta > runner.deltaMax ? runner.deltaMax : delta;
|
|
||||||
|
|
||||||
// update engine timing object
|
|
||||||
runner.delta = delta;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create an event object
|
if (runner.frameDeltaSmoothing) {
|
||||||
|
// record frame delta over a number of frames
|
||||||
|
runner.frameDeltaHistory.push(frameDelta);
|
||||||
|
runner.frameDeltaHistory = runner.frameDeltaHistory.slice(-runner.frameDeltaHistorySize);
|
||||||
|
|
||||||
|
// sort frame delta history
|
||||||
|
var deltaHistorySorted = runner.frameDeltaHistory.slice(0).sort();
|
||||||
|
|
||||||
|
// sample a central window to limit outliers
|
||||||
|
var deltaHistoryWindow = runner.frameDeltaHistory.slice(
|
||||||
|
deltaHistorySorted.length * Runner._smoothingLowerBound,
|
||||||
|
deltaHistorySorted.length * Runner._smoothingUpperBound
|
||||||
|
);
|
||||||
|
|
||||||
|
// take the mean of the central window
|
||||||
|
var frameDeltaSmoothed = _mean(deltaHistoryWindow);
|
||||||
|
frameDelta = frameDeltaSmoothed || frameDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runner.frameDeltaSnapping) {
|
||||||
|
// snap frame delta to the nearest 1 Hz
|
||||||
|
frameDelta = 1000 / Math.round(1000 / frameDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update runner values for next call
|
||||||
|
runner.frameDelta = frameDelta;
|
||||||
|
runner.timeLastTick = time;
|
||||||
|
|
||||||
|
// accumulate elapsed time
|
||||||
|
runner.timeBuffer += runner.frameDelta;
|
||||||
|
|
||||||
|
// limit time buffer size to a single frame of updates
|
||||||
|
runner.timeBuffer = Common.clamp(
|
||||||
|
runner.timeBuffer, 0, runner.frameDelta + engineDelta * Runner._timeBufferMargin
|
||||||
|
);
|
||||||
|
|
||||||
|
// reset count of over budget updates
|
||||||
|
runner.lastUpdatesDeferred = 0;
|
||||||
|
|
||||||
|
// get max updates per frame
|
||||||
|
var maxUpdates = runner.maxUpdates || Math.ceil(runner.maxFrameTime / engineDelta);
|
||||||
|
|
||||||
|
// create event object
|
||||||
var event = {
|
var event = {
|
||||||
timestamp: timing.timestamp
|
timestamp: engine.timing.timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// tick events before update
|
||||||
Events.trigger(runner, 'beforeTick', event);
|
Events.trigger(runner, 'beforeTick', event);
|
||||||
|
|
||||||
// fps counter
|
|
||||||
runner.frameCounter += 1;
|
|
||||||
if (time - runner.counterTimestamp >= 1000) {
|
|
||||||
runner.fps = runner.frameCounter * ((time - runner.counterTimestamp) / 1000);
|
|
||||||
runner.counterTimestamp = time;
|
|
||||||
runner.frameCounter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Events.trigger(runner, 'tick', event);
|
Events.trigger(runner, 'tick', event);
|
||||||
|
|
||||||
// update
|
var updateStartTime = Common.now();
|
||||||
Events.trigger(runner, 'beforeUpdate', event);
|
|
||||||
|
|
||||||
Engine.update(engine, delta);
|
// simulate time elapsed between calls
|
||||||
|
while (engineDelta > 0 && runner.timeBuffer >= engineDelta * Runner._timeBufferMargin) {
|
||||||
|
// update the engine
|
||||||
|
Events.trigger(runner, 'beforeUpdate', event);
|
||||||
|
Engine.update(engine, engineDelta);
|
||||||
|
Events.trigger(runner, 'afterUpdate', event);
|
||||||
|
|
||||||
Events.trigger(runner, 'afterUpdate', event);
|
// consume time simulated from buffer
|
||||||
|
runner.timeBuffer -= engineDelta;
|
||||||
|
updateCount += 1;
|
||||||
|
|
||||||
|
// find elapsed time during this tick
|
||||||
|
var elapsedTimeTotal = Common.now() - tickStartTime,
|
||||||
|
elapsedTimeUpdates = Common.now() - updateStartTime,
|
||||||
|
elapsedNextEstimate = elapsedTimeTotal + Runner._elapsedNextEstimate * elapsedTimeUpdates / updateCount;
|
||||||
|
|
||||||
|
// defer updates if over performance budgets for this frame
|
||||||
|
if (updateCount >= maxUpdates || elapsedNextEstimate > runner.maxFrameTime) {
|
||||||
|
runner.lastUpdatesDeferred = Math.round(Math.max(0, (runner.timeBuffer / engineDelta) - Runner._timeBufferMargin));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// track timing metrics
|
||||||
|
engine.timing.lastUpdatesPerFrame = updateCount;
|
||||||
|
|
||||||
|
// tick events after update
|
||||||
Events.trigger(runner, 'afterTick', event);
|
Events.trigger(runner, 'afterTick', event);
|
||||||
|
|
||||||
|
// show useful warnings if needed
|
||||||
|
if (runner.frameDeltaHistory.length >= 100) {
|
||||||
|
if (runner.lastUpdatesDeferred && Math.round(runner.frameDelta / engineDelta) > maxUpdates) {
|
||||||
|
Common.warnOnce('Matter.Runner: runner reached runner.maxUpdates, see docs.');
|
||||||
|
} else if (runner.lastUpdatesDeferred) {
|
||||||
|
Common.warnOnce('Matter.Runner: runner reached runner.maxFrameTime, see docs.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof runner.isFixed !== 'undefined') {
|
||||||
|
Common.warnOnce('Matter.Runner: runner.isFixed is now redundant, see docs.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runner.deltaMin || runner.deltaMax) {
|
||||||
|
Common.warnOnce('Matter.Runner: runner.deltaMin and runner.deltaMax were removed, see docs.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runner.fps !== 0) {
|
||||||
|
Common.warnOnce('Matter.Runner: runner.fps was replaced by runner.delta, see docs.');
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ends execution of `Runner.run` on the given `runner`, by canceling the animation frame request event loop.
|
* Ends execution of `Runner.run` on the given `runner` by canceling the frame loop.
|
||||||
* If you wish to only temporarily pause the runner, see `runner.enabled` instead.
|
* Alternatively to temporarily pause the runner, see `runner.enabled`.
|
||||||
* @method stop
|
* @method stop
|
||||||
* @param {runner} runner
|
* @param {runner} runner
|
||||||
*/
|
*/
|
||||||
Runner.stop = function(runner) {
|
Runner.stop = function(runner) {
|
||||||
_cancelAnimationFrame(runner.frameRequestId);
|
Runner._cancelNextFrame(runner);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias for `Runner.run`.
|
* Schedules the `callback` on this `runner` for the next animation frame.
|
||||||
* @method start
|
* @private
|
||||||
|
* @method _onNextFrame
|
||||||
* @param {runner} runner
|
* @param {runner} runner
|
||||||
* @param {engine} engine
|
* @param {function} callback
|
||||||
|
* @return {number} frameRequestId
|
||||||
*/
|
*/
|
||||||
Runner.start = function(runner, engine) {
|
Runner._onNextFrame = function(runner, callback) {
|
||||||
Runner.run(runner, engine);
|
if (typeof window !== 'undefined' && window.requestAnimationFrame) {
|
||||||
|
runner.frameRequestId = window.requestAnimationFrame(callback);
|
||||||
|
} else {
|
||||||
|
throw new Error('Matter.Runner: missing required global window.requestAnimationFrame.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return runner.frameRequestId;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the last callback scheduled by `Runner._onNextFrame` on this `runner`.
|
||||||
|
* @private
|
||||||
|
* @method _cancelNextFrame
|
||||||
|
* @param {runner} runner
|
||||||
|
*/
|
||||||
|
Runner._cancelNextFrame = function(runner) {
|
||||||
|
if (typeof window !== 'undefined' && window.cancelAnimationFrame) {
|
||||||
|
window.cancelAnimationFrame(runner.frameRequestId);
|
||||||
|
} else {
|
||||||
|
throw new Error('Matter.Runner: missing required global window.cancelAnimationFrame.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean of the given numbers.
|
||||||
|
* @method _mean
|
||||||
|
* @private
|
||||||
|
* @param {Number[]} values
|
||||||
|
* @return {Number} the mean of given values.
|
||||||
|
*/
|
||||||
|
var _mean = function(values) {
|
||||||
|
var result = 0,
|
||||||
|
valuesLength = values.length;
|
||||||
|
|
||||||
|
for (var i = 0; i < valuesLength; i += 1) {
|
||||||
|
result += values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result / valuesLength) || 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -184,7 +276,7 @@ var Common = require('./Common');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired at the start of a tick, before any updates to the engine or timing
|
* Fired once at the start of the browser frame, before any engine updates.
|
||||||
*
|
*
|
||||||
* @event beforeTick
|
* @event beforeTick
|
||||||
* @param {} event An event object
|
* @param {} event An event object
|
||||||
|
@ -194,7 +286,7 @@ var Common = require('./Common');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired after engine timing updated, but just before update
|
* Fired once at the start of the browser frame, after `beforeTick`.
|
||||||
*
|
*
|
||||||
* @event tick
|
* @event tick
|
||||||
* @param {} event An event object
|
* @param {} event An event object
|
||||||
|
@ -204,7 +296,7 @@ var Common = require('./Common');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired at the end of a tick, after engine update and after rendering
|
* Fired once at the end of the browser frame, after `beforeTick`, `tick` and after any engine updates.
|
||||||
*
|
*
|
||||||
* @event afterTick
|
* @event afterTick
|
||||||
* @param {} event An event object
|
* @param {} event An event object
|
||||||
|
@ -214,7 +306,8 @@ var Common = require('./Common');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired before update
|
* Fired before each and every engine update in this browser frame (if any).
|
||||||
|
* There may be multiple engine update calls per browser frame (or none) depending on framerate and timestep delta.
|
||||||
*
|
*
|
||||||
* @event beforeUpdate
|
* @event beforeUpdate
|
||||||
* @param {} event An event object
|
* @param {} event An event object
|
||||||
|
@ -224,7 +317,8 @@ var Common = require('./Common');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired after update
|
* Fired after each and every engine update in this browser frame (if any).
|
||||||
|
* There may be multiple engine update calls per browser frame (or none) depending on framerate and timestep delta.
|
||||||
*
|
*
|
||||||
* @event afterUpdate
|
* @event afterUpdate
|
||||||
* @param {} event An event object
|
* @param {} event An event object
|
||||||
|
@ -240,7 +334,31 @@ var Common = require('./Common');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flag that specifies whether the runner is running or not.
|
* The fixed timestep size used for `Engine.update` calls in milliseconds, known as `delta`.
|
||||||
|
*
|
||||||
|
* This value is recommended to be `1000 / 60` ms or smaller (i.e. equivalent to at least 60hz).
|
||||||
|
*
|
||||||
|
* Smaller `delta` values provide higher quality results at the cost of performance.
|
||||||
|
*
|
||||||
|
* You should usually avoid changing `delta` during running, otherwise quality may be affected.
|
||||||
|
*
|
||||||
|
* For smoother frame pacing choose a `delta` that is an even multiple of each display FPS you target, i.e. `1000 / (n * fps)` as this helps distribute an equal number of updates over each display frame.
|
||||||
|
*
|
||||||
|
* For example with a 60 Hz `delta` i.e. `1000 / 60` the runner will on average perform one update per frame on displays running 60 FPS and one update every two frames on displays running 120 FPS, etc.
|
||||||
|
*
|
||||||
|
* Where as e.g. using a 240 Hz `delta` i.e. `1000 / 240` the runner will on average perform four updates per frame on displays running 60 FPS and two updates per frame on displays running 120 FPS, etc.
|
||||||
|
*
|
||||||
|
* Therefore `Runner.run` will call multiple engine updates (or none) as needed to simulate the time elapsed between browser frames.
|
||||||
|
*
|
||||||
|
* In practice the number of updates in any particular frame may be restricted to respect the runner's performance budgets. These are specified by `runner.maxFrameTime` and `runner.maxUpdates`, see those properties for details.
|
||||||
|
*
|
||||||
|
* @property delta
|
||||||
|
* @type number
|
||||||
|
* @default 1000 / 60
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A flag that can be toggled to enable or disable tick calls on this runner, therefore pausing engine updates and events while the runner loop remains running.
|
||||||
*
|
*
|
||||||
* @property enabled
|
* @property enabled
|
||||||
* @type boolean
|
* @type boolean
|
||||||
|
@ -248,23 +366,88 @@ var Common = require('./Common');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `Boolean` that specifies if the runner should use a fixed timestep (otherwise it is variable).
|
* The accumulated time elapsed that has yet to be simulated in milliseconds.
|
||||||
* If timing is fixed, then the apparent simulation speed will change depending on the frame rate (but behaviour will be deterministic).
|
* This value is clamped within certain limits (see `Runner.tick` code).
|
||||||
* If the timing is variable, then the apparent simulation speed will be constant (approximately, but at the cost of determininism).
|
|
||||||
*
|
*
|
||||||
* @property isFixed
|
* @private
|
||||||
* @type boolean
|
* @property timeBuffer
|
||||||
* @default false
|
* @type number
|
||||||
|
* @default 0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `Number` that specifies the time step between updates in milliseconds.
|
* The measured time elapsed between the last two browser frames measured in milliseconds.
|
||||||
* If `engine.timing.isFixed` is set to `true`, then `delta` is fixed.
|
* This is useful e.g. to estimate the current browser FPS using `1000 / runner.frameDelta`.
|
||||||
* If it is `false`, then `delta` can dynamically change to maintain the correct apparent simulation speed.
|
|
||||||
*
|
*
|
||||||
* @property delta
|
* @readonly
|
||||||
|
* @property frameDelta
|
||||||
* @type number
|
* @type number
|
||||||
* @default 1000 / 60
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables averaging to smooth frame rate measurements and therefore stabilise play rate.
|
||||||
|
*
|
||||||
|
* @property frameDeltaSmoothing
|
||||||
|
* @type boolean
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds measured browser frame delta to the nearest 1 Hz.
|
||||||
|
* This option can help smooth frame rate measurements and simplify handling hardware timing differences e.g. 59.94Hz and 60Hz displays.
|
||||||
|
* For best results you should also round your `runner.delta` equivalent to the nearest 1 Hz.
|
||||||
|
*
|
||||||
|
* @property frameDeltaSnapping
|
||||||
|
* @type boolean
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A performance budget that limits execution time allowed for this runner per browser frame in milliseconds.
|
||||||
|
*
|
||||||
|
* To calculate the effective browser FPS at which this throttle is applied use `1000 / runner.maxFrameTime`.
|
||||||
|
*
|
||||||
|
* This performance budget is intended to help maintain browser interactivity and help improve framerate recovery during temporary high CPU usage.
|
||||||
|
*
|
||||||
|
* This budget only covers the measured time elapsed executing the functions called in the scope of the runner tick, including `Engine.update` and its related user event callbacks.
|
||||||
|
*
|
||||||
|
* You may also reduce this budget to allow for any significant additional processing you perform on the same thread outside the scope of this runner tick, e.g. rendering time.
|
||||||
|
*
|
||||||
|
* See also `runner.maxUpdates`.
|
||||||
|
*
|
||||||
|
* @property maxFrameTime
|
||||||
|
* @type number
|
||||||
|
* @default 1000 / 30
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional limit for maximum engine update count allowed per frame tick in addition to `runner.maxFrameTime`.
|
||||||
|
*
|
||||||
|
* Unless you set a value it is automatically chosen based on `runner.delta` and `runner.maxFrameTime`.
|
||||||
|
*
|
||||||
|
* See also `runner.maxFrameTime`.
|
||||||
|
*
|
||||||
|
* @property maxUpdates
|
||||||
|
* @type number
|
||||||
|
* @default null
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp of the last call to `Runner.tick` used to measure `frameDelta`.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @property timeLastTick
|
||||||
|
* @type number
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the last call to `Runner._onNextFrame`.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @property frameRequestId
|
||||||
|
* @type number
|
||||||
|
* @default null
|
||||||
*/
|
*/
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* The `Matter.Render` module is a simple canvas based renderer for visualising instances of `Matter.Engine`.
|
* The `Matter.Render` module is a lightweight, optional utility which provides a simple canvas based renderer for visualising instances of `Matter.Engine`.
|
||||||
* It is intended for development and debugging purposes, but may also be suitable for simple games.
|
* It is intended for development and debugging purposes, but may also be suitable for simple games.
|
||||||
* It includes a number of drawing options including wireframe, vector with support for sprites and viewports.
|
* It includes a number of drawing options including wireframe, vector with support for sprites and viewports.
|
||||||
*
|
*
|
||||||
|
@ -61,6 +61,7 @@ var Mouse = require('../core/Mouse');
|
||||||
timestampElapsedHistory: [],
|
timestampElapsedHistory: [],
|
||||||
engineDeltaHistory: [],
|
engineDeltaHistory: [],
|
||||||
engineElapsedHistory: [],
|
engineElapsedHistory: [],
|
||||||
|
engineUpdatesHistory: [],
|
||||||
elapsedHistory: []
|
elapsedHistory: []
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
@ -552,15 +553,19 @@ var Mouse = require('../core/Mouse');
|
||||||
elapsedHistory = timing.elapsedHistory,
|
elapsedHistory = timing.elapsedHistory,
|
||||||
timestampElapsedHistory = timing.timestampElapsedHistory,
|
timestampElapsedHistory = timing.timestampElapsedHistory,
|
||||||
engineDeltaHistory = timing.engineDeltaHistory,
|
engineDeltaHistory = timing.engineDeltaHistory,
|
||||||
|
engineUpdatesHistory = timing.engineUpdatesHistory,
|
||||||
engineElapsedHistory = timing.engineElapsedHistory,
|
engineElapsedHistory = timing.engineElapsedHistory,
|
||||||
|
lastEngineUpdatesPerFrame = engine.timing.lastUpdatesPerFrame,
|
||||||
lastEngineDelta = engine.timing.lastDelta;
|
lastEngineDelta = engine.timing.lastDelta;
|
||||||
|
|
||||||
var deltaMean = _mean(deltaHistory),
|
var deltaMean = _mean(deltaHistory),
|
||||||
elapsedMean = _mean(elapsedHistory),
|
elapsedMean = _mean(elapsedHistory),
|
||||||
engineDeltaMean = _mean(engineDeltaHistory),
|
engineDeltaMean = _mean(engineDeltaHistory),
|
||||||
|
engineUpdatesMean = _mean(engineUpdatesHistory),
|
||||||
engineElapsedMean = _mean(engineElapsedHistory),
|
engineElapsedMean = _mean(engineElapsedHistory),
|
||||||
timestampElapsedMean = _mean(timestampElapsedHistory),
|
timestampElapsedMean = _mean(timestampElapsedHistory),
|
||||||
rateMean = (timestampElapsedMean / deltaMean) || 0,
|
rateMean = (timestampElapsedMean / deltaMean) || 0,
|
||||||
|
neededUpdatesPerFrame = Math.round(deltaMean / lastEngineDelta),
|
||||||
fps = (1000 / deltaMean) || 0;
|
fps = (1000 / deltaMean) || 0;
|
||||||
|
|
||||||
var graphHeight = 4,
|
var graphHeight = 4,
|
||||||
|
@ -572,7 +577,7 @@ var Mouse = require('../core/Mouse');
|
||||||
|
|
||||||
// background
|
// background
|
||||||
context.fillStyle = '#0e0f19';
|
context.fillStyle = '#0e0f19';
|
||||||
context.fillRect(0, 50, gap * 4 + width * 5 + 22, height);
|
context.fillRect(0, 50, gap * 5 + width * 6 + 22, height);
|
||||||
|
|
||||||
// show FPS
|
// show FPS
|
||||||
Render.status(
|
Render.status(
|
||||||
|
@ -590,17 +595,25 @@ var Mouse = require('../core/Mouse');
|
||||||
function(i) { return (engineDeltaHistory[i] / engineDeltaMean) - 1; }
|
function(i) { return (engineDeltaHistory[i] / engineDeltaMean) - 1; }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// show engine updates per frame
|
||||||
|
Render.status(
|
||||||
|
context, x + (gap + width) * 2, y, width, graphHeight, engineUpdatesHistory.length,
|
||||||
|
lastEngineUpdatesPerFrame + ' upf',
|
||||||
|
Math.pow(Common.clamp((engineUpdatesMean / neededUpdatesPerFrame) || 1, 0, 1), 4),
|
||||||
|
function(i) { return (engineUpdatesHistory[i] / engineUpdatesMean) - 1; }
|
||||||
|
);
|
||||||
|
|
||||||
// show engine update time
|
// show engine update time
|
||||||
Render.status(
|
Render.status(
|
||||||
context, x + (gap + width) * 2, y, width, graphHeight, engineElapsedHistory.length,
|
context, x + (gap + width) * 3, y, width, graphHeight, engineElapsedHistory.length,
|
||||||
engineElapsedMean.toFixed(2) + ' ut',
|
engineElapsedMean.toFixed(2) + ' ut',
|
||||||
1 - (engineElapsedMean / Render._goodFps),
|
1 - (lastEngineUpdatesPerFrame * engineElapsedMean / Render._goodFps),
|
||||||
function(i) { return (engineElapsedHistory[i] / engineElapsedMean) - 1; }
|
function(i) { return (engineElapsedHistory[i] / engineElapsedMean) - 1; }
|
||||||
);
|
);
|
||||||
|
|
||||||
// show render time
|
// show render time
|
||||||
Render.status(
|
Render.status(
|
||||||
context, x + (gap + width) * 3, y, width, graphHeight, elapsedHistory.length,
|
context, x + (gap + width) * 4, y, width, graphHeight, elapsedHistory.length,
|
||||||
elapsedMean.toFixed(2) + ' rt',
|
elapsedMean.toFixed(2) + ' rt',
|
||||||
1 - (elapsedMean / Render._goodFps),
|
1 - (elapsedMean / Render._goodFps),
|
||||||
function(i) { return (elapsedHistory[i] / elapsedMean) - 1; }
|
function(i) { return (elapsedHistory[i] / elapsedMean) - 1; }
|
||||||
|
@ -608,7 +621,7 @@ var Mouse = require('../core/Mouse');
|
||||||
|
|
||||||
// show effective speed
|
// show effective speed
|
||||||
Render.status(
|
Render.status(
|
||||||
context, x + (gap + width) * 4, y, width, graphHeight, timestampElapsedHistory.length,
|
context, x + (gap + width) * 5, y, width, graphHeight, timestampElapsedHistory.length,
|
||||||
rateMean.toFixed(2) + ' x',
|
rateMean.toFixed(2) + ' x',
|
||||||
rateMean * rateMean * rateMean,
|
rateMean * rateMean * rateMean,
|
||||||
function(i) { return (((timestampElapsedHistory[i] / deltaHistory[i]) / rateMean) || 0) - 1; }
|
function(i) { return (((timestampElapsedHistory[i] / deltaHistory[i]) / rateMean) || 0) - 1; }
|
||||||
|
@ -1433,6 +1446,9 @@ var Mouse = require('../core/Mouse');
|
||||||
timing.timestampElapsedHistory.unshift(timing.timestampElapsed);
|
timing.timestampElapsedHistory.unshift(timing.timestampElapsed);
|
||||||
timing.timestampElapsedHistory.length = Math.min(timing.timestampElapsedHistory.length, historySize);
|
timing.timestampElapsedHistory.length = Math.min(timing.timestampElapsedHistory.length, historySize);
|
||||||
|
|
||||||
|
timing.engineUpdatesHistory.unshift(engine.timing.lastUpdatesPerFrame);
|
||||||
|
timing.engineUpdatesHistory.length = Math.min(timing.engineUpdatesHistory.length, historySize);
|
||||||
|
|
||||||
timing.engineElapsedHistory.unshift(engine.timing.lastElapsed);
|
timing.engineElapsedHistory.unshift(engine.timing.lastElapsed);
|
||||||
timing.engineElapsedHistory.length = Math.min(timing.engineElapsedHistory.length, historySize);
|
timing.engineElapsedHistory.length = Math.min(timing.engineElapsedHistory.length, historySize);
|
||||||
|
|
||||||
|
@ -1722,6 +1738,7 @@ var Mouse = require('../core/Mouse');
|
||||||
*
|
*
|
||||||
* - average render frequency (e.g. 60 fps)
|
* - average render frequency (e.g. 60 fps)
|
||||||
* - exact engine delta time used for last update (e.g. 16.66ms)
|
* - exact engine delta time used for last update (e.g. 16.66ms)
|
||||||
|
* - average updates per frame (e.g. 1)
|
||||||
* - average engine execution duration (e.g. 5.00ms)
|
* - average engine execution duration (e.g. 5.00ms)
|
||||||
* - average render execution duration (e.g. 0.40ms)
|
* - average render execution duration (e.g. 0.40ms)
|
||||||
* - average effective play speed (e.g. '1.00x' is 'real-time')
|
* - average effective play speed (e.g. '1.00x' is 'real-time')
|
||||||
|
|
Loading…
Reference in a new issue