@gnsx/three 0.184.18 → 0.184.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnsx/three",
3
- "version": "0.184.18",
3
+ "version": "0.184.19",
4
4
  "description": "JavaScript 3D library",
5
5
  "type": "module",
6
6
  "main": "./build/three.cjs",
@@ -108,7 +108,7 @@
108
108
  "jsdoc": "^4.0.5",
109
109
  "magic-string": "^0.30.0",
110
110
  "pngjs": "^7.0.0",
111
- "puppeteer": "^24.40.0",
111
+ "puppeteer": "^25.1.0",
112
112
  "qunit": "^2.19.4",
113
113
  "rollup": "^4.6.0",
114
114
  "three": "file:.",
@@ -24,7 +24,7 @@ class ProfilerServiceClass {
24
24
  this._profile = 'full';
25
25
  this._enabled = false;
26
26
 
27
- /** @type {Map<string, Float64Array>} */
27
+ /** @type {Map<string, Float64Array>} Inclusive time ring buffers (one per label). */
28
28
  this.buffers = new Map();
29
29
  /** @type {Map<string, number>} */
30
30
  this.cursors = new Map();
@@ -37,6 +37,20 @@ class ProfilerServiceClass {
37
37
  this.traceStartTime = 0;
38
38
  this._markId = 0;
39
39
 
40
+ /** @type {Map<string, Float64Array>} Exclusive (self) time ring buffers (one per label). */
41
+ this.selfBuffers = new Map();
42
+ /** @type {Map<string, number>} */
43
+ this.selfCursors = new Map();
44
+ /** @type {Map<string, number>} */
45
+ this.selfCounts = new Map();
46
+ /**
47
+ * Global call stack tracking nesting across all labels for exclusive-time computation.
48
+ * @type {Array<{ label: string, childTime: number }>}
49
+ */
50
+ this.callStack = [];
51
+ /** @type {Map<string, number>} Total (uncapped) invocation count since last reset, for calls-per-frame. */
52
+ this.invocations = new Map();
53
+
40
54
  if ( this._enabled ) {
41
55
 
42
56
  this.traceStartTime = performance.now();
@@ -143,6 +157,9 @@ class ProfilerServiceClass {
143
157
  performance.mark( startMark );
144
158
  stack.push( { startTime, startMark, traced } );
145
159
 
160
+ // Push onto the global call stack for exclusive-time tracking.
161
+ this.callStack.push( { label, childTime: 0 } );
162
+
146
163
  }
147
164
 
148
165
  /**
@@ -178,6 +195,42 @@ class ProfilerServiceClass {
178
195
  this.cursors.set( label, ( cursor + 1 ) % RING_SIZE );
179
196
  this.counts.set( label, Math.min( ( this.counts.get( label ) + 1 ), RING_SIZE ) );
180
197
 
198
+ // Track total invocations (uncapped) for calls-per-frame computation.
199
+ this.invocations.set( label, ( this.invocations.get( label ) ?? 0 ) + 1 );
200
+
201
+ // Exclusive (self) time via the global call stack.
202
+ const top = this.callStack.length > 0 ? this.callStack[ this.callStack.length - 1 ] : undefined;
203
+ if ( top !== undefined && top.label === label ) {
204
+
205
+ this.callStack.pop();
206
+ const selfTime = Math.max( 0, duration - top.childTime );
207
+
208
+ let selfBuffer = this.selfBuffers.get( label );
209
+ if ( ! selfBuffer ) {
210
+
211
+ selfBuffer = new Float64Array( RING_SIZE );
212
+ this.selfBuffers.set( label, selfBuffer );
213
+ this.selfCursors.set( label, 0 );
214
+ this.selfCounts.set( label, 0 );
215
+
216
+ }
217
+
218
+ const selfCursor = this.selfCursors.get( label );
219
+ selfBuffer[ selfCursor ] = selfTime;
220
+ this.selfCursors.set( label, ( selfCursor + 1 ) % RING_SIZE );
221
+ this.selfCounts.set( label, Math.min( ( this.selfCounts.get( label ) + 1 ), RING_SIZE ) );
222
+
223
+ // Propagate inclusive duration to the parent scope's child accumulator.
224
+ const parent = this.callStack.length > 0 ? this.callStack[ this.callStack.length - 1 ] : undefined;
225
+ if ( parent !== undefined ) parent.childTime += duration;
226
+
227
+ } else {
228
+
229
+ // Label mismatch — likely async interleaving. Reset to avoid corruption.
230
+ this.callStack.length = 0;
231
+
232
+ }
233
+
181
234
  if ( traced ) {
182
235
 
183
236
  this.traceEvents.push( {
@@ -220,6 +273,23 @@ class ProfilerServiceClass {
220
273
  const sorted = [ ...samples ].sort( ( a, b ) => a - b );
221
274
  const avg = sorted.reduce( ( a, b ) => a + b, 0 ) / sorted.length;
222
275
 
276
+ // Exclusive (self) time stats.
277
+ const selfCount = this.selfCounts.get( label ) ?? 0;
278
+ let selfAvg, selfMin, selfMax, selfP95, selfFrameBudget;
279
+ if ( selfCount > 0 ) {
280
+
281
+ const selfBuffer = this.selfBuffers.get( label );
282
+ const selfSamples = [ ...( selfCount < RING_SIZE
283
+ ? selfBuffer.subarray( 0, selfCount )
284
+ : selfBuffer ) ].sort( ( a, b ) => a - b );
285
+ selfAvg = selfSamples.reduce( ( a, b ) => a + b, 0 ) / selfSamples.length;
286
+ selfMin = selfSamples[ 0 ];
287
+ selfMax = selfSamples[ selfSamples.length - 1 ];
288
+ selfP95 = selfSamples[ Math.floor( selfSamples.length * 0.95 ) ];
289
+ selfFrameBudget = ( selfAvg / FRAME_BUDGET_MS ) * 100;
290
+
291
+ }
292
+
223
293
  return {
224
294
  label,
225
295
  samples: samples.length,
@@ -228,6 +298,12 @@ class ProfilerServiceClass {
228
298
  max: sorted[ sorted.length - 1 ],
229
299
  p95: sorted[ Math.floor( sorted.length * 0.95 ) ],
230
300
  frameBudget: ( avg / FRAME_BUDGET_MS ) * 100,
301
+ totalInvocations: this.invocations.get( label ) ?? 0,
302
+ selfAvg,
303
+ selfMin,
304
+ selfMax,
305
+ selfP95,
306
+ selfFrameBudget,
231
307
  };
232
308
 
233
309
  }
@@ -350,6 +426,11 @@ class ProfilerServiceClass {
350
426
  this.traceEvents = [];
351
427
  this.traceStartTime = performance.now();
352
428
  this._markId = 0;
429
+ this.selfBuffers.clear();
430
+ this.selfCursors.clear();
431
+ this.selfCounts.clear();
432
+ this.callStack.length = 0;
433
+ this.invocations.clear();
353
434
 
354
435
  }
355
436
 
@@ -371,6 +452,12 @@ class ProfilerServiceClass {
371
452
  * @property {number} max
372
453
  * @property {number} p95
373
454
  * @property {number} frameBudget Percentage of a 60 fps frame budget (16.67 ms)
455
+ * @property {number} totalInvocations Total (uncapped) call count since last reset, for calls-per-frame computation.
456
+ * @property {number|undefined} selfAvg Exclusive (self) avg ms — inclusive time minus child scope time.
457
+ * @property {number|undefined} selfMin
458
+ * @property {number|undefined} selfMax
459
+ * @property {number|undefined} selfP95
460
+ * @property {number|undefined} selfFrameBudget Exclusive time as percentage of a 60 fps frame budget.
374
461
  */
375
462
 
376
463
  /**