33"use strict" ;
44
55const mock = require ( 'mock-require' ) ;
6- const { requireUncached } = require ( './TestTools' ) ;
6+ const { requireUncached, serialize } = require ( './TestTools' ) ;
77const consoleOriginal = global . console ;
88
99const 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
7483const 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
147181const 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
233253const 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+
250304const collisionId = ( collision ) =>
251305 Math . min ( collision . bodyA . id , collision . bodyB . id ) + Math . max ( collision . bodyA . id , collision . bodyB . id ) * 10000 ;
252306
253307const collisionCompareId = ( collisionA , collisionB ) => collisionId ( collisionA ) - collisionId ( collisionB ) ;
254308
255309const 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 } ;
0 commit comments