@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.core.js
CHANGED
|
@@ -59738,14 +59738,22 @@ class TextureUtils {
|
|
|
59738
59738
|
* __gnsx_profiler.downloadTrace() // download gnsx-trace.json for Speedscope / Perfetto
|
|
59739
59739
|
* __gnsx_profiler.reset() // clear samples and trace
|
|
59740
59740
|
* __gnsx_profiler.disable()
|
|
59741
|
+
*
|
|
59742
|
+
* Chrome trace: `tid=1` is synchronous work; `@profile` on async methods records the
|
|
59743
|
+
* promise lifetime on `tid=2` so long async does not flatten the main row. For manual
|
|
59744
|
+
* spans, use `beginSpan` / `endSpan` with `{ asyncTimeline: true }` in a `.finally()`.
|
|
59741
59745
|
*/
|
|
59742
59746
|
|
|
59743
59747
|
const RING_SIZE = 120;
|
|
59744
59748
|
const FRAME_BUDGET_MS = 1000 / 60;
|
|
59745
|
-
|
|
59746
|
-
const
|
|
59749
|
+
const TRACE_TID_MAIN = 1;
|
|
59750
|
+
const TRACE_TID_ASYNC = 2;
|
|
59747
59751
|
const NOOP = () => {};
|
|
59748
59752
|
|
|
59753
|
+
const DUMMY_SPAN = Object.freeze( { label: '', t0: 0, _seq: 0 } );
|
|
59754
|
+
const NOOP_BEGIN_SPAN = () => DUMMY_SPAN;
|
|
59755
|
+
const NOOP_END_SPAN = () => {};
|
|
59756
|
+
|
|
59749
59757
|
class ProfilerServiceClass {
|
|
59750
59758
|
|
|
59751
59759
|
constructor() {
|
|
@@ -59759,7 +59767,7 @@ class ProfilerServiceClass {
|
|
|
59759
59767
|
this.cursors = new Map();
|
|
59760
59768
|
/** @type {Map<string, number>} */
|
|
59761
59769
|
this.counts = new Map();
|
|
59762
|
-
/** @type {Map<string, Array<{ startTime: number, startMark: string
|
|
59770
|
+
/** @type {Map<string, Array<{ startTime: number, startMark: string }>>} */
|
|
59763
59771
|
this.marks = new Map();
|
|
59764
59772
|
/** @type {ChromeTraceEvent[]} */
|
|
59765
59773
|
this.traceEvents = [];
|
|
@@ -59785,6 +59793,8 @@ class ProfilerServiceClass {
|
|
|
59785
59793
|
this.traceStartTime = performance.now();
|
|
59786
59794
|
this.begin = this._beginImpl.bind( this );
|
|
59787
59795
|
this.end = this._endImpl.bind( this );
|
|
59796
|
+
this.beginSpan = this._beginSpanImpl.bind( this );
|
|
59797
|
+
this.endSpan = this._endSpanImpl.bind( this );
|
|
59788
59798
|
this._exposeGlobal();
|
|
59789
59799
|
console.log( `[ProfilerService] auto-started from env (profile: ${this._profile})` );
|
|
59790
59800
|
|
|
@@ -59792,6 +59802,8 @@ class ProfilerServiceClass {
|
|
|
59792
59802
|
|
|
59793
59803
|
this.begin = NOOP;
|
|
59794
59804
|
this.end = NOOP;
|
|
59805
|
+
this.beginSpan = NOOP_BEGIN_SPAN;
|
|
59806
|
+
this.endSpan = NOOP_END_SPAN;
|
|
59795
59807
|
|
|
59796
59808
|
}
|
|
59797
59809
|
|
|
@@ -59823,6 +59835,8 @@ class ProfilerServiceClass {
|
|
|
59823
59835
|
this._enabled = true;
|
|
59824
59836
|
this.begin = this._beginImpl.bind( this );
|
|
59825
59837
|
this.end = this._endImpl.bind( this );
|
|
59838
|
+
this.beginSpan = this._beginSpanImpl.bind( this );
|
|
59839
|
+
this.endSpan = this._endSpanImpl.bind( this );
|
|
59826
59840
|
this._exposeGlobal();
|
|
59827
59841
|
console.log( `[ProfilerService] enabled (profile: ${this._profile}) — call __gnsx_profiler.report() or downloadTrace() from the console` );
|
|
59828
59842
|
|
|
@@ -59834,6 +59848,8 @@ class ProfilerServiceClass {
|
|
|
59834
59848
|
this._enabled = false;
|
|
59835
59849
|
this.begin = NOOP;
|
|
59836
59850
|
this.end = NOOP;
|
|
59851
|
+
this.beginSpan = NOOP_BEGIN_SPAN;
|
|
59852
|
+
this.endSpan = NOOP_END_SPAN;
|
|
59837
59853
|
console.log( '[ProfilerService] disabled' );
|
|
59838
59854
|
|
|
59839
59855
|
}
|
|
@@ -59869,22 +59885,8 @@ class ProfilerServiceClass {
|
|
|
59869
59885
|
|
|
59870
59886
|
}
|
|
59871
59887
|
|
|
59872
|
-
const traced = this._profile === 'full' && this.traceEvents.length < MAX_TRACE_EVENTS;
|
|
59873
|
-
if ( traced ) {
|
|
59874
|
-
|
|
59875
|
-
this.traceEvents.push( {
|
|
59876
|
-
name: label,
|
|
59877
|
-
ph: 'B',
|
|
59878
|
-
ts: this._getTraceTimestamp( startTime ),
|
|
59879
|
-
pid: 1,
|
|
59880
|
-
tid: 1,
|
|
59881
|
-
cat: 'gnsx',
|
|
59882
|
-
} );
|
|
59883
|
-
|
|
59884
|
-
}
|
|
59885
|
-
|
|
59886
59888
|
performance.mark( startMark );
|
|
59887
|
-
stack.push( { startTime, startMark
|
|
59889
|
+
stack.push( { startTime, startMark } );
|
|
59888
59890
|
|
|
59889
59891
|
// Push onto the global call stack for exclusive-time tracking.
|
|
59890
59892
|
this.callStack.push( { label, childTime: 0 } );
|
|
@@ -59902,13 +59904,61 @@ class ProfilerServiceClass {
|
|
|
59902
59904
|
if ( stack.length === 0 ) this.marks.delete( label );
|
|
59903
59905
|
|
|
59904
59906
|
const now = performance.now();
|
|
59905
|
-
const { startTime, startMark
|
|
59907
|
+
const { startTime, startMark } = mark;
|
|
59906
59908
|
const duration = now - startTime;
|
|
59907
59909
|
const endMark = `gnsx:${label}:end:${++ this._markId}`;
|
|
59908
59910
|
|
|
59909
59911
|
performance.mark( endMark );
|
|
59910
59912
|
performance.measure( `gnsx:${label}`, startMark, endMark );
|
|
59911
59913
|
|
|
59914
|
+
this._commitDurationSample( label, startTime, duration, TRACE_TID_MAIN );
|
|
59915
|
+
|
|
59916
|
+
}
|
|
59917
|
+
|
|
59918
|
+
/**
|
|
59919
|
+
* Per-invocation span start (pair with {@link ProfilerServiceClass#endSpan}).
|
|
59920
|
+
* Safe for concurrent async with the same label.
|
|
59921
|
+
*
|
|
59922
|
+
* @param {string} label
|
|
59923
|
+
* @return {import('./ProfilerService.js').SpanHandle}
|
|
59924
|
+
*/
|
|
59925
|
+
_beginSpanImpl( label ) {
|
|
59926
|
+
|
|
59927
|
+
const seq = ++ this._markId;
|
|
59928
|
+
const t0 = performance.now();
|
|
59929
|
+
const startMark = `gnsx:${label}:s${seq}:start`;
|
|
59930
|
+
performance.mark( startMark );
|
|
59931
|
+
return { label, t0, _seq: seq, _startMark: startMark };
|
|
59932
|
+
|
|
59933
|
+
}
|
|
59934
|
+
|
|
59935
|
+
/**
|
|
59936
|
+
* @param {import('./ProfilerService.js').SpanHandle} handle
|
|
59937
|
+
* @param {import('./ProfilerService.js').EndSpanOptions} [opts]
|
|
59938
|
+
*/
|
|
59939
|
+
_endSpanImpl( handle, opts ) {
|
|
59940
|
+
|
|
59941
|
+
if ( handle._seq === 0 ) return;
|
|
59942
|
+
|
|
59943
|
+
const now = performance.now();
|
|
59944
|
+
const duration = now - handle.t0;
|
|
59945
|
+
const endMark = `gnsx:${handle.label}:s${handle._seq}:end`;
|
|
59946
|
+
performance.mark( endMark );
|
|
59947
|
+
performance.measure( `gnsx:${handle.label}#${handle._seq}`, handle._startMark, endMark );
|
|
59948
|
+
|
|
59949
|
+
const traceTid = opts?.asyncTimeline === true ? TRACE_TID_ASYNC : TRACE_TID_MAIN;
|
|
59950
|
+
this._commitDurationSample( handle.label, handle.t0, duration, traceTid );
|
|
59951
|
+
|
|
59952
|
+
}
|
|
59953
|
+
|
|
59954
|
+
/**
|
|
59955
|
+
* @param {string} label
|
|
59956
|
+
* @param {number} startTime
|
|
59957
|
+
* @param {number} durationMs
|
|
59958
|
+
* @param {typeof TRACE_TID_MAIN|typeof TRACE_TID_ASYNC} traceTid
|
|
59959
|
+
*/
|
|
59960
|
+
_commitDurationSample( label, startTime, durationMs, traceTid ) {
|
|
59961
|
+
|
|
59912
59962
|
let buffer = this.buffers.get( label );
|
|
59913
59963
|
if ( ! buffer ) {
|
|
59914
59964
|
|
|
@@ -59920,7 +59970,7 @@ class ProfilerServiceClass {
|
|
|
59920
59970
|
}
|
|
59921
59971
|
|
|
59922
59972
|
const cursor = this.cursors.get( label );
|
|
59923
|
-
buffer[ cursor ] =
|
|
59973
|
+
buffer[ cursor ] = durationMs;
|
|
59924
59974
|
this.cursors.set( label, ( cursor + 1 ) % RING_SIZE );
|
|
59925
59975
|
this.counts.set( label, Math.min( ( this.counts.get( label ) + 1 ), RING_SIZE ) );
|
|
59926
59976
|
|
|
@@ -59932,7 +59982,7 @@ class ProfilerServiceClass {
|
|
|
59932
59982
|
if ( top !== undefined && top.label === label ) {
|
|
59933
59983
|
|
|
59934
59984
|
this.callStack.pop();
|
|
59935
|
-
const selfTime = Math.max( 0,
|
|
59985
|
+
const selfTime = Math.max( 0, durationMs - top.childTime );
|
|
59936
59986
|
|
|
59937
59987
|
let selfBuffer = this.selfBuffers.get( label );
|
|
59938
59988
|
if ( ! selfBuffer ) {
|
|
@@ -59951,7 +60001,7 @@ class ProfilerServiceClass {
|
|
|
59951
60001
|
|
|
59952
60002
|
// Propagate inclusive duration to the parent scope's child accumulator.
|
|
59953
60003
|
const parent = this.callStack.length > 0 ? this.callStack[ this.callStack.length - 1 ] : undefined;
|
|
59954
|
-
if ( parent !== undefined ) parent.childTime +=
|
|
60004
|
+
if ( parent !== undefined ) parent.childTime += durationMs;
|
|
59955
60005
|
|
|
59956
60006
|
} else {
|
|
59957
60007
|
|
|
@@ -59960,14 +60010,16 @@ class ProfilerServiceClass {
|
|
|
59960
60010
|
|
|
59961
60011
|
}
|
|
59962
60012
|
|
|
59963
|
-
if (
|
|
60013
|
+
if ( traceTid ) {
|
|
59964
60014
|
|
|
60015
|
+
const traceName = traceTid === TRACE_TID_ASYNC ? `${label} (promise)` : label;
|
|
59965
60016
|
this.traceEvents.push( {
|
|
59966
|
-
name:
|
|
59967
|
-
ph: '
|
|
59968
|
-
ts: this._getTraceTimestamp(
|
|
60017
|
+
name: traceName,
|
|
60018
|
+
ph: 'X',
|
|
60019
|
+
ts: Math.round( this._getTraceTimestamp( startTime ) ),
|
|
60020
|
+
dur: Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
59969
60021
|
pid: 1,
|
|
59970
|
-
tid:
|
|
60022
|
+
tid: traceTid,
|
|
59971
60023
|
cat: 'gnsx',
|
|
59972
60024
|
} );
|
|
59973
60025
|
|
|
@@ -60078,13 +60130,53 @@ class ProfilerServiceClass {
|
|
|
60078
60130
|
*/
|
|
60079
60131
|
exportChromeTrace() {
|
|
60080
60132
|
|
|
60133
|
+
const slices = [ ...this.traceEvents ];
|
|
60134
|
+
slices.sort( ( a, b ) => {
|
|
60135
|
+
|
|
60136
|
+
if ( a.ts !== b.ts ) return a.ts - b.ts;
|
|
60137
|
+
if ( a.tid !== b.tid ) return a.tid - b.tid;
|
|
60138
|
+
return a.name.localeCompare( b.name );
|
|
60139
|
+
|
|
60140
|
+
} );
|
|
60141
|
+
const hasAsync = slices.some( e => e.tid === TRACE_TID_ASYNC );
|
|
60142
|
+
/** @type {import('./ProfilerService.js').ChromeTraceMetadataEvent[]} */
|
|
60143
|
+
const prefix = [
|
|
60144
|
+
{
|
|
60145
|
+
cat: '__metadata',
|
|
60146
|
+
name: 'process_name',
|
|
60147
|
+
ph: 'M',
|
|
60148
|
+
pid: 1,
|
|
60149
|
+
tid: 0,
|
|
60150
|
+
ts: 0,
|
|
60151
|
+
args: { name: 'Genesys Profiler' },
|
|
60152
|
+
},
|
|
60153
|
+
{
|
|
60154
|
+
cat: '__metadata',
|
|
60155
|
+
name: 'thread_name',
|
|
60156
|
+
ph: 'M',
|
|
60157
|
+
pid: 1,
|
|
60158
|
+
tid: TRACE_TID_MAIN,
|
|
60159
|
+
ts: 0,
|
|
60160
|
+
args: { name: 'Main thread' },
|
|
60161
|
+
},
|
|
60162
|
+
];
|
|
60163
|
+
if ( hasAsync ) {
|
|
60164
|
+
|
|
60165
|
+
prefix.push( {
|
|
60166
|
+
cat: '__metadata',
|
|
60167
|
+
name: 'thread_name',
|
|
60168
|
+
ph: 'M',
|
|
60169
|
+
pid: 1,
|
|
60170
|
+
tid: TRACE_TID_ASYNC,
|
|
60171
|
+
ts: 0,
|
|
60172
|
+
args: { name: 'Async (promise lifetime)' },
|
|
60173
|
+
} );
|
|
60174
|
+
|
|
60175
|
+
}
|
|
60176
|
+
|
|
60081
60177
|
return {
|
|
60082
60178
|
displayTimeUnit: 'ms',
|
|
60083
|
-
traceEvents: [
|
|
60084
|
-
{ name: 'process_name', ph: 'M', pid: 1, args: { name: 'Genesys Profiler' } },
|
|
60085
|
-
{ name: 'thread_name', ph: 'M', pid: 1, tid: 1, args: { name: 'Main Thread' } },
|
|
60086
|
-
...this.traceEvents,
|
|
60087
|
-
],
|
|
60179
|
+
traceEvents: [ ...prefix, ...slices ],
|
|
60088
60180
|
};
|
|
60089
60181
|
|
|
60090
60182
|
}
|
|
@@ -60189,22 +60281,38 @@ class ProfilerServiceClass {
|
|
|
60189
60281
|
* @property {number|undefined} selfFrameBudget Exclusive time as percentage of a 60 fps frame budget.
|
|
60190
60282
|
*/
|
|
60191
60283
|
|
|
60284
|
+
/**
|
|
60285
|
+
* @typedef {Object} SpanHandle
|
|
60286
|
+
* @property {string} label
|
|
60287
|
+
* @property {number} t0
|
|
60288
|
+
* @property {number} _seq
|
|
60289
|
+
* @property {string} _startMark
|
|
60290
|
+
*/
|
|
60291
|
+
|
|
60292
|
+
/**
|
|
60293
|
+
* @typedef {Object} EndSpanOptions
|
|
60294
|
+
* @property {boolean} [asyncTimeline] When true, trace slice uses `tid=2` (virtual async row).
|
|
60295
|
+
*/
|
|
60296
|
+
|
|
60192
60297
|
/**
|
|
60193
60298
|
* @typedef {Object} ChromeTraceEvent
|
|
60194
60299
|
* @property {string} name
|
|
60195
|
-
* @property {'
|
|
60300
|
+
* @property {'X'} ph
|
|
60196
60301
|
* @property {number} ts
|
|
60302
|
+
* @property {number} dur
|
|
60197
60303
|
* @property {1} pid
|
|
60198
|
-
* @property {
|
|
60304
|
+
* @property {number} tid
|
|
60199
60305
|
* @property {'gnsx'} cat
|
|
60200
60306
|
*/
|
|
60201
60307
|
|
|
60202
60308
|
/**
|
|
60203
60309
|
* @typedef {Object} ChromeTraceMetadataEvent
|
|
60310
|
+
* @property {'__metadata'} cat
|
|
60204
60311
|
* @property {'process_name'|'thread_name'} name
|
|
60205
60312
|
* @property {'M'} ph
|
|
60206
60313
|
* @property {1} pid
|
|
60207
|
-
* @property {
|
|
60314
|
+
* @property {number} [tid]
|
|
60315
|
+
* @property {0} ts
|
|
60208
60316
|
* @property {{ name: string }} args
|
|
60209
60317
|
*/
|
|
60210
60318
|
|
|
@@ -60220,28 +60328,38 @@ class ProfilerServiceClass {
|
|
|
60220
60328
|
|
|
60221
60329
|
const ProfilerService = new ProfilerServiceClass();
|
|
60222
60330
|
|
|
60331
|
+
function isThenable( x ) {
|
|
60332
|
+
|
|
60333
|
+
return (
|
|
60334
|
+
( typeof x === 'object' || typeof x === 'function' ) &&
|
|
60335
|
+
x !== null &&
|
|
60336
|
+
typeof x.then === 'function'
|
|
60337
|
+
);
|
|
60338
|
+
|
|
60339
|
+
}
|
|
60340
|
+
|
|
60223
60341
|
function applyProfileToMethod( label, descriptor ) {
|
|
60224
60342
|
|
|
60225
60343
|
const original = descriptor.value;
|
|
60226
60344
|
|
|
60227
60345
|
function profiled( ...args ) {
|
|
60228
60346
|
|
|
60229
|
-
ProfilerService.
|
|
60347
|
+
const span = ProfilerService.beginSpan( label );
|
|
60230
60348
|
try {
|
|
60231
60349
|
|
|
60232
60350
|
const result = original.apply( this, args );
|
|
60233
|
-
if ( result
|
|
60351
|
+
if ( isThenable( result ) ) {
|
|
60234
60352
|
|
|
60235
|
-
return result.finally( () => ProfilerService.
|
|
60353
|
+
return Promise.resolve( result ).finally( () => ProfilerService.endSpan( span, { asyncTimeline: true } ) );
|
|
60236
60354
|
|
|
60237
60355
|
}
|
|
60238
60356
|
|
|
60239
|
-
ProfilerService.
|
|
60357
|
+
ProfilerService.endSpan( span );
|
|
60240
60358
|
return result;
|
|
60241
60359
|
|
|
60242
60360
|
} catch ( error ) {
|
|
60243
60361
|
|
|
60244
|
-
ProfilerService.
|
|
60362
|
+
ProfilerService.endSpan( span );
|
|
60245
60363
|
throw error;
|
|
60246
60364
|
|
|
60247
60365
|
}
|
|
@@ -60255,14 +60373,39 @@ function applyProfileToMethod( label, descriptor ) {
|
|
|
60255
60373
|
|
|
60256
60374
|
/**
|
|
60257
60375
|
* Method decorator that profiles the decorated method.
|
|
60258
|
-
*
|
|
60376
|
+
* Default label: `ClassName.methodName`. Pass a custom tag via `@profile('My tag')`.
|
|
60259
60377
|
*
|
|
60378
|
+
* @param {Object|string} [targetOrTag]
|
|
60379
|
+
* @param {string|symbol} [propertyKey]
|
|
60380
|
+
* @param {PropertyDescriptor} [descriptor]
|
|
60381
|
+
* @return {PropertyDescriptor|function(Object, string|symbol, PropertyDescriptor): PropertyDescriptor}
|
|
60382
|
+
*/
|
|
60383
|
+
function profile( targetOrTag, propertyKey, descriptor ) {
|
|
60384
|
+
|
|
60385
|
+
if ( arguments.length === 0 ) {
|
|
60386
|
+
|
|
60387
|
+
return defaultProfileDecorator;
|
|
60388
|
+
|
|
60389
|
+
}
|
|
60390
|
+
|
|
60391
|
+
if ( arguments.length === 1 && typeof targetOrTag === 'string' ) {
|
|
60392
|
+
|
|
60393
|
+
const customTag = targetOrTag;
|
|
60394
|
+
return ( target, key, desc ) => applyProfileToMethod( customTag, desc );
|
|
60395
|
+
|
|
60396
|
+
}
|
|
60397
|
+
|
|
60398
|
+
return defaultProfileDecorator( targetOrTag, propertyKey, descriptor );
|
|
60399
|
+
|
|
60400
|
+
}
|
|
60401
|
+
|
|
60402
|
+
/**
|
|
60260
60403
|
* @param {Object} target
|
|
60261
60404
|
* @param {string|symbol} propertyKey
|
|
60262
60405
|
* @param {PropertyDescriptor} descriptor
|
|
60263
60406
|
* @return {PropertyDescriptor}
|
|
60264
60407
|
*/
|
|
60265
|
-
function
|
|
60408
|
+
function defaultProfileDecorator( target, propertyKey, descriptor ) {
|
|
60266
60409
|
|
|
60267
60410
|
const className = target.constructor?.name ?? 'Unknown';
|
|
60268
60411
|
return applyProfileToMethod( `${className}.${String( propertyKey )}`, descriptor );
|