0
0
Fork 0
mirror of https://github.com/liabru/matter-js.git synced 2024-12-26 13:49:01 -05:00

Merge branch 'master' into timing-improve

* master:
  added support for Matter.Runner and Matter.Render in tests
  remove render element warning
  deprecated render.controller property
  handle null constraint points in Constraint.pointAWorld and Constraint.pointBWorld
  improved error messages on tests
This commit is contained in:
liabru 2021-12-23 10:41:57 +00:00
commit 4230a8bec6
4 changed files with 194 additions and 89 deletions

View file

@ -312,8 +312,10 @@ var Common = require('../core/Common');
*/
Constraint.pointAWorld = function(constraint) {
return {
x: (constraint.bodyA ? constraint.bodyA.position.x : 0) + constraint.pointA.x,
y: (constraint.bodyA ? constraint.bodyA.position.y : 0) + constraint.pointA.y
x: (constraint.bodyA ? constraint.bodyA.position.x : 0)
+ (constraint.pointA ? constraint.pointA.x : 0),
y: (constraint.bodyA ? constraint.bodyA.position.y : 0)
+ (constraint.pointA ? constraint.pointA.y : 0)
};
};
@ -325,8 +327,10 @@ var Common = require('../core/Common');
*/
Constraint.pointBWorld = function(constraint) {
return {
x: (constraint.bodyB ? constraint.bodyB.position.x : 0) + constraint.pointB.x,
y: (constraint.bodyB ? constraint.bodyB.position.y : 0) + constraint.pointB.y
x: (constraint.bodyB ? constraint.bodyB.position.x : 0)
+ (constraint.pointB ? constraint.pointB.x : 0),
y: (constraint.bodyB ? constraint.bodyB.position.y : 0)
+ (constraint.pointB ? constraint.pointB.y : 0)
};
};

View file

