Skip to content

Commit 7d7bad0

Browse files
committed
added support for Matter.Runner and Matter.Render in tests
1 parent 459425b commit 7d7bad0

2 files changed

Lines changed: 174 additions & 80 deletions

File tree

test/ExampleWorker.js

Lines changed: 128 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@
33
"use strict";
44

55
const mock = require('mock-require');
6-
const { requireUncached } = require('./TestTools');
6+
const { requireUncached, serialize } = require('./TestTools');
77
const consoleOriginal = global.console;
88

99
const runExample = options => {
10-
const Matter = prepareMatter(options);
11-
const logs = prepareEnvironment(Matter);
10+
const {
11+
Matter,
12+
logs,
13+
frameCallbacks
14+
} = prepareEnvironment(options);
1215

1316
const Examples = requireUncached('../examples/index');
1417
const example = Examples[options.name]();
1518

1619
const engine = example.engine;
1720
const runner = example.runner;
18-
19-
runner.delta = 1000 / 60;
20-
runner.isFixed = true;
21+
const render = example.render;
2122

2223
let totalMemory = 0;
2324
let totalDuration = 0;
@@ -31,14 +32,20 @@ const runExample = options => {
3132

3233
try {
3334
for (i = 0; i < options.updates; i += 1) {
34-
const startTime = process.hrtime();
35-
totalMemory += process.memoryUsage().heapUsed;
35+
const time = i * runner.delta;
36+
const callbackCount = frameCallbacks.length;
3637

37-
Matter.Runner.tick(runner, engine, i * runner.delta);
38+
for (let p = 0; p < callbackCount; p += 1) {
39+
totalMemory += process.memoryUsage().heapUsed;
40+
const callback = frameCallbacks.shift();
41+
const startTime = process.hrtime();
3842

39-
const duration = process.hrtime(startTime);
40-
totalDuration += duration[0] * 1e9 + duration[1];
41-
totalMemory += process.memoryUsage().heapUsed;
43+
callback(time);
44+
45+
const duration = process.hrtime(startTime);
46+
totalMemory += process.memoryUsage().heapUsed;
47+
totalDuration += duration[0] * 1e9 + duration[1];
48+
}
4249

4350
const pairsList = engine.pairs.list;
4451
const pairsListLength = engine.pairs.list.length;
@@ -53,22 +60,24 @@ const runExample = options => {
5360
}
5461
}
5562
}
63+
64+
resetEnvironment();
65+
66+
return {
67+
name: options.name,
68+
duration: totalDuration,
69+
overlap: overlapTotal / (overlapCount || 1),
70+
memory: totalMemory,
71+
logs: logs,
72+
extrinsic: captureExtrinsics(engine, Matter),
73+
intrinsic: captureIntrinsics(engine, Matter),
74+
state: captureState(engine, runner, render)
75+
};
76+
5677
} catch (err) {
5778
err.message = `On example '${options.name}' update ${i}:\n\n ${err.message}`;
5879
throw err;
5980
}
60-
61-
resetEnvironment();
62-
63-
return {
64-
name: options.name,
65-
duration: totalDuration,
66-
overlap: overlapTotal / (overlapCount || 1),
67-
memory: totalMemory,
68-
logs: logs,
69-
extrinsic: captureExtrinsics(engine, Matter),
70-
intrinsic: captureIntrinsics(engine, Matter),
71-
};
7281
};
7382

