mirror of
https://github.com/liabru/matter-js.git
synced 2024-11-27 09:50:52 -05:00
added support for Matter.Runner and Matter.Render in tests
This commit is contained in:
parent
459425b2a3
commit
7d7bad0456
2 changed files with 174 additions and 80 deletions
|
@ -3,21 +3,22 @@
|
|||
"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;
|
||||
|
@ -31,14 +32,20 @@ const runExample = options => {
|
|||
|
||||
try {
|
||||
for (i = 0; i < options.updates; i += 1) {
|
||||
const startTime = process.hrtime();
|
||||
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();
|
||||
|
||||
callback(time);
|
||||
|
||||
const duration = process.hrtime(startTime);
|
||||
totalDuration += duration[0] * 1e9 + duration[1];
|
||||
totalMemory += process.memoryUsage().heapUsed;
|
||||
totalDuration += duration[0] * 1e9 + duration[1];
|
||||
}
|
||||
|
||||
const pairsList = engine.pairs.list;
|
||||
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();
|
||||
|
||||
|
@ -68,7 +71,13 @@ const runExample = options => {
|
|||
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;
|
||||
}
|
||||
};
|
||||
|
||||
const prepareMatter = (options) => {
|
||||
|
@ -78,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) {
|
||||
|
@ -129,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 = () => {
|
||||
|
@ -167,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,
|
||||
|
@ -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[body.id] = body;
|
||||
return bodies;
|
||||
|
@ -198,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',
|
||||
|
||||
|
@ -238,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;
|
||||
|
||||
|
@ -254,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 };
|
|
@ -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