@ -44,7 +44,6 @@ var Mouse = require('../core/Mouse');
*/
Render.create = function(options) {
var defaults = {
controller: Render,
engine: null,
element: null,
canvas: null,
@ -116,6 +115,7 @@ var Mouse = require('../core/Mouse');
};
// for temporary back compatibility only
render.controller = Render;
render.options.showBroadphase = false;
if (render.options.pixelRatio !== 1) {
@ -124,8 +124,6 @@ var Mouse = require('../core/Mouse');
if (Common.isElement(render.element)) {
render.element.appendChild(render.canvas);
} else if (!render.canvas.parentNode) {
Common.log('Render.create: options.element was undefined, render.canvas was created but not appended', 'warn');
}
return render;
@ -1525,6 +1523,7 @@ var Mouse = require('../core/Mouse');
/**
* A back-reference to the `Matter.Render` module.
*
* @deprecated
* @property controller
* @type render
*/

View file

@ -3,38 +3,49 @@
"use strict";
const mock = require('mock-require');
const { requireUncached } = require('./TestTools');
const { requireUncached, serialize } = require('./TestTools');
const consoleOriginal = global.console;
const runExample = options => {
const Matter = prepareMatter(options);
const logs = prepareEnvironment(Matter);
const {
Matter,
logs,
frameCallbacks
} = prepareEnvironment(options);
const Examples = requireUncached('../examples/index');
const example = Examples[options.name]();
const engine = example.engine;
const runner = example.runner;
runner.delta = 1000 / 60;
runner.isFixed = true;
const render = example.render;
let totalMemory = 0;
let totalDuration = 0;
let overlapTotal = 0;
let overlapCount = 0;
let i;
global.gc();
if (global.gc) {
global.gc();
}
for (let i = 0; i < options.updates; i += 1) {
const startTime = process.hrtime();
totalMemory += process.memoryUsage().heapUsed;
try {
for (i = 0; i < options.updates; i += 1) {
const time = i * runner.delta;
const callbackCount = frameCallbacks.length;
Matter.Runner.tick(runner, engine, i * runner.delta);
for (let p = 0; p < callbackCount; p += 1) {
totalMemory += process.memoryUsage().heapUsed;
const callback = frameCallbacks.shift();
const startTime = process.hrtime();
const duration = process.hrtime(startTime);
totalDuration += duration[0] * 1e9 + duration[1];
totalMemory += process.memoryUsage().heapUsed;
callback(time);
const duration = process.hrtime(startTime);
totalMemory += process.memoryUsage().heapUsed;
totalDuration += duration[0] * 1e9 + duration[1];
}
const pairsList = engine.pairs.list;
const pairsListLength = engine.pairs.list.length;
@ -48,19 +59,25 @@ const runExample = options => {
overlapCount += 1;
}
}
}
resetEnvironment();
return {
name: options.name,
duration: totalDuration,
overlap: overlapTotal / (overlapCount || 1),
memory: totalMemory,
logs: logs,
extrinsic: captureExtrinsics(engine, Matter),
intrinsic: captureIntrinsics(engine, Matter),
state: captureState(engine, runner, render)
};
} catch (err) {
err.message = `On example '${options.name}' update ${i}:\n\n ${err.message}`;
throw err;
}
resetEnvironment();
return {
name: options.name,
duration: totalDuration,
overlap: overlapTotal / (overlapCount || 1),
memory: totalMemory,
logs: logs,
extrinsic: captureExtrinsics(engine, Matter),
intrinsic: captureIntrinsics(engine, Matter),
};
};
const prepareMatter = (options) => {
@ -70,12 +87,6 @@ const prepareMatter = (options) => {
throw 'Matter instance has already been used.';
}
const noop = () => ({ collisionFilter: {}, mouse: {} });
Matter.Render.create = () => ({ options: {}, bounds: { min: { x: 0, y: 0 }, max: { x: 800, y: 600 }}});
Matter.Render.run = Matter.Render.lookAt = noop;
Matter.Runner.create = Matter.Runner.run = noop;
Matter.MouseConstraint.create = Matter.Mouse.create = noop;
Matter.Common.info = Matter.Common.warn = Matter.Common.log;
if (options.stableSort) {
@ -121,19 +132,50 @@ const prepareMatter = (options) => {
return Matter;
};
const prepareEnvironment = Matter => {
mock('matter-js', Matter);
global.Matter = Matter;
const prepareEnvironment = options => {
const logs = [];
global.document = global.window = { addEventListener: () => {} };
const frameCallbacks = [];
global.document = global.window = {
addEventListener: () => {},
requestAnimationFrame: callback => {
frameCallbacks.push(callback);
return frameCallbacks.length;
},
createElement: () => ({
parentNode: {},
width: 800,
height: 600,
style: {},
addEventListener: () => {},
getAttribute: name => ({
'data-pixel-ratio': '1'
}[name]),
getContext: () => new Proxy({}, {
get() { return () => {}; }
})
})
};
global.document.body = global.document.createElement();
global.Image = function Image() { };
global.console = {
log: (...args) => {
logs.push(args.join(' '));
}
};
return logs;
const Matter = prepareMatter(options);
mock('matter-js', Matter);
global.Matter = Matter;
return {
Matter,
logs,
frameCallbacks
};
};
const resetEnvironment = () => {
@ -159,8 +201,20 @@ const captureExtrinsics = ({ world }, Matter) => ({
return bodies;
}, {}),
constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => {
const positionA = Matter.Constraint.pointAWorld(constraint);
const positionB = Matter.Constraint.pointBWorld(constraint);
let positionA;
let positionB;
try {
positionA = Matter.Constraint.pointAWorld(constraint);
} catch (err) {
positionA = { x: 0, y: 0 };
}
try {
positionB = Matter.Constraint.pointBWorld(constraint);
} catch (err) {
positionB = { x: 0, y: 0 };
}
constraints[constraint.id] = [
positionA.x,
@ -173,7 +227,7 @@ const captureExtrinsics = ({ world }, Matter) => ({
}, {})
});
const captureIntrinsics = ({ world }, Matter) => formatIntrinsics({
const captureIntrinsics = ({ world }, Matter) => serialize({
bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => {
bodies[body.id] = body;
return bodies;
@ -190,39 +244,16 @@ const captureIntrinsics = ({ world }, Matter) => formatIntrinsics({
};
return composites;
}, {})
});
}, (key) => !Number.isInteger(parseInt(key)) && !intrinsicProperties.includes(key));
const formatIntrinsics = (obj, depth=0) => {
if (obj === Infinity) {
return 'Infinity';
} else if (typeof obj === 'number') {
return limitPrecision(obj);
} else if (Array.isArray(obj)) {
return obj.map(item => formatIntrinsics(item, depth + 1));
} else if (typeof obj !== 'object') {
return obj;
}
const result = Object.entries(obj)
.filter(([key]) => depth <= 1 || intrinsicProperties.includes(key))
.reduce((cleaned, [key, val]) => {
if (val && val.id && String(val.id) !== key) {
val = val.id;
}
if (Array.isArray(val) && !['composites', 'constraints', 'bodies'].includes(key)) {
val = `[${val.length}]`;
}
cleaned[key] = formatIntrinsics(val, depth + 1);
return cleaned;
}, {});
return Object.keys(result).sort()
.reduce((sorted, key) => (sorted[key] = result[key], sorted), {});
};
const captureState = (engine, runner, render, excludeKeys=excludeStateProperties) => (
serialize({ engine, runner, render }, (key) => excludeKeys.includes(key))
);
const intrinsicProperties = [
// Composite
'bodies', 'constraints', 'composites',
// Common
'id', 'label',
@ -230,15 +261,46 @@ const intrinsicProperties = [
'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness',
// Body
'area', 'axes', 'collisionFilter', 'category', 'mask',
'group', 'density', 'friction', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', 'isSensor',
'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution', 'sleepThreshold', 'slop',
'timeScale', 'vertices',
'area', 'collisionFilter', 'category', 'mask', 'group', 'density', 'friction',
'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass',
'isSensor', 'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution',
'sleepThreshold', 'slop', 'timeScale',
// Composite
'bodies', 'constraints', 'composites'
];
const extrinsicProperties = [
'axes',
'vertices',
'bounds',
'angle',
'anglePrev',
'angularVelocity',
'angularSpeed',
'speed',
'velocity',
'position',
'positionPrev',
];
const excludeStateProperties = [
'cache',
'grid',
'context',
'broadphase',
'metrics',
'controller',
'detector',
'pairs',
'lastElapsed',
'deltaHistory',
'elapsedHistory',
'engineDeltaHistory',
'engineElapsedHistory',
'timestampElapsedHistory',
].concat(extrinsicProperties);
const collisionId = (collision) =>
Math.min(collision.bodyA.id, collision.bodyB.id) + Math.max(collision.bodyA.id, collision.bodyB.id) * 10000;
@ -246,6 +308,4 @@ const collisionCompareId = (collisionA, collisionB) => collisionId(collisionA) -
const sortById = (objs) => objs.sort((objA, objB) => objA.id - objB.id);
const limitPrecision = (val, precision=3) => parseFloat(val.toPrecision(precision));
module.exports = { runExample };
module.exports = { runExample };

View file

@ -35,11 +35,12 @@ const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildV
const captureSummary = Object.entries(capturesDev)
.map(([name]) => {
const changedIntrinsics = !equals(capturesDev[name].intrinsic, capturesBuild[name].intrinsic);
if (changedIntrinsics) {
capturesDev[name].changedIntrinsics = true;
if (intrinsicChangeCount < 2) {
devIntrinsicsChanged[name] = capturesDev[name].intrinsic;
buildIntrinsicsChanged[name] = capturesBuild[name].intrinsic;
if (intrinsicChangeCount < 1) {
devIntrinsicsChanged[name] = capturesDev[name].state;
buildIntrinsicsChanged[name] = capturesBuild[name].state;
intrinsicChangeCount += 1;
}
}
@ -172,6 +173,47 @@ const extrinsicSimilarityAverage = (similaritys) => {
return average /= entries.length;
};
const serialize = (obj, exclude=()=>false, precision=4, path='$', visited=[], paths=[]) => {
if (typeof obj === 'number') {
return parseFloat(obj.toPrecision(precision));
} else if (typeof obj === 'string' || typeof obj === 'boolean') {
return obj;
} else if (obj === null) {
return 'null';
} else if (typeof obj === 'undefined') {
return 'undefined';
} else if (obj === Infinity) {
return 'Infinity';
} else if (obj === -Infinity) {
return '-Infinity';
} else if (typeof obj === 'function') {
return 'function';
} else if (Array.isArray(obj)) {
return obj.map(
(item, index) => serialize(item, exclude, precision, path + '.' + index, visited, paths)
);
}
const visitedIndex = visited.indexOf(obj);
if (visitedIndex !== -1) {
return paths[visitedIndex];
}
visited.push(obj);
paths.push(path);
const result = {};
for (const key of Object.keys(obj).sort()) {
if (!exclude(key, obj[key], path + '.' + key)) {
result[key] = serialize(obj[key], exclude, precision, path + '.' + key, visited, paths);
}
}
return result;
};
const writeResult = (name, obj) => {
try {
fs.mkdirSync(comparePath, { recursive: true });
@ -245,5 +287,5 @@ const toMatchIntrinsics = {
module.exports = {
requireUncached, comparisonReport, logReport,
toMatchExtrinsics, toMatchIntrinsics
serialize, toMatchExtrinsics, toMatchIntrinsics
};