0
0
Fork 0
mirror of https://github.com/liabru/matter-js.git synced 2025-02-01 18:24:54 -05:00

added support for Matter.Runner and Matter.Render in tests

This commit is contained in:
liabru 2021-12-22 23:33:57 +00:00
parent 459425b2a3
commit 7d7bad0456
2 changed files with 174 additions and 80 deletions

View file

@ -3,21 +3,22 @@
"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;
@ -31,14 +32,20 @@ const runExample = options => {
try { try {
for (i = 0; i < options.updates; i += 1) { for (i = 0; i < options.updates; i += 1) {
const startTime = process.hrtime(); const time = i * runner.delta;
totalMemory += process.memoryUsage().heapUsed; 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();
callback(time);
const duration = process.hrtime(startTime); const duration = process.hrtime(startTime);
totalDuration += duration[0] * 1e9 + duration[1];
totalMemory += process.memoryUsage().heapUsed; 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;
@ -53,10 +60,6 @@ const runExample = options => {
} }
} }
} }
} catch (err) {
err.message = `On example '${options.name}' update ${i}:\n\n ${err.message}`;
throw err;
}
resetEnvironment(); resetEnvironment();
@ -68,7 +71,13 @@ const runExample = options => {
logs: logs, logs: logs,
extrinsic: captureExtrinsics(engine, Matter), extrinsic: captureExtrinsics(engine, Matter),
intrinsic: captureIntrinsics(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;
}
}; };
const prepareMatter = (options) => { const prepareMatter = (options) => {
@ -78,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) {
@ -129,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 = () => {
@ -167,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,
@ -181,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;
@ -198,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',
@ -238,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;
@ -254,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
}; };