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:
commit
4230a8bec6
4 changed files with 194 additions and 89 deletions
|
@ -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)
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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
|
||||
};
|
Loading…
Reference in a new issue