@gnsx/three 0.184.19 → 0.184.20
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/build/three.cjs +185 -42
- package/build/three.core.js +185 -42
- package/build/three.core.min.js +1 -1
- package/package.json +1 -1
- package/src/profiler/ProfilerService.js +185 -42
package/build/three.cjs
CHANGED
|
@@ -59718,14 +59718,22 @@ class TextureUtils {
|
|
|
59718
59718
|
* __gnsx_profiler.downloadTrace() // download gnsx-trace.json for Speedscope / Perfetto
|
|
59719
59719
|
* __gnsx_profiler.reset() // clear samples and trace
|
|
59720
59720
|
* __gnsx_profiler.disable()
|
|
59721
|
+
*
|
|
59722
|
+
* Chrome trace: `tid=1` is synchronous work; `@profile` on async methods records the
|
|
59723
|
+
* promise lifetime on `tid=2` so long async does not flatten the main row. For manual
|
|
59724
|
+
* spans, use `beginSpan` / `endSpan` with `{ asyncTimeline: true }` in a `.finally()`.
|
|
59721
59725
|
*/
|
|
59722
59726
|
|
|
59723
59727
|
const RING_SIZE = 120;
|
|
59724
59728
|
const FRAME_BUDGET_MS = 1000 / 60;
|
|
59725
|
-
|
|
59726
|
-
const
|
|
59729
|
+
const TRACE_TID_MAIN = 1;
|
|
59730
|
+
const TRACE_TID_ASYNC = 2;
|
|
59727
59731
|
const NOOP = () => {};
|
|
59728
59732
|
|
|
59733
|
+
const DUMMY_SPAN = Object.freeze( { label: '', t0: 0, _seq: 0 } );
|
|
59734
|
+
const NOOP_BEGIN_SPAN = () => DUMMY_SPAN;
|
|
59735
|
+
const NOOP_END_SPAN = () => {};
|
|
59736
|
+
|
|
59729
59737
|
class ProfilerServiceClass {
|
|
59730
59738
|
|
|
59731
59739
|
constructor() {
|
|
@@ -59739,7 +59747,7 @@ class ProfilerServiceClass {
|
|
|
59739
59747
|
this.cursors = new Map();
|
|
59740
59748
|
/** @type {Map<string, number>} */
|
|
59741
59749
|
this.counts = new Map();
|
|
59742
|
-
/** @type {Map<string, Array<{ startTime: number, startMark: string
|
|
59750
|
+
/** @type {Map<string, Array<{ startTime: number, startMark: string }>>} */
|
|
59743
59751
|
this.marks = new Map();
|
|
59744
59752
|
/** @type {ChromeTraceEvent[]} */
|
|
59745
59753
|
this.traceEvents = [];
|
|
@@ -59765,6 +59773,8 @@ class ProfilerServiceClass {
|
|
|
59765
59773
|
this.traceStartTime = performance.now();
|
|
59766
59774
|
this.begin = this._beginImpl.bind( this );
|
|
59767
59775
|
this.end = this._endImpl.bind( this );
|
|
59776
|
+
this.beginSpan = this._beginSpanImpl.bind( this );
|
|
59777
|
+
this.endSpan = this._endSpanImpl.bind( this );
|
|
59768
59778
|
this._exposeGlobal();
|
|
59769
59779
|
console.log( `[ProfilerService] auto-started from env (profile: ${this._profile})` );
|
|
59770
59780
|
|
|
@@ -59772,6 +59782,8 @@ class ProfilerServiceClass {
|
|
|
59772
59782
|
|
|
59773
59783
|
this.begin = NOOP;
|
|
59774
59784
|
this.end = NOOP;
|
|
59785
|
+
this.beginSpan = NOOP_BEGIN_SPAN;
|
|
59786
|
+
this.endSpan = NOOP_END_SPAN;
|
|
59775
59787
|
|
|
59776
59788
|
}
|
|
59777
59789
|
|
|
@@ -59803,6 +59815,8 @@ class ProfilerServiceClass {
|
|
|
59803
59815
|
this._enabled = true;
|
|
59804
59816
|
this.begin = this._beginImpl.bind( this );
|
|
59805
59817
|
this.end = this._endImpl.bind( this );
|
|
59818
|
+
this.beginSpan = this._beginSpanImpl.bind( this );
|
|
59819
|
+
this.endSpan = this._endSpanImpl.bind( this );
|
|
59806
59820
|
this._exposeGlobal();
|
|
59807
59821
|
console.log( `[ProfilerService] enabled (profile: ${this._profile}) — call __gnsx_profiler.report() or downloadTrace() from the console` );
|
|
59808
59822
|
|
|
@@ -59814,6 +59828,8 @@ class ProfilerServiceClass {
|
|
|
59814
59828
|
this._enabled = false;
|
|
59815
59829
|
this.begin = NOOP;
|
|
59816
59830
|
this.end = NOOP;
|
|
59831
|
+
this.beginSpan = NOOP_BEGIN_SPAN;
|
|
59832
|
+
this.endSpan = NOOP_END_SPAN;
|
|
59817
59833
|
console.log( '[ProfilerService] disabled' );
|
|
59818
59834
|
|
|
59819
59835
|
}
|
|
@@ -59849,22 +59865,8 @@ class ProfilerServiceClass {
|
|
|
59849
59865
|
|
|
59850
59866
|
}
|
|
59851
59867
|
|
|
59852
|
-
const traced = this._profile === 'full' && this.traceEvents.length < MAX_TRACE_EVENTS;
|
|
59853
|
-
if ( traced ) {
|
|
59854
|
-
|
|
59855
|
-
this.traceEvents.push( {
|
|
59856
|
-
name: label,
|
|
59857
|
-
ph: 'B',
|
|
59858
|
-
ts: this._getTraceTimestamp( startTime ),
|
|
59859
|
-
pid: 1,
|
|
59860
|
-
tid: 1,
|
|
59861
|
-
cat: 'gnsx',
|
|
59862
|
-
} );
|
|
59863
|
-
|
|
59864
|
-
}
|
|
59865
|
-
|
|
59866
59868
|
performance.mark( startMark );
|
|
59867
|
-
stack.push( { startTime, startMark
|
|
59869
|
+
stack.push( { startTime, startMark } );
|
|
59868
59870
|
|
|
59869
59871
|
// Push onto the global call stack for exclusive-time tracking.
|
|
59870
59872
|
this.callStack.push( { label, childTime: 0 } );
|
|
@@ -59882,13 +59884,61 @@ class ProfilerServiceClass {
|
|
|
59882
59884
|
if ( stack.length === 0 ) this.marks.delete( label );
|
|
59883
59885
|
|
|
59884
59886
|
const now = performance.now();
|
|
59885
|
-
const { startTime, startMark
|
|
59887
|
+
const { startTime, startMark } = mark;
|
|
59886
59888
|
const duration = now - startTime;
|
|
59887
59889
|
const endMark = `gnsx:${label}:end:${++ this._markId}`;
|
|
59888
59890
|
|
|
59889
59891
|
performance.mark( endMark );
|
|
59890
59892
|
performance.measure( `gnsx:${label}`, startMark, endMark );
|
|
59891
59893
|
|
|
59894
|
+
this._commitDurationSample( label, startTime, duration, TRACE_TID_MAIN );
|
|
59895
|
+
|
|
59896
|
+
}
|
|
59897
|
+
|
|
59898
|
+
/**
|
|
59899
|
+
* Per-invocation span start (pair with {@link ProfilerServiceClass#endSpan}).
|
|
59900
|
+
* Safe for concurrent async with the same label.
|
|
59901
|
+
*
|
|
59902
|
+
* @param {string} label
|
|
59903
|
+
* @return {import('./ProfilerService.js').SpanHandle}
|
|
59904
|
+
*/
|
|
59905
|
+
_beginSpanImpl( label ) {
|
|
59906
|
+
|
|
59907
|
+
const seq = ++ this._markId;
|
|
59908
|
+
const t0 = performance.now();
|
|
59909
|
+
const startMark = `gnsx:${label}:s${seq}:start`;
|
|
59910
|
+
performance.mark( startMark );
|
|
59911
|
+
return { label, t0, _seq: seq, _startMark: startMark };
|
|
59912
|
+
|
|
59913
|
+
}
|
|
59914
|
+
|
|
59915
|
+
/**
|
|
59916
|
+
* @param {import('./ProfilerService.js').SpanHandle} handle
|
|
59917
|
+
* @param {import('./ProfilerService.js').EndSpanOptions} [opts]
|
|
59918
|
+
*/
|
|
59919
|
+
_endSpanImpl( handle, opts ) {
|
|
59920
|
+
|
|
59921
|
+
if ( handle._seq === 0 ) return;
|
|
59922
|
+
|
|
59923
|
+
const now = performance.now();
|
|
59924
|
+
const duration = now - handle.t0;
|
|
59925
|
+
const endMark = `gnsx:${handle.label}:s${handle._seq}:end`;
|
|
59926
|
+
performance.mark( endMark );
|
|
59927
|
+
performance.measure( `gnsx:${handle.label}#${handle._seq}`, handle._startMark, endMark );
|
|
59928
|
+
|
|
59929
|
+
const traceTid = opts?.asyncTimeline === true ? TRACE_TID_ASYNC : TRACE_TID_MAIN;
|
|
59930
|
+
this._commitDurationSample( handle.label, handle.t0, duration, traceTid );
|
|
59931
|
+
|
|
59932
|
+
}
|
|
59933
|
+
|
|
59934
|
+
/**
|
|
59935
|
+
* @param {string} label
|
|
59936
|
+
* @param {number} startTime
|
|
59937
|
+
* @param {number} durationMs
|
|
59938
|
+
* @param {typeof TRACE_TID_MAIN|typeof TRACE_TID_ASYNC} traceTid
|
|
59939
|
+
*/
|
|
59940
|
+
_commitDurationSample( label, startTime, durationMs, traceTid ) {
|
|
59941
|
+
|
|
59892
59942
|
let buffer = this.buffers.get( label );
|
|
59893
59943
|
if ( ! buffer ) {
|
|
59894
59944
|
|
|
@@ -59900,7 +59950,7 @@ class ProfilerServiceClass {
|
|
|
59900
59950
|
}
|
|
59901
59951
|
|
|
59902
59952
|
const cursor = this.cursors.get( label );
|
|
59903
|
-
buffer[ cursor ] =
|
|
59953
|
+
buffer[ cursor ] = durationMs;
|
|
59904
59954
|
this.cursors.set( label, ( cursor + 1 ) % RING_SIZE );
|
|
59905
59955
|
this.counts.set( label, Math.min( ( this.counts.get( label ) + 1 ), RING_SIZE ) );
|
|
59906
59956
|
|
|
@@ -59912,7 +59962,7 @@ class ProfilerServiceClass {
|
|
|
59912
59962
|
if ( top !== undefined && top.label === label ) {
|
|
59913
59963
|
|
|
59914
59964
|
this.callStack.pop();
|
|
59915
|
-
const selfTime = Math.max( 0,
|
|
59965
|
+
const selfTime = Math.max( 0, durationMs - top.childTime );
|
|
59916
59966
|
|
|
59917
59967
|
let selfBuffer = this.selfBuffers.get( label );
|
|
59918
59968
|
if ( ! selfBuffer ) {
|
|
@@ -59931,7 +59981,7 @@ class ProfilerServiceClass {
|
|
|
59931
59981
|
|
|
59932
59982
|
// Propagate inclusive duration to the parent scope's child accumulator.
|
|
59933
59983
|
const parent = this.callStack.length > 0 ? this.callStack[ this.callStack.length - 1 ] : undefined;
|
|
59934
|
-
if ( parent !== undefined ) parent.childTime +=
|
|
59984
|
+
if ( parent !== undefined ) parent.childTime += durationMs;
|
|
59935
59985
|
|
|
59936
59986
|
} else {
|
|
59937
59987
|
|
|
@@ -59940,14 +59990,16 @@ class ProfilerServiceClass {
|
|
|
59940
59990
|
|
|
59941
59991
|
}
|
|
59942
59992
|
|
|
59943
|
-
if (
|
|
59993
|
+
if ( traceTid ) {
|
|
59944
59994
|
|
|
59995
|
+
const traceName = traceTid === TRACE_TID_ASYNC ? `${label} (promise)` : label;
|
|
59945
59996
|
this.traceEvents.push( {
|
|
59946
|
-
name:
|
|
59947
|
-
ph: '
|
|
59948
|
-
ts: this._getTraceTimestamp(
|
|
59997
|
+
name: traceName,
|
|
59998
|
+
ph: 'X',
|
|
59999
|
+
ts: Math.round( this._getTraceTimestamp( startTime ) ),
|
|
60000
|
+
dur: Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
59949
60001
|
pid: 1,
|
|
59950
|
-
tid:
|
|
60002
|
+
tid: traceTid,
|
|
59951
60003
|
cat: 'gnsx',
|
|
59952
60004
|
} );
|
|
59953
60005
|
|
|
@@ -60058,13 +60110,53 @@ class ProfilerServiceClass {
|
|
|
60058
60110
|
*/
|
|
60059
60111
|
exportChromeTrace() {
|
|
60060
60112
|
|
|
60113
|
+
const slices = [ ...this.traceEvents ];
|
|
60114
|
+
slices.sort( ( a, b ) => {
|
|
60115
|
+
|
|
60116
|
+
if ( a.ts !== b.ts ) return a.ts - b.ts;
|
|
60117
|
+
if ( a.tid !== b.tid ) return a.tid - b.tid;
|
|
60118
|
+
return a.name.localeCompare( b.name );
|
|
60119
|
+
|
|
60120
|
+
} );
|
|
60121
|
+
const hasAsync = slices.some( e => e.tid === TRACE_TID_ASYNC );
|
|
60122
|
+
/** @type {import('./ProfilerService.js').ChromeTraceMetadataEvent[]} */
|
|
60123
|
+
const prefix = [
|
|
60124
|
+
{
|
|
60125
|
+
cat: '__metadata',
|
|
60126
|
+
name: 'process_name',
|
|
60127
|
+
ph: 'M',
|
|
60128
|
+
pid: 1,
|
|
60129
|
+
tid: 0,
|
|
60130
|
+
ts: 0,
|
|
60131
|
+
args: { name: 'Genesys Profiler' },
|
|
60132
|
+
},
|
|
60133
|
+
{
|
|
60134
|
+
cat: '__metadata',
|
|
60135
|
+
name: 'thread_name',
|
|
60136
|
+
ph: 'M',
|
|
60137
|
+
pid: 1,
|
|
60138
|
+
tid: TRACE_TID_MAIN,
|
|
60139
|
+
ts: 0,
|
|
60140
|
+
args: { name: 'Main thread' },
|
|
60141
|
+
},
|
|
60142
|
+
];
|
|
60143
|
+
if ( hasAsync ) {
|
|
60144
|
+
|
|
60145
|
+
prefix.push( {
|
|
60146
|
+
cat: '__metadata',
|
|
60147
|
+
name: 'thread_name',
|
|
60148
|
+
ph: 'M',
|
|
60149
|
+
pid: 1,
|
|
60150
|
+
tid: TRACE_TID_ASYNC,
|
|
60151
|
+
ts: 0,
|
|
60152
|
+
args: { name: 'Async (promise lifetime)' },
|
|
60153
|
+
} );
|
|
60154
|
+
|
|
60155
|
+
}
|
|
60156
|
+
|
|
60061
60157
|
return {
|
|
60062
60158
|
displayTimeUnit: 'ms',
|
|
60063
|
-
traceEvents: [
|
|
60064
|
-
{ name: 'process_name', ph: 'M', pid: 1, args: { name: 'Genesys Profiler' } },
|
|
60065
|
-
{ name: 'thread_name', ph: 'M', pid: 1, tid: 1, args: { name: 'Main Thread' } },
|
|
60066
|
-
...this.traceEvents,
|
|
60067
|
-
],
|
|
60159
|
+
traceEvents: [ ...prefix, ...slices ],
|
|
60068
60160
|
};
|
|
60069
60161
|
|
|
60070
60162
|
}
|
|
@@ -60169,22 +60261,38 @@ class ProfilerServiceClass {
|
|
|
60169
60261
|
* @property {number|undefined} selfFrameBudget Exclusive time as percentage of a 60 fps frame budget.
|
|
60170
60262
|
*/
|
|
60171
60263
|
|
|
60264
|
+
/**
|
|
60265
|
+
* @typedef {Object} SpanHandle
|
|
60266
|
+
* @property {string} label
|
|
60267
|
+
* @property {number} t0
|
|
60268
|
+
* @property {number} _seq
|
|
60269
|
+
* @property {string} _startMark
|
|
60270
|
+
*/
|
|
60271
|
+
|
|
60272
|
+
/**
|
|
60273
|
+
* @typedef {Object} EndSpanOptions
|
|
60274
|
+
* @property {boolean} [asyncTimeline] When true, trace slice uses `tid=2` (virtual async row).
|
|
60275
|
+
*/
|
|
60276
|
+
|
|
60172
60277
|
/**
|
|
60173
60278
|
* @typedef {Object} ChromeTraceEvent
|
|
60174
60279
|
* @property {string} name
|
|
60175
|
-
* @property {'
|
|
60280
|
+
* @property {'X'} ph
|
|
60176
60281
|
* @property {number} ts
|
|
60282
|
+
* @property {number} dur
|
|
60177
60283
|
* @property {1} pid
|
|
60178
|
-
* @property {
|
|
60284
|
+
* @property {number} tid
|
|
60179
60285
|
* @property {'gnsx'} cat
|
|
60180
60286
|
*/
|
|
60181
60287
|
|
|
60182
60288
|
/**
|
|
60183
60289
|
* @typedef {Object} ChromeTraceMetadataEvent
|
|
60290
|
+
* @property {'__metadata'} cat
|
|
60184
60291
|
* @property {'process_name'|'thread_name'} name
|
|
60185
60292
|
* @property {'M'} ph
|
|
60186
60293
|
* @property {1} pid
|
|
60187
|
-
* @property {
|
|
60294
|
+
* @property {number} [tid]
|
|
60295
|
+
* @property {0} ts
|
|
60188
60296
|
* @property {{ name: string }} args
|
|
60189
60297
|
*/
|
|
60190
60298
|
|
|
@@ -60200,28 +60308,38 @@ class ProfilerServiceClass {
|
|
|
60200
60308
|
|
|
60201
60309
|
const ProfilerService = new ProfilerServiceClass();
|
|
60202
60310
|
|
|
60311
|
+
function isThenable( x ) {
|
|
60312
|
+
|
|
60313
|
+
return (
|
|
60314
|
+
( typeof x === 'object' || typeof x === 'function' ) &&
|
|
60315
|
+
x !== null &&
|
|
60316
|
+
typeof x.then === 'function'
|
|
60317
|
+
);
|
|
60318
|
+
|
|
60319
|
+
}
|
|
60320
|
+
|
|
60203
60321
|
function applyProfileToMethod( label, descriptor ) {
|
|
60204
60322
|
|
|
60205
60323
|
const original = descriptor.value;
|
|
60206
60324
|
|
|
60207
60325
|
function profiled( ...args ) {
|
|
60208
60326
|
|
|
60209
|
-
ProfilerService.
|
|
60327
|
+
const span = ProfilerService.beginSpan( label );
|
|
60210
60328
|
try {
|
|
60211
60329
|
|
|
60212
60330
|
const result = original.apply( this, args );
|
|
60213
|
-
if ( result
|
|
60331
|
+
if ( isThenable( result ) ) {
|
|
60214
60332
|
|
|
60215
|
-
return result.finally( () => ProfilerService.
|
|
60333
|
+
return Promise.resolve( result ).finally( () => ProfilerService.endSpan( span, { asyncTimeline: true } ) );
|
|
60216
60334
|
|
|
60217
60335
|
}
|
|
60218
60336
|
|
|
60219
|
-
ProfilerService.
|
|
60337
|
+
ProfilerService.endSpan( span );
|
|
60220
60338
|
return result;
|
|
60221
60339
|
|
|
60222
60340
|
} catch ( error ) {
|
|
60223
60341
|
|
|
60224
|
-
ProfilerService.
|
|
60342
|
+
ProfilerService.endSpan( span );
|
|
60225
60343
|
throw error;
|
|
60226
60344
|
|
|
60227
60345
|
}
|
|
@@ -60235,14 +60353,39 @@ function applyProfileToMethod( label, descriptor ) {
|
|
|
60235
60353
|
|
|
60236
60354
|
/**
|
|
60237
60355
|
* Method decorator that profiles the decorated method.
|
|
60238
|
-
*
|
|
60356
|
+
* Default label: `ClassName.methodName`. Pass a custom tag via `@profile('My tag')`.
|
|
60239
60357
|
*
|
|
60358
|
+
* @param {Object|string} [targetOrTag]
|
|
60359
|
+
* @param {string|symbol} [propertyKey]
|
|
60360
|
+
* @param {PropertyDescriptor} [descriptor]
|
|
60361
|
+
* @return {PropertyDescriptor|function(Object, string|symbol, PropertyDescriptor): PropertyDescriptor}
|
|
60362
|
+
*/
|
|
60363
|
+
function profile( targetOrTag, propertyKey, descriptor ) {
|
|
60364
|
+
|
|
60365
|
+
if ( arguments.length === 0 ) {
|
|
60366
|
+
|
|
60367
|
+
return defaultProfileDecorator;
|
|
60368
|
+
|
|
60369
|
+
}
|
|
60370
|
+
|
|
60371
|
+
if ( arguments.length === 1 && typeof targetOrTag === 'string' ) {
|
|
60372
|
+
|
|
60373
|
+
const customTag = targetOrTag;
|
|
60374
|
+
return ( target, key, desc ) => applyProfileToMethod( customTag, desc );
|
|
60375
|
+
|
|
60376
|
+
}
|
|
60377
|
+
|
|
60378
|
+
return defaultProfileDecorator( targetOrTag, propertyKey, descriptor );
|
|
60379
|
+
|
|
60380
|
+
}
|
|
60381
|
+
|
|
60382
|
+
/**
|
|
60240
60383
|
* @param {Object} target
|
|
60241
60384
|
* @param {string|symbol} propertyKey
|
|
60242
60385
|
* @param {PropertyDescriptor} descriptor
|
|
60243
60386
|
* @return {PropertyDescriptor}
|
|
60244
60387
|
*/
|
|
60245
|
-
function
|
|
60388
|
+
function defaultProfileDecorator( target, propertyKey, descriptor ) {
|
|
60246
60389
|
|
|
60247
60390
|
const className = target.constructor?.name ?? 'Unknown';
|
|
60248
60391
|
return applyProfileToMethod( `${className}.${String( propertyKey )}`, descriptor );
|