2019-11-07 17:34:59 -05:00
|
|
|
/* eslint-env es6 */
|
|
|
|
/* eslint no-global-assign: 0 */
|
|
|
|
"use strict";
|
|
|
|
|
2021-01-31 12:32:47 -05:00
|
|
|
const mock = require('mock-require');
|
2021-11-28 16:39:11 -05:00
|
|
|
const { requireUncached } = require('./TestTools');
|
2021-11-28 16:25:11 -05:00
|
|
|
const consoleOriginal = global.console;
|
2019-11-07 17:34:59 -05:00
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const runExample = options => {
|
|
|
|
const Matter = prepareMatter(options);
|
|
|
|
const logs = prepareEnvironment(Matter);
|
2021-11-28 16:39:11 -05:00
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const Examples = requireUncached('../examples/index');
|
|
|
|
const example = Examples[options.name]();
|
2021-12-07 18:23:34 -05:00
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const engine = example.engine;
|
2021-12-07 18:23:34 -05:00
|
|
|
const runner = example.runner;
|
|
|
|
|
|
|
|
runner.delta = 1000 / 60;
|
|
|
|
runner.isFixed = true;
|
2021-11-30 18:16:20 -05:00
|
|
|
|
|
|
|
let totalMemory = 0;
|
|
|
|
let totalDuration = 0;
|
|
|
|
let overlapTotal = 0;
|
|
|
|
let overlapCount = 0;
|
2021-12-20 16:17:02 -05:00
|
|
|
let i;
|
2021-11-28 16:39:11 -05:00
|
|
|
|
2021-12-20 16:17:02 -05:00
|
|
|
if (global.gc) {
|
|
|
|
global.gc();
|
|
|
|
}
|
2021-11-28 16:39:11 -05:00
|
|
|
|
2021-12-20 16:17:02 -05:00
|
|
|
try {
|
|
|
|
for (i = 0; i < options.updates; i += 1) {
|
2021-11-30 18:16:20 -05:00
|
|
|
const startTime = process.hrtime();
|
|
|
|
totalMemory += process.memoryUsage().heapUsed;
|
2021-12-07 18:23:34 -05:00
|
|
|
|
|
|
|
Matter.Runner.tick(runner, engine, i * runner.delta);
|
2021-11-30 18:16:20 -05:00
|
|
|
|
|
|
|
const duration = process.hrtime(startTime);
|
|
|
|
totalDuration += duration[0] * 1e9 + duration[1];
|
|
|
|
totalMemory += process.memoryUsage().heapUsed;
|
|
|
|
|
|
|
|
const pairsList = engine.pairs.list;
|
|
|
|
const pairsListLength = engine.pairs.list.length;
|
|
|
|
|
|
|
|
for (let p = 0; p < pairsListLength; p += 1) {
|
|
|
|
const pair = pairsList[p];
|
|
|
|
const separation = pair.separation - pair.slop;
|
|
|
|
|
|
|
|
if (pair.isActive && !pair.isSensor) {
|
|
|
|
overlapTotal += separation > 0 ? separation : 0;
|
|
|
|
overlapCount += 1;
|
|
|
|
}
|
|
|
|
}
|
2021-12-20 16:17:02 -05:00
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
err.message = `On example '${options.name}' update ${i}:\n\n ${err.message}`;
|
|
|
|
throw err;
|
2021-11-30 18:16:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
resetEnvironment();
|
|
|
|
|
|
|
|
return {
|
|
|
|
name: options.name,
|
|
|
|
duration: totalDuration,
|
|
|
|
overlap: overlapTotal / (overlapCount || 1),
|
|
|
|
memory: totalMemory,
|
|
|
|
logs: logs,
|
|
|
|
extrinsic: captureExtrinsics(engine, Matter),
|
|
|
|
intrinsic: captureIntrinsics(engine, Matter),
|
|
|
|
};
|
|
|
|
};
|
2021-11-28 16:39:11 -05:00
|
|
|
|
2021-11-28 16:25:11 -05:00
|
|
|
const prepareMatter = (options) => {
|
|
|
|
const Matter = requireUncached(options.useDev ? '../build/matter.dev' : '../build/matter');
|
2019-11-07 17:34:59 -05:00
|
|
|
|
2021-11-28 16:25:11 -05:00
|
|
|
if (Matter.Common._nextId !== 0) {
|
|
|
|
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;
|
2021-11-30 18:16:20 -05:00
|
|
|
|
|
|
|
if (options.stableSort) {
|
2021-12-12 05:45:50 -05:00
|
|
|
if (Matter.Collision) {
|
|
|
|
const MatterCollisionCollides = Matter.Collision.collides;
|
|
|
|
Matter.Collision.collides = function(bodyA, bodyB, pairs) {
|
|
|
|
const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB;
|
|
|
|
const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA;
|
|
|
|
return MatterCollisionCollides(_bodyA, _bodyB, pairs);
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
const MatterSATCollides = Matter.SAT.collides;
|
|
|
|
Matter.SAT.collides = function(bodyA, bodyB, previousCollision, pairActive) {
|
|
|
|
const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB;
|
|
|
|
const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA;
|
|
|
|
return MatterSATCollides(_bodyA, _bodyB, previousCollision, pairActive);
|
|
|
|
};
|
|
|
|
}
|
2021-11-30 18:16:20 -05:00
|
|
|
|
|
|
|
Matter.after('Detector.collisions', function() { this.sort(collisionCompareId); });
|
|
|
|
Matter.after('Composite.allBodies', function() { sortById(this); });
|
|
|
|
Matter.after('Composite.allConstraints', function() { sortById(this); });
|
|
|
|
Matter.after('Composite.allComposites', function() { sortById(this); });
|
|
|
|
|
|
|
|
Matter.before('Pairs.update', function(pairs) {
|
|
|
|
pairs.list.sort((pairA, pairB) => collisionCompareId(pairA.collision, pairB.collision));
|
|
|
|
});
|
|
|
|
|
|
|
|
Matter.after('Pairs.update', function(pairs) {
|
|
|
|
pairs.list.sort((pairA, pairB) => collisionCompareId(pairA.collision, pairB.collision));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.jitter) {
|
|
|
|
Matter.after('Body.create', function() {
|
|
|
|
Matter.Body.applyForce(this, this.position, {
|
|
|
|
x: Math.cos(this.id * this.id) * options.jitter * this.mass,
|
|
|
|
y: Math.sin(this.id * this.id) * options.jitter * this.mass
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-11-28 16:25:11 -05:00
|
|
|
|
|
|
|
return Matter;
|
|
|
|
};
|
|
|
|
|
|
|
|
const prepareEnvironment = Matter => {
|
2021-01-31 12:32:47 -05:00
|
|
|
mock('matter-js', Matter);
|
2019-11-07 17:34:59 -05:00
|
|
|
global.Matter = Matter;
|
2021-01-31 12:32:47 -05:00
|
|
|
|
2021-11-28 16:25:11 -05:00
|
|
|
const logs = [];
|
2021-01-31 12:32:47 -05:00
|
|
|
global.document = global.window = { addEventListener: () => {} };
|
2021-01-28 19:09:27 -05:00
|
|
|
global.console = {
|
|
|
|
log: (...args) => {
|
|
|
|
logs.push(args.join(' '));
|
|
|
|
}
|
|
|
|
};
|
2019-11-07 17:34:59 -05:00
|
|
|
|
2021-11-28 16:25:11 -05:00
|
|
|
return logs;
|
|
|
|
};
|
|
|
|
|
|
|
|
const resetEnvironment = () => {
|
|
|
|
global.console = consoleOriginal;
|
|
|
|
global.window = undefined;
|
|
|
|
global.document = undefined;
|
|
|
|
global.Matter = undefined;
|
|
|
|
mock.stopAll();
|
|
|
|
};
|
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const captureExtrinsics = ({ world }, Matter) => ({
|
|
|
|
bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => {
|
2021-11-28 16:39:11 -05:00
|
|
|
bodies[body.id] = [
|
|
|
|
body.position.x,
|
|
|
|
body.position.y,
|
|
|
|
body.positionPrev.x,
|
|
|
|
body.positionPrev.y,
|
|
|
|
body.angle,
|
|
|
|
body.anglePrev,
|
|
|
|
...body.vertices.reduce((flat, vertex) => (flat.push(vertex.x, vertex.y), flat), [])
|
|
|
|
];
|
|
|
|
|
|
|
|
return bodies;
|
|
|
|
}, {}),
|
2021-11-30 18:16:20 -05:00
|
|
|
constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => {
|
2021-11-28 16:39:11 -05:00
|
|
|
const positionA = Matter.Constraint.pointAWorld(constraint);
|
|
|
|
const positionB = Matter.Constraint.pointBWorld(constraint);
|
|
|
|
|
|
|
|
constraints[constraint.id] = [
|
|
|
|
positionA.x,
|
|
|
|
positionA.y,
|
|
|
|
positionB.x,
|
|
|
|
positionB.y
|
|
|
|
];
|
|
|
|
|
|
|
|
return constraints;
|
|
|
|
}, {})
|
|
|
|
});
|
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const captureIntrinsics = ({ world }, Matter) => formatIntrinsics({
|
|
|
|
bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => {
|
2021-11-28 16:39:11 -05:00
|
|
|
bodies[body.id] = body;
|
|
|
|
return bodies;
|
|
|
|
}, {}),
|
2021-11-30 18:16:20 -05:00
|
|
|
constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => {
|
2021-11-28 16:39:11 -05:00
|
|
|
constraints[constraint.id] = constraint;
|
|
|
|
return constraints;
|
|
|
|
}, {}),
|
2021-11-30 18:16:20 -05:00
|
|
|
composites: Matter.Composite.allComposites(world).reduce((composites, composite) => {
|
2021-11-28 16:39:11 -05:00
|
|
|
composites[composite.id] = {
|
2021-11-30 18:16:20 -05:00
|
|
|
bodies: Matter.Composite.allBodies(composite).map(body => body.id),
|
|
|
|
constraints: Matter.Composite.allConstraints(composite).map(constraint => constraint.id),
|
|
|
|
composites: Matter.Composite.allComposites(composite).map(composite => composite.id)
|
2021-11-28 16:39:11 -05:00
|
|
|
};
|
|
|
|
return composites;
|
|
|
|
}, {})
|
|
|
|
});
|
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const formatIntrinsics = (obj, depth=0) => {
|
2021-11-28 16:39:11 -05:00
|
|
|
if (obj === Infinity) {
|
|
|
|
return 'Infinity';
|
|
|
|
} else if (typeof obj === 'number') {
|
|
|
|
return limitPrecision(obj);
|
|
|
|
} else if (Array.isArray(obj)) {
|
2021-11-30 18:16:20 -05:00
|
|
|
return obj.map(item => formatIntrinsics(item, depth + 1));
|
2021-11-28 16:39:11 -05:00
|
|
|
} else if (typeof obj !== 'object') {
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
const result = Object.entries(obj)
|
2021-11-30 18:16:20 -05:00
|
|
|
.filter(([key]) => depth <= 1 || intrinsicProperties.includes(key))
|
2021-11-28 16:39:11 -05:00
|
|
|
.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}]`;
|
|
|
|
}
|
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
cleaned[key] = formatIntrinsics(val, depth + 1);
|
2021-11-28 16:39:11 -05:00
|
|
|
return cleaned;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
return Object.keys(result).sort()
|
|
|
|
.reduce((sorted, key) => (sorted[key] = result[key], sorted), {});
|
|
|
|
};
|
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const intrinsicProperties = [
|
|
|
|
// Common
|
|
|
|
'id', 'label',
|
2021-11-20 07:27:14 -05:00
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
// Constraint
|
|
|
|
'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness',
|
2019-11-07 17:34:59 -05:00
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
// 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',
|
2020-03-09 16:56:06 -04:00
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
// Composite
|
|
|
|
'bodies', 'constraints', 'composites'
|
|
|
|
];
|
2021-11-21 12:00:53 -05:00
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const collisionId = (collision) =>
|
|
|
|
Math.min(collision.bodyA.id, collision.bodyB.id) + Math.max(collision.bodyA.id, collision.bodyB.id) * 10000;
|
2020-03-09 16:56:06 -04:00
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const collisionCompareId = (collisionA, collisionB) => collisionId(collisionA) - collisionId(collisionB);
|
2019-11-07 17:34:59 -05:00
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const sortById = (objs) => objs.sort((objA, objB) => objA.id - objB.id);
|
2019-11-07 17:34:59 -05:00
|
|
|
|
2021-11-30 18:16:20 -05:00
|
|
|
const limitPrecision = (val, precision=3) => parseFloat(val.toPrecision(precision));
|
2019-11-07 17:34:59 -05:00
|
|
|
|
|
|
|
module.exports = { runExample };
|