From 6dc703f9a9073ca07fc4e6b801036a34b45dc5a7 Mon Sep 17 00:00:00 2001 From: liabru Date: Tue, 16 Mar 2021 23:59:13 +0000 Subject: [PATCH 1/6] added lastDelta and lastElapsed to engine.timing --- src/core/Engine.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/Engine.js b/src/core/Engine.js index 3e439a7..9bb3250 100644 --- a/src/core/Engine.js +++ b/src/core/Engine.js @@ -54,7 +54,9 @@ var Body = require('../body/Body'); plugin: {}, timing: { timestamp: 0, - timeScale: 1 + timeScale: 1, + lastDelta: 0, + lastElapsed: 0 }, broadphase: { controller: Grid @@ -111,6 +113,8 @@ var Body = require('../body/Body'); * @param {number} [correction=1] */ Engine.update = function(engine, delta, correction) { + var startTime = Common.now(); + delta = delta || 1000 / 60; correction = correction || 1; @@ -122,6 +126,7 @@ var Body = require('../body/Body'); // increment timestamp timing.timestamp += delta * timing.timeScale; + timing.lastDelta = delta * timing.timeScale; // create an event object var event = { @@ -229,6 +234,9 @@ var Body = require('../body/Body'); Events.trigger(engine, 'afterUpdate', event); + // log the time elapsed computing this update + engine.timing.lastElapsed = Common.now() - startTime; + return engine; }; From f2ef3aadd8f36afe3052c025767e456640888fb5 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 17 Mar 2021 23:55:30 +0000 Subject: [PATCH 2/6] set render showDebug option on examples stress and stress2 --- examples/stress.js | 3 ++- examples/stress2.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/stress.js b/examples/stress.js index 5ef6ae6..2f268bd 100644 --- a/examples/stress.js +++ b/examples/stress.js @@ -20,7 +20,8 @@ Example.stress = function() { engine: engine, options: { width: 800, - height: 600 + height: 600, + showDebug: true } }); diff --git a/examples/stress2.js b/examples/stress2.js index 7af50f3..18ba734 100644 --- a/examples/stress2.js +++ b/examples/stress2.js @@ -20,7 +20,8 @@ Example.stress2 = function() { engine: engine, options: { width: 800, - height: 600 + height: 600, + showDebug: true } }); From 1753bf09046a18fe4060dc2bfff1247a979af268 Mon Sep 17 00:00:00 2001 From: liabru Date: Thu, 18 Mar 2021 23:59:52 +0000 Subject: [PATCH 3/6] improve Render.debug --- src/render/Render.js | 297 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 239 insertions(+), 58 deletions(-) diff --git a/src/render/Render.js b/src/render/Render.js index 700f53a..181a908 100644 --- a/src/render/Render.js +++ b/src/render/Render.js @@ -32,6 +32,9 @@ var Mouse = require('../core/Mouse'); || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame; } + Render._goodFps = 30; + Render._goodDelta = 1000 / 60; + /** * Creates a new renderer. 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. @@ -48,6 +51,18 @@ var Mouse = require('../core/Mouse'); canvas: null, mouse: null, frameRequestId: null, + timing: { + historySize: 60, + delta: 0, + deltaHistory: [], + lastTime: 0, + lastTimestamp: 0, + timestampElapsed: 0, + timestampElapsedHistory: [], + engineDeltaHistory: [], + engineElapsedHistory: [], + elapsedHistory: [] + }, options: { width: 800, height: 600, @@ -121,7 +136,12 @@ var Mouse = require('../core/Mouse'); Render.run = function(render) { (function loop(time){ render.frameRequestId = _requestAnimationFrame(loop); - Render.world(render); + + Render.world(render, time); + _updateTiming(render, time); + + if (render.options.showDebug) + Render.debug(render, render.context, time); })(); }; @@ -289,13 +309,16 @@ var Mouse = require('../core/Mouse'); * @method world * @param {render} render */ - Render.world = function(render) { - var engine = render.engine, + Render.world = function(render, time) { + var startTime = Common.now(), + engine = render.engine, world = engine.world, canvas = render.canvas, context = render.context, options = render.options, - allBodies = Composite.allBodies(world), + timing = render.timing; + + var allBodies = Composite.allBodies(world), allConstraints = Composite.allConstraints(world), background = options.wireframes ? options.wireframeBackground : options.background, bodies = [], @@ -409,81 +432,190 @@ var Mouse = require('../core/Mouse'); if (options.showBroadphase && engine.broadphase.controller === Grid) Render.grid(render, engine.broadphase, context); - if (options.showDebug) - Render.debug(render, context); - if (options.hasBounds) { // revert view transforms Render.endViewTransform(render); } Events.trigger(render, 'afterRender', event); + + // log the time elapsed computing this update + timing.lastElapsed = Common.now() - startTime; }; /** - * Description + * Renders debug information. * @private * @method debug * @param {render} render * @param {RenderingContext} context + * @param {Number} time */ - Render.debug = function(render, context) { - var c = context, - engine = render.engine, + Render.debug = function(render, context, time) { + var engine = render.engine, world = engine.world, - metrics = engine.metrics, - options = render.options, bodies = Composite.allBodies(world), - space = " "; - - if (engine.timing.timestamp - (render.debugTimestamp || 0) >= 500) { - var text = ""; - - if (metrics.timing) { - text += "fps: " + Math.round(metrics.timing.fps) + space; - } - - // @if DEBUG - if (metrics.extended) { - if (metrics.timing) { - text += "delta: " + metrics.timing.delta.toFixed(3) + space; - text += "correction: " + metrics.timing.correction.toFixed(3) + space; - } - - text += "bodies: " + bodies.length + space; - - if (engine.broadphase.controller === Grid) - text += "buckets: " + metrics.buckets + space; - - text += "\n"; - - text += "collisions: " + metrics.collisions + space; - text += "pairs: " + engine.pairs.list.length + space; - text += "broad: " + metrics.broadEff + space; - text += "mid: " + metrics.midEff + space; - text += "narrow: " + metrics.narrowEff + space; - } - // @endif - - render.debugString = text; - render.debugTimestamp = engine.timing.timestamp; + parts = 0, + width = 55, + height = 44, + x = 0, + y = 0; + + // count parts + for (var i = 0; i < bodies.length; i += 1) { + parts += bodies[i].parts.length; } - if (render.debugString) { - c.font = "12px Arial"; + // sections + var sections = { + 'Part': parts, + 'Body': bodies.length, + 'Cons': Composite.allConstraints(world).length, + 'Comp': Composite.allComposites(world).length, + 'Pair': engine.pairs.list.length + }; - if (options.wireframes) { - c.fillStyle = 'rgba(255,255,255,0.5)'; - } else { - c.fillStyle = 'rgba(0,0,0,0.5)'; - } + // background + context.fillStyle = '#0e0f19'; + context.fillRect(x, y, width * 5.5, height); - var split = render.debugString.split('\n'); + context.font = '12px Arial'; + context.textBaseline = 'top'; + context.textAlign = 'right'; - for (var i = 0; i < split.length; i++) { - c.fillText(split[i], 50, 50 + i * 18); - } + // sections + for (var key in sections) { + var section = sections[key]; + // label + context.fillStyle = '#aaa'; + context.fillText(key, x + width, y + 8); + + // value + context.fillStyle = '#eee'; + context.fillText(section, x + width, y + 26); + + x += width; } + + Render.timing(render, context, time); + }; + + /** + * Renders timing information. + * @private + * @method timing + * @param {render} render + * @param {RenderingContext} context + */ + Render.timing = function(render, context) { + var engine = render.engine, + timing = render.timing, + deltaHistory = timing.deltaHistory, + elapsedHistory = timing.elapsedHistory, + timestampElapsedHistory = timing.timestampElapsedHistory, + engineDeltaHistory = timing.engineDeltaHistory, + engineElapsedHistory = timing.engineElapsedHistory, + lastEngineDelta = engine.timing.lastDelta; + + var deltaMean = _mean(deltaHistory), + elapsedMean = _mean(elapsedHistory), + engineDeltaMean = _mean(engineDeltaHistory), + engineElapsedMean = _mean(engineElapsedHistory), + timestampElapsedMean = _mean(timestampElapsedHistory), + rateMean = (timestampElapsedMean / deltaMean) || 0, + fps = (1000 / deltaMean) || 0; + + var graphHeight = 4, + gap = 12, + width = 60, + height = 34, + x = 10, + y = 69; + + // background + context.fillStyle = '#0e0f19'; + context.fillRect(0, 50, gap * 4 + width * 5 + 22, height); + + // show FPS + Render.status( + context, x, y, width, graphHeight, deltaHistory.length, + Math.round(fps) + ' fps', + fps / Render._goodFps, + function(i) { return (deltaHistory[i] / deltaMean) - 1; } + ); + + // show engine delta + Render.status( + context, x + gap + width, y, width, graphHeight, engineDeltaHistory.length, + lastEngineDelta.toFixed(2) + ' dt', + Render._goodDelta / lastEngineDelta, + function(i) { return (engineDeltaHistory[i] / engineDeltaMean) - 1; } + ); + + // show engine update time + Render.status( + context, x + (gap + width) * 2, y, width, graphHeight, engineElapsedHistory.length, + engineElapsedMean.toFixed(2) + ' ut', + 1 - (engineElapsedMean / Render._goodFps), + function(i) { return (engineElapsedHistory[i] / engineElapsedMean) - 1; } + ); + + // show render time + Render.status( + context, x + (gap + width) * 3, y, width, graphHeight, elapsedHistory.length, + elapsedMean.toFixed(2) + ' rt', + 1 - (elapsedMean / Render._goodFps), + function(i) { return (elapsedHistory[i] / elapsedMean) - 1; } + ); + + // show effective speed + Render.status( + context, x + (gap + width) * 4, y, width, graphHeight, timestampElapsedHistory.length, + rateMean.toFixed(2) + ' x', + rateMean * rateMean * rateMean, + function(i) { return (((timestampElapsedHistory[i] / deltaHistory[i]) / rateMean) || 0) - 1; } + ); + }; + + /** + * Renders a label, indicator and a chart. + * @private + * @method status + * @param {RenderingContext} context + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {number} count + * @param {string} label + * @param {string} indicator + * @param {function} plotY + */ + Render.status = function(context, x, y, width, height, count, label, indicator, plotY) { + // background + context.strokeStyle = '#888'; + context.fillStyle = '#444'; + context.lineWidth = 1; + context.fillRect(x, y + 7, width, 1); + + // chart + context.beginPath(); + context.moveTo(x, y + 7 - height * Common.clamp(0.4 * plotY(0), -2, 2)); + for (var i = 0; i < width; i += 1) { + context.lineTo(x + i, y + 7 - (i < count ? height * Common.clamp(0.4 * plotY(i), -2, 2) : 0)); + } + context.stroke(); + + // indicator + context.fillStyle = 'hsl(' + Common.clamp(25 + 95 * indicator, 0, 120) + ',100%,60%)'; + context.fillRect(x, y - 7, 4, 4); + + // label + context.font = '12px Arial'; + context.textBaseline = 'middle'; + context.textAlign = 'right'; + context.fillStyle = '#eee'; + context.fillText(label, x + width, y - 5); }; /** @@ -1322,7 +1454,56 @@ var Mouse = require('../core/Mouse'); }; /** - * Description + * Updates render timing. + * @method _updateTiming + * @private + * @param {render} render + * @param {number} time + */ + var _updateTiming = function(render, time) { + var engine = render.engine, + timing = render.timing, + historySize = timing.historySize, + timestamp = engine.timing.timestamp; + + timing.delta = time - timing.lastTime || Render._goodDelta; + timing.lastTime = time; + + timing.timestampElapsed = timestamp - timing.lastTimestamp || 0; + timing.lastTimestamp = timestamp; + + timing.deltaHistory.unshift(timing.delta); + timing.deltaHistory.length = Math.min(timing.deltaHistory.length, historySize); + + timing.engineDeltaHistory.unshift(engine.timing.lastDelta); + timing.engineDeltaHistory.length = Math.min(timing.engineDeltaHistory.length, historySize); + + timing.timestampElapsedHistory.unshift(timing.timestampElapsed); + timing.timestampElapsedHistory.length = Math.min(timing.timestampElapsedHistory.length, historySize); + + timing.engineElapsedHistory.unshift(engine.timing.lastElapsed); + timing.engineElapsedHistory.length = Math.min(timing.engineElapsedHistory.length, historySize); + + timing.elapsedHistory.unshift(timing.lastElapsed); + timing.elapsedHistory.length = Math.min(timing.elapsedHistory.length, historySize); + }; + + /** + * Returns the mean value of the given numbers. + * @method _mean + * @private + * @param {Number[]} values + * @return {Number} the mean of given values + */ + var _mean = function(values) { + var result = 0; + for (var i = 0; i < values.length; i += 1) { + result += values[i]; + } + return (result / values.length) || 0; + }; + + /** * @method _createCanvas * @private * @param {} width From 119881b43c1aac0a068d5c03330a7427161ebbe4 Mon Sep 17 00:00:00 2001 From: liabru Date: Sat, 20 Mar 2021 23:44:31 +0000 Subject: [PATCH 4/6] added debug stats and performance monitoring to Matter.Render --- examples/stress.js | 3 ++- examples/stress2.js | 3 ++- src/render/Render.js | 29 ++++++++++++++++++----------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/examples/stress.js b/examples/stress.js index 2f268bd..cdea0d4 100644 --- a/examples/stress.js +++ b/examples/stress.js @@ -21,7 +21,8 @@ Example.stress = function() { options: { width: 800, height: 600, - showDebug: true + showStats: true, + showPerformance: true } }); diff --git a/examples/stress2.js b/examples/stress2.js index 18ba734..0f131ef 100644 --- a/examples/stress2.js +++ b/examples/stress2.js @@ -21,7 +21,8 @@ Example.stress2 = function() { options: { width: 800, height: 600, - showDebug: true + showStats: true, + showPerformance: true } }); diff --git a/src/render/Render.js b/src/render/Render.js index 181a908..9536b5b 100644 --- a/src/render/Render.js +++ b/src/render/Render.js @@ -57,6 +57,7 @@ var Mouse = require('../core/Mouse'); deltaHistory: [], lastTime: 0, lastTimestamp: 0, + lastElapsed: 0, timestampElapsed: 0, timestampElapsedHistory: [], engineDeltaHistory: [], @@ -74,6 +75,8 @@ var Mouse = require('../core/Mouse'); wireframes: true, showSleeping: true, showDebug: false, + showStats: false, + showPerformance: false, showBroadphase: false, showBounds: false, showVelocity: false, @@ -137,11 +140,17 @@ var Mouse = require('../core/Mouse'); (function loop(time){ render.frameRequestId = _requestAnimationFrame(loop); - Render.world(render, time); _updateTiming(render, time); - if (render.options.showDebug) - Render.debug(render, render.context, time); + Render.world(render, time); + + if (render.options.showStats || render.options.showDebug) { + Render.stats(render, render.context, time); + } + + if (render.options.showPerformance || render.options.showDebug) { + Render.performance(render, render.context, time); + } })(); }; @@ -444,14 +453,14 @@ var Mouse = require('../core/Mouse'); }; /** - * Renders debug information. + * Renders statistics about the engine and world useful for debugging. * @private - * @method debug + * @method stats * @param {render} render * @param {RenderingContext} context * @param {Number} time */ - Render.debug = function(render, context, time) { + Render.stats = function(render, context, time) { var engine = render.engine, world = engine.world, bodies = Composite.allBodies(world), @@ -496,18 +505,16 @@ var Mouse = require('../core/Mouse'); x += width; } - - Render.timing(render, context, time); }; /** - * Renders timing information. + * Renders engine and render performance information. * @private - * @method timing + * @method performance * @param {render} render * @param {RenderingContext} context */ - Render.timing = function(render, context) { + Render.performance = function(render, context) { var engine = render.engine, timing = render.timing, deltaHistory = timing.deltaHistory, From 991500793c40ea3a2698fcbdb5a695b9fd6428fb Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 21 Mar 2021 15:37:22 +0000 Subject: [PATCH 5/6] added Example.stats --- examples/index.js | 1 + examples/stats.js | 91 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 examples/stats.js diff --git a/examples/index.js b/examples/index.js index 1894b72..b4c3cf5 100644 --- a/examples/index.js +++ b/examples/index.js @@ -35,6 +35,7 @@ module.exports = { sprites: require('./sprites.js'), stack: require('./stack.js'), staticFriction: require('./staticFriction.js'), + stats: require('./stats.js'), stress: require('./stress.js'), stress2: require('./stress2.js'), svg: require('./svg.js'), diff --git a/examples/stats.js b/examples/stats.js new file mode 100644 index 0000000..862a69e --- /dev/null +++ b/examples/stats.js @@ -0,0 +1,91 @@ +var Example = Example || {}; + +Example.stats = function() { + var Engine = Matter.Engine, + Render = Matter.Render, + Runner = Matter.Runner, + Common = Matter.Common, + Composites = Matter.Composites, + MouseConstraint = Matter.MouseConstraint, + Mouse = Matter.Mouse, + World = Matter.World, + 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, + // show stats and performance monitors + showStats: true, + showPerformance: true + } + }); + + Render.run(render); + + // create runner + var runner = Runner.create(); + Runner.run(runner, engine); + + // scene code + var stack = Composites.stack(70, 30, 13, 9, 5, 5, function(x, y) { + return Bodies.circle(x, y, 10 + Common.random() * 20); + }); + + World.add(world, [ + stack, + Bodies.rectangle(400, 0, 800, 50, { isStatic: true }), + Bodies.rectangle(400, 600, 800, 50, { isStatic: true }), + Bodies.rectangle(800, 300, 50, 600, { isStatic: true }), + Bodies.rectangle(0, 300, 50, 600, { isStatic: true }) + ]); + + // add mouse control + var mouse = Mouse.create(render.canvas), + mouseConstraint = MouseConstraint.create(engine, { + mouse: mouse, + constraint: { + stiffness: 0.2, + render: { + visible: false + } + } + }); + + World.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.stats.title = 'Stats & Performance'; +Example.stats.for = '>=0.16.1'; + +if (typeof module !== 'undefined') { + module.exports = Example.stats; +} From c06c10735fce357d298aeaf5adca85ebe5eec764 Mon Sep 17 00:00:00 2001 From: liabru Date: Fri, 9 Apr 2021 20:49:12 +0100 Subject: [PATCH 6/6] added Date.now fallback to Common.now, closes #739 --- src/core/Common.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/core/Common.js b/src/core/Common.js index f03de7b..2d8aa3e 100644 --- a/src/core/Common.js +++ b/src/core/Common.js @@ -252,9 +252,9 @@ module.exports = Common; /** * Returns the current timestamp since the time origin (e.g. from page load). - * The result will be high-resolution including decimal places if available. + * The result is in milliseconds and will use high-resolution timing if available. * @method now - * @return {number} the current timestamp + * @return {number} the current timestamp in milliseconds */ Common.now = function() { if (typeof window !== 'undefined' && window.performance) { @@ -265,6 +265,10 @@ module.exports = Common; } } + if (Date.now) { + return Date.now(); + } + return (new Date()) - Common._nowStartTime; };