7483
const prepareMatter = (options) => {
@@ -78,12 +87,6 @@ const prepareMatter = (options) => {
7887
throw 'Matter instance has already been used.';
7988
}
8089

81-
const noop = () => ({ collisionFilter: {}, mouse: {} });
82-
83-
Matter.Render.create = () => ({ options: {}, bounds: { min: { x: 0, y: 0 }, max: { x: 800, y: 600 }}});
84-
Matter.Render.run = Matter.Render.lookAt = noop;
85-
Matter.Runner.create = Matter.Runner.run = noop;
86-
Matter.MouseConstraint.create = Matter.Mouse.create = noop;
8790
Matter.Common.info = Matter.Common.warn = Matter.Common.log;
8891

8992
if (options.stableSort) {
@@ -129,19 +132,50 @@ const prepareMatter = (options) => {
129132
return Matter;
130133
};
131134

132-
const prepareEnvironment = Matter => {
133-
mock('matter-js', Matter);
134-
global.Matter = Matter;
135-
135+
const prepareEnvironment = options => {
136136
const logs = [];
137-
global.document = global.window = { addEventListener: () => {} };
137+
const frameCallbacks = [];
138+
139+
global.document = global.window = {
140+
addEventListener: () => {},
141+
requestAnimationFrame: callback => {
142+
frameCallbacks.push(callback);
143+
return frameCallbacks.length;
144+
},
145+
createElement: () => ({
146+
parentNode: {},
147+
width: 800,
148+
height: 600,
149+
style: {},
150+
addEventListener: () => {},
151+
getAttribute: name => ({
152+
'data-pixel-ratio': '1'
153+
}[name]),
154+
getContext: () => new Proxy({}, {
155+
get() { return () => {}; }
156+
})
157+
})
158+
};
159+
160+
global.document.body = global.document.createElement();
161+
162+
global.Image = function Image() { };
163+
138164
global.console = {
139165
log: (...args) => {
140166
logs.push(args.join(' '));
141167
}
142168
};
143169

144-
return logs;
170+
const Matter = prepareMatter(options);
171+
mock('matter-js', Matter);
172+
global.Matter = Matter;
173+
174+
return {
175+
Matter,
176+
logs,
177+
frameCallbacks
178+
};
145179
};
146180

147181
const resetEnvironment = () => {
@@ -167,8 +201,20 @@ const captureExtrinsics = ({ world }, Matter) => ({
167201
return bodies;
168202
}, {}),
169203
constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => {
170-
const positionA = Matter.Constraint.pointAWorld(constraint);
171-
const positionB = Matter.Constraint.pointBWorld(constraint);
204+
let positionA;
205+
let positionB;
206+
207+
try {
208+
positionA = Matter.Constraint.pointAWorld(constraint);
209+
} catch (err) {
210+
positionA = { x: 0, y: 0 };
211+
}
212+
213+
try {
214+
positionB = Matter.Constraint.pointBWorld(constraint);
215+
} catch (err) {
216+
positionB = { x: 0, y: 0 };
217+
}
172218

173219
constraints[constraint.id] = [
174220
positionA.x,
@@ -181,7 +227,7 @@ const captureExtrinsics = ({ world }, Matter) => ({
181227
}, {})
182228
});
183229

184-
const captureIntrinsics = ({ world }, Matter) => formatIntrinsics({
230+
const captureIntrinsics = ({ world }, Matter) => serialize({
185231
bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => {
186232
bodies[body.id] = body;
187233
return bodies;
@@ -198,62 +244,68 @@ const captureIntrinsics = ({ world }, Matter) => formatIntrinsics({
198244
};
199245
return composites;
200246
}, {})
201-
});
247+
}, (key) => !Number.isInteger(parseInt(key)) && !intrinsicProperties.includes(key));
202248

203-
const formatIntrinsics = (obj, depth=0) => {
204-
if (obj === Infinity) {
205-
return 'Infinity';
206-
} else if (typeof obj === 'number') {
207-
return limitPrecision(obj);
208-
} else if (Array.isArray(obj)) {
209-
return obj.map(item => formatIntrinsics(item, depth + 1));
210-
} else if (typeof obj !== 'object') {
211-
return obj;
212-
}
213-
214-
const result = Object.entries(obj)
215-
.filter(([key]) => depth <= 1 || intrinsicProperties.includes(key))
216-
.reduce((cleaned, [key, val]) => {
217-
if (val && val.id && String(val.id) !== key) {
218-
val = val.id;
219-
}
220-
221-
if (Array.isArray(val) && !['composites', 'constraints', 'bodies'].includes(key)) {
222-
val = `[${val.length}]`;
223-
}
224-
225-
cleaned[key] = formatIntrinsics(val, depth + 1);
226-
return cleaned;
227-
}, {});
228-
229-
return Object.keys(result).sort()
230-
.reduce((sorted, key) => (sorted[key] = result[key], sorted), {});
231-
};
249+
const captureState = (engine, runner, render, excludeKeys=excludeStateProperties) => (
250+
serialize({ engine, runner, render }, (key) => excludeKeys.includes(key))
251+
);
232252

233253
const intrinsicProperties = [
254+
// Composite
255+
'bodies', 'constraints', 'composites',
256+
234257
// Common
235258
'id', 'label',
236259

237260
// Constraint
238261
'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness',
239262

240263
// Body
241-
'area', 'axes', 'collisionFilter', 'category', 'mask',
242-
'group', 'density', 'friction', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', 'isSensor',
243-
'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution', 'sleepThreshold', 'slop',
244-
'timeScale', 'vertices',
264+
'area', 'collisionFilter', 'category', 'mask', 'group', 'density', 'friction',
265+
'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass',
266+
'isSensor', 'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution',
267+
'sleepThreshold', 'slop', 'timeScale',
245268

246269
// Composite
247270
'bodies', 'constraints', 'composites'
248271
];
249272

273+
const extrinsicProperties = [
274+
'axes',
275+
'vertices',
276+
'bounds',
277+
'angle',
278+
'anglePrev',
279+
'angularVelocity',
280+
'angularSpeed',
281+
'speed',
282+
'velocity',
283+
'position',
284+
'positionPrev',
285+
];
286+
287+
const excludeStateProperties = [
288+
'cache',
289+
'grid',
290+
'context',
291+
'broadphase',
292+
'metrics',
293+
'controller',
294+
'detector',
295+
'pairs',
296+
'lastElapsed',
297+
'deltaHistory',
298+
'elapsedHistory',
299+
'engineDeltaHistory',
300+
'engineElapsedHistory',
301+
'timestampElapsedHistory',
302+
].concat(extrinsicProperties);
303+
250304
const collisionId = (collision) =>
251305
Math.min(collision.bodyA.id, collision.bodyB.id) + Math.max(collision.bodyA.id, collision.bodyB.id) * 10000;
252306

253307
const collisionCompareId = (collisionA, collisionB) => collisionId(collisionA) - collisionId(collisionB);
254308

255309
const sortById = (objs) => objs.sort((objA, objB) => objA.id - objB.id);
256310

257-
const limitPrecision = (val, precision=3) => parseFloat(val.toPrecision(precision));
258-
259-
module.exports = { runExample };
311+
module.exports = { runExample };

test/TestTools.js

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@ const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildV
3535
const captureSummary = Object.entries(capturesDev)
3636
.map(([name]) => {
3737
const changedIntrinsics = !equals(capturesDev[name].intrinsic, capturesBuild[name].intrinsic);
38+
3839
if (changedIntrinsics) {
3940
capturesDev[name].changedIntrinsics = true;
40-
if (intrinsicChangeCount < 2) {
41-
devIntrinsicsChanged[name] = capturesDev[name].intrinsic;
42-
buildIntrinsicsChanged[name] = capturesBuild[name].intrinsic;
41+
if (intrinsicChangeCount < 1) {
42+
devIntrinsicsChanged[name] = capturesDev[name].state;
43+
buildIntrinsicsChanged[name] = capturesBuild[name].state;
4344
intrinsicChangeCount += 1;
4445
}
4546
}
@@ -172,6 +173,47 @@ const extrinsicSimilarityAverage = (similaritys) => {
172173
return average /= entries.length;
173174
};
174175

176+
const serialize = (obj, exclude=()=>false, precision=4, path='$', visited=[], paths=[]) => {
177+
if (typeof obj === 'number') {
178+
return parseFloat(obj.toPrecision(precision));
179+
} else if (typeof obj === 'string' || typeof obj === 'boolean') {
180+
return obj;
181+
} else if (obj === null) {
182+
return 'null';
183+
} else if (typeof obj === 'undefined') {
184+
return 'undefined';
185+
} else if (obj === Infinity) {
186+
return 'Infinity';
187+
} else if (obj === -Infinity) {
188+
return '-Infinity';
189+
} else if (typeof obj === 'function') {
190+
return 'function';
191+
} else if (Array.isArray(obj)) {
192+
return obj.map(
193+
(item, index) => serialize(item, exclude, precision, path + '.' + index, visited, paths)
194+
);
195+
}
196+
197+
const visitedIndex = visited.indexOf(obj);
198+
199+
if (visitedIndex !== -1) {
200+
return paths[visitedIndex];
201+
}
202+
203+
visited.push(obj);
204+
paths.push(path);
205+
206+
const result = {};
207+
208+
for (const key of Object.keys(obj).sort()) {
209+
if (!exclude(key, obj[key], path + '.' + key)) {
210+
result[key] = serialize(obj[key], exclude, precision, path + '.' + key, visited, paths);
211+
}
212+
}
213+
214+
return result;
215+
};
216+
175217
const writeResult = (name, obj) => {
176218
try {
177219
fs.mkdirSync(comparePath, { recursive: true });
@@ -245,5 +287,5 @@ const toMatchIntrinsics = {
245287

246288
module.exports = {
247289
requireUncached, comparisonReport, logReport,
248-
toMatchExtrinsics, toMatchIntrinsics
290+
serialize, toMatchExtrinsics, toMatchIntrinsics
249291
};

0 commit comments

Comments
 (0)