0
0
Fork 0
mirror of https://github.com/liabru/matter-js.git synced 2024-12-27 13:59: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) { Constraint.pointAWorld = function(constraint) {
return { return {
x: (constraint.bodyA ? constraint.bodyA.position.x : 0) + constraint.pointA.x, x: (constraint.bodyA ? constraint.bodyA.position.x : 0)
y: (constraint.bodyA ? constraint.bodyA.position.y : 0) + constraint.pointA.y + (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) { Constraint.pointBWorld = function(constraint) {
return { return {
x: (constraint.bodyB ? constraint.bodyB.position.x : 0) + constraint.pointB.x, x: (constraint.bodyB ? constraint.bodyB.position.x : 0)
y: (constraint.bodyB ? constraint.bodyB.position.y : 0) + constraint.pointB.y + (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) { Render.create = function(options) {
var defaults = { var defaults = {
controller: Render,
engine: null, engine: null,
element: null, element: null,
canvas: null, canvas: null,
@ -116,6 +115,7 @@ var Mouse = require('../core/Mouse');
}; };
// for temporary back compatibility only // for temporary back compatibility only
render.controller = Render;
render.options.showBroadphase = false; render.options.showBroadphase = false;
if (render.options.pixelRatio !== 1) { if (render.options.pixelRatio !== 1) {
@ -124,8 +124,6 @@ var Mouse = require('../core/Mouse');
if (Common.isElement(render.element)) { if (Common.isElement(render.element)) {
render.element.appendChild(render.canvas); 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; return render;
@ -1525,6 +1523,7 @@ var Mouse = require('../core/Mouse');
/** /**
* A back-reference to the `Matter.Render` module. * A back-reference to the `Matter.Render` module.
* *
* @deprecated
* @property controller * @property controller
* @type render * @type render
*/ */

View file

@ -3,38 +3,49 @@
"use strict"; "use strict";
const mock = require('mock-require'); const mock = require('mock-require');
const { requireUncached } = require('./TestTools'); const { requireUncached, serialize } = require('./TestTools');
const consoleOriginal = global.console; const consoleOriginal = global.console;
const runExample = options => { const runExample = options => {
const Matter = prepareMatter(options); const {
const logs = prepareEnvironment(Matter); Matter,
logs,
frameCallbacks
} = prepareEnvironment(options);
const Examples = requireUncached('../examples/index'); const Examples = requireUncached('../examples/index');
const example = Examples[options.name](); const example = Examples[options.name]();
const engine = example.engine; const engine = example.engine;
const runner = example.runner; const runner = example.runner;
const render = example.render;
runner.delta = 1000 / 60;
runner.isFixed = true;
let totalMemory = 0; let totalMemory = 0;
let totalDuration = 0; let totalDuration = 0;
let overlapTotal = 0; let overlapTotal = 0;
let overlapCount = 0; let overlapCount = 0;
let i;
global.gc(); if (global.gc) {
global.gc();
}
for (let i = 0; i < options.updates; i += 1) { try {
const startTime = process.hrtime(); for (i = 0; i < options.updates; i += 1) {
totalMemory += process.memoryUsage().heapUsed; 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); callback(time);
totalDuration += duration[0] * 1e9 + duration[1];
totalMemory += process.memoryUsage().heapUsed; const duration = process.hrtime(startTime);
totalMemory += process.memoryUsage().heapUsed;
totalDuration += duration[0] * 1e9 + duration[1];
}
const pairsList = engine.pairs.list; const pairsList = engine.pairs.list;
const pairsListLength = engine.pairs.list.length; const pairsListLength = engine.pairs.list.length;
@ -48,19 +59,25 @@ const runExample = options => {
overlapCount += 1; 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) => { const prepareMatter = (options) => {
@ -70,12 +87,6 @@ const prepareMatter = (options) => {
throw 'Matter instance has already been used.'; 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; Matter.Common.info = Matter.Common.warn = Matter.Common.log;
if (options.stableSort) { if (options.stableSort) {
@ -121,19 +132,50 @@ const prepareMatter = (options) => {
return Matter; return Matter;
}; };
const prepareEnvironment = Matter => { const prepareEnvironment = options => {
mock('matter-js', Matter);
global.Matter = Matter;
const logs = []; 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 = { global.console = {
log: (...args) => { log: (...args) => {
logs.push(args.join(' ')); logs.push(args.join(' '));
} }
}; };
return logs; const Matter = prepareMatter(options);
mock('matter-js', Matter);
global.Matter = Matter;
return {
Matter,
logs,
frameCallbacks
};
}; };
const resetEnvironment = () => { const resetEnvironment = () => {
@ -159,8 +201,20 @@ const captureExtrinsics = ({ world }, Matter) => ({
return bodies; return bodies;
}, {}), }, {}),
constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => { constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => {
const positionA = Matter.Constraint.pointAWorld(constraint); let positionA;
const positionB = Matter.Constraint.pointBWorld(constraint); 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] = [ constraints[constraint.id] = [
positionA.x, 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: Matter.Composite.allBodies(world).reduce((bodies, body) => {
bodies[body.id] = body; bodies[body.id] = body;
return bodies; return bodies;
@ -190,39 +244,16 @@ const captureIntrinsics = ({ world }, Matter) => formatIntrinsics({
}; };
return composites; return composites;
}, {}) }, {})
}); }, (key) => !Number.isInteger(parseInt(key)) && !intrinsicProperties.includes(key));
const formatIntrinsics = (obj, depth=0) => { const captureState = (engine, runner, render, excludeKeys=excludeStateProperties) => (
if (obj === Infinity) { serialize({ engine, runner, render }, (key) => excludeKeys.includes(key))
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 intrinsicProperties = [ const intrinsicProperties = [
// Composite
'bodies', 'constraints', 'composites',
// Common // Common
'id', 'label', 'id', 'label',
@ -230,15 +261,46 @@ const intrinsicProperties = [
'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness', 'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness',
// Body // Body
'area', 'axes', 'collisionFilter', 'category', 'mask', 'area', 'collisionFilter', 'category', 'mask', 'group', 'density', 'friction',
'group', 'density', 'friction', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', 'isSensor', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass',
'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution', 'sleepThreshold', 'slop', 'isSensor', 'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution',
'timeScale', 'vertices', 'sleepThreshold', 'slop', 'timeScale',
// Composite // Composite
'bodies', 'constraints', 'composites' '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) => const collisionId = (collision) =>
Math.min(collision.bodyA.id, collision.bodyB.id) + Math.max(collision.bodyA.id, collision.bodyB.id) * 10000; 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 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) const captureSummary = Object.entries(capturesDev)
.map(([name]) => { .map(([name]) => {
const changedIntrinsics = !equals(capturesDev[name].intrinsic, capturesBuild[name].intrinsic); const changedIntrinsics = !equals(capturesDev[name].intrinsic, capturesBuild[name].intrinsic);
if (changedIntrinsics) { if (changedIntrinsics) {
capturesDev[name].changedIntrinsics = true; capturesDev[name].changedIntrinsics = true;
if (intrinsicChangeCount < 2) { if (intrinsicChangeCount < 1) {
devIntrinsicsChanged[name] = capturesDev[name].intrinsic; devIntrinsicsChanged[name] = capturesDev[name].state;
buildIntrinsicsChanged[name] = capturesBuild[name].intrinsic; buildIntrinsicsChanged[name] = capturesBuild[name].state;
intrinsicChangeCount += 1; intrinsicChangeCount += 1;
} }
} }
@ -172,6 +173,47 @@ const extrinsicSimilarityAverage = (similaritys) => {
return average /= entries.length; 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) => { const writeResult = (name, obj) => {
try { try {
fs.mkdirSync(comparePath, { recursive: true }); fs.mkdirSync(comparePath, { recursive: true });
@ -245,5 +287,5 @@ const toMatchIntrinsics = {
module.exports = { module.exports = {
requireUncached, comparisonReport, logReport, requireUncached, comparisonReport, logReport,
toMatchExtrinsics, toMatchIntrinsics serialize, toMatchExtrinsics, toMatchIntrinsics
}; };