dcp-client 4.2.31 → 4.2.32

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.
@@ -1 +1 @@
1
- {"browser":["deny-node","kvin/kvin.js","script-load-wrapper","wrap-event-listeners","event-loop-virtualization","gpu-timers","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"node":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","wrap-event-listeners","event-loop-virtualization","gpu-timers","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"native":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","wrap-event-listeners","event-loop-virtualization","gpu-timers","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"webGpuNative":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","wrap-event-listeners","event-loop-virtualization","webgpu-worker-environment","gpu-timers","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"nodeTesting":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","wrap-event-listeners","event-loop-virtualization","gpu-timers","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap","testing.js"],"testing":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","wrap-event-listeners","event-loop-virtualization","gpu-timers","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap","testing.js"]}
1
+ {"browser":["deny-node","kvin/kvin.js","script-load-wrapper","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"node":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"native":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"webGpuNative":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","wrap-event-listeners","timer-classes","webgpu-worker-environment","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"nodeTesting":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap","testing.js"],"testing":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap","testing.js"]}
@@ -649,11 +649,10 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
649
649
  // Set values to true to disallow access to symbols
650
650
  const blockList = {
651
651
  OffscreenCanvas: false,
652
+ WebGPUWindow: false,
653
+ GPU: false,
652
654
  };
653
655
 
654
- const blockListRequirements = {
655
- OffscreenCanvas: "environment.offscreenCanvas"
656
- };
657
656
  /**
658
657
  * Applies a allow list and a block list of properties to an object. After this function, if someone tries
659
658
  * to access non-allowed or blocked properties, a warning is logged and it will return undefined. The allow
@@ -663,10 +662,9 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
663
662
  * @param {object} obj - The object, which will have the allow list applied to its properties.
664
663
  * @param {Set} allowList - A set of properties to allow people to access.
665
664
  * @param {Set} blockList - An object of property names mapping to booleans to indicate whether access is allowed or not.
666
- * @param {Set} blockListRequirements - An object of property names mapping requirement path strings, used to print useful warnings.
667
665
  * @param {Set} polyfills - An object of property names that have been polyfilled.
668
666
  */
669
- function applyAccessLists(obj, allowList, blockList = {}, blockListRequirements = {}, polyfills = {}) {
667
+ function applyAccessLists(obj, allowList, blockList = {}, polyfills = {}) {
670
668
  if (!obj) { return; }
671
669
  Object.getOwnPropertyNames(obj).forEach(function (prop) {
672
670
  if (Object.getOwnPropertyDescriptor(obj, prop).configurable) {
@@ -693,7 +691,6 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
693
691
  } else if (prop in blockList) {
694
692
  let isSet = false;
695
693
  let blocked = blockList[prop];
696
- let requirement = blockListRequirements[prop];
697
694
  let propValue = obj[prop];
698
695
  Object.defineProperty(obj, prop, {
699
696
  get: function () {
@@ -761,38 +758,32 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
761
758
  // before we allow Object's properties
762
759
 
763
760
  var global = typeof globalThis === 'undefined' ? self : globalThis;
764
- // Save them in scope because they'll get hidden by the allowList
765
- let _allowList = allowList;
766
- let _blockList = blockList;
767
- let _polyfills = polyfills;
768
761
 
769
762
  // Ternary expression to avoid a ReferenceError on navigator
770
- let _navigator = typeof navigator !== 'undefined' ? navigator : undefined;
771
763
  let _GPU = ((typeof navigator !== 'undefined') && (typeof navigator.gpu !== 'undefined')) ? navigator.gpu :
772
764
  (typeof GPU !== 'undefined'? GPU : undefined);
773
- let _blockListRequirements = blockListRequirements;
774
- let _applyAccessLists = applyAccessLists;
775
- let _applyPolyfills = applyPolyfills;
776
765
 
777
766
  for (let g = global; g.__proto__ && (g.__proto__ !== Object); g = g.__proto__) {
778
- applyAccessLists(g, allowList, blockList, blockListRequirements, polyfills);
767
+ applyAccessLists(g, allowList, blockList, polyfills);
779
768
  }
780
769
 
781
- if (typeof _navigator === 'undefined') {
782
- _navigator = navigator = {
770
+ if (typeof navigator === 'undefined')
771
+ {
772
+ navigator = {
783
773
  userAgent: 'not a browser',
784
- gpu: _GPU,
774
+ gpu: _GPU,
785
775
  };
786
- } else {
776
+ }
777
+ else if (!protectedStorage.createdNewNavigator)
778
+ {
787
779
  // We also want to allowList certain parts of navigator, but not others.
788
-
789
- navAllowlist = new Set([
780
+ const navAllowlist = new Set([
790
781
  'userAgent',
791
782
  'gpu',
792
783
  ]);
793
784
  let navPolyfill = {
794
785
  userAgent: typeof navigator.userAgent !== 'undefined'? navigator.userAgent : 'not a browser',
795
- gpu: _GPU
786
+ gpu: _GPU,
796
787
  };
797
788
  applyAccessLists(navigator.__proto__, navAllowlist, {}, {}, navPolyfill);
798
789
  applyPolyfills(navigator.__proto__, navPolyfill);
@@ -810,7 +801,21 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
810
801
  global.requestAnimationFrame = callback => setTimeout(callback, 0);
811
802
  }
812
803
 
813
- if (protectedStorage.hasWebglSupport()) {
804
+ function hasWebGLSupport()
805
+ {
806
+ try
807
+ {
808
+ const canvas = new OffscreenCanvas(1,1);
809
+ return Boolean(canvas.getContext('webgl') || canvas.getContext('webgl2'));
810
+ }
811
+ catch
812
+ {
813
+ return false;
814
+ }
815
+
816
+ }
817
+
818
+ if (hasWebGLSupport()) {
814
819
 
815
820
  // This deals with Firefox bug 1529995, which causes the tab to crash if fenceSync is called.
816
821
  if (navigator.userAgent.indexOf('Firefox') >= 0) {
@@ -840,6 +845,8 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
840
845
  // Assume the scheduler gave us a nicely-shaped req object.
841
846
  const requirements = event.requirements;
842
847
  blockList.OffscreenCanvas = !requirements.environment.offscreenCanvas;
848
+ blockList.WebGPUWindow = !requirements.environment.webgpu;
849
+ blockList.GPU = !requirements.environment.webgpu;
843
850
  applyAllAccessLists();
844
851
 
845
852
  ring1PostMessage({ request: 'applyRequirementsDone' });
@@ -13,19 +13,13 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
13
13
  {
14
14
  // This file starts at ring 2, but transitions to ring 3 partway through it.
15
15
  const ring2PostMessage = self.postMessage;
16
- let ring3PostMessage
16
+ let ring3PostMessage;
17
+ let totalTime;
17
18
 
18
19
  bravojs.ww = {}
19
20
  bravojs.ww.allDeps = []
20
21
  bravojs.ww.provideCallbacks = {}
21
22
 
22
- async function tryFlushMicroTaskQueue()
23
- {
24
- await Promise.resolve();
25
- await Promise.resolve();
26
- await Promise.resolve();
27
- }
28
-
29
23
  //Listens for postMessage from the sandbox
30
24
  addEventListener('message', async (event) => {
31
25
  let message = event
@@ -180,27 +174,34 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
180
174
  });
181
175
  };
182
176
 
183
- /* Report the GPU and total metrics for a slice that was rejected */
184
- function reportRejectedGPUandTotal (t0) {
185
- try
186
- {
187
- const total = performance.now() - t0;
188
- const webGL = protectedStorage.getAndResetWebGLTimer();
189
- protectedStorage.subtractWebGLTimeFromCPUTime(webGL);
190
- ring3PostMessage({ request: 'measurement', total, webGL });
191
- }
192
- catch (error)
193
- {
194
- ring3PostMessage({ request: 'sandboxError', error });
195
- }
177
+ /* Report metrics to sandbox/supervisor */
178
+ async function reportTimes ()
179
+ {
180
+ const timers = protectedStorage.timers;
181
+ const webGL = timers.webGL.duration();
182
+ const webGPU = await timers.webGPU.duration();
183
+
184
+ timers.cpu.mostRecentInterval.stop();
185
+ let CPU = timers.cpu.duration();
186
+ CPU -= webGL; // webGL is synchronous gpu usage, subtract that from cpu time.
187
+
188
+ totalTime.stop();
189
+ const total = totalTime.length;
190
+
191
+ timers.cpu.reset();
192
+ timers.webGL.reset();
193
+ timers.webGPU.reset();
194
+ protectedStorage.clearAllTimers();
195
+
196
+ ring3PostMessage({ request: 'measurement', total, webGL, webGPU, CPU });
196
197
  }
197
198
 
198
199
  /* Report an error from the work function to the supervisor */
199
- function reportError (t0, error)
200
+ function reportError (error)
200
201
  {
201
202
  let err = { message: 'initial state', name: 'initial state' };
202
203
 
203
- for (prop of [ 'message', 'name', 'code', 'stack', 'lineNumber', 'columnNumber' ])
204
+ for (const prop of [ 'message', 'name', 'code', 'stack', 'lineNumber', 'columnNumber' ])
204
205
  {
205
206
  try
206
207
  {
@@ -214,31 +215,25 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
214
215
  err['message'] = protectedStorage.workRejectReason;
215
216
  err['name'] = 'EWORKREJECT';
216
217
  err['stack'] = 'Slice was rejected in the sandbox by work.reject'
217
- reportRejectedGPUandTotal(t0);
218
+ reportTimes().then(() => ring3PostMessage({ request: 'workError', error: err }));
219
+ }
220
+ else
221
+ {
222
+ ring3PostMessage({request: 'workError', error: err});
218
223
  }
219
-
220
- ring3PostMessage({request: 'workError', error: err});
221
224
  }
222
225
 
223
226
  /**
224
227
  * Report a result from work function and metrics to the supervisor.
225
- * @param t0 timestamp when work began
226
228
  * @param result the value that the work function returned promise resolved to
227
229
  */
228
- function reportResult (t0, result)
230
+ function reportResult (result)
229
231
  {
230
- try
231
- {
232
- const total = performance.now() - t0 + 1; /* +1 to ensure we never have "0 second slices" */
233
- const webGL = protectedStorage.getAndResetWebGLTimer();
234
- protectedStorage.subtractWebGLTimeFromCPUTime(webGL); /* Because webGL is sync but doesn't use CPU */
235
- ring3PostMessage({ request: 'measurement', total, webGL });
232
+ reportTimes().then(() => {
236
233
  ring3PostMessage({ request: 'complete', result });
237
- }
238
- catch(error)
239
- {
234
+ }).catch((error) => {
240
235
  ring3PostMessage({ request: 'sandboxError', error });
241
- }
236
+ });
242
237
  }
243
238
 
244
239
  /**
@@ -266,12 +261,14 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
266
261
  rejection = error;
267
262
  }
268
263
 
269
- /* try to flush any pending tasks on the microtask queue, then flush any pending console events,
270
- * especially in the case of a repeating message that hasn't been emitted yet
271
- */
272
- try { await tryFlushMicroTaskQueue(); } catch(e) {};
264
+ // flush any pending console events, especially in the case of a repeating message that hasn't been emitted yet
273
265
  try { flushLastLog(); } catch(e) {};
274
- try { protectedStorage.markCPUTimeAsDone(); } catch(e) {};
266
+ try
267
+ {
268
+ protectedStorage.lockTimers(); // lock timers so no new timeouts will be run.
269
+ await new Promise(r => protectedStorage.realSetTimeout(r)); // flush microtask queue
270
+ }
271
+ catch(e) {}
275
272
 
276
273
  if (rejection)
277
274
  errorCallback(rejection);
@@ -290,12 +287,21 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
290
287
  function runWorkFunction(datum)
291
288
  {
292
289
  // Measure performance directly before and after the job to get as accurate total time as
293
- const t0 = performance.now();
294
-
290
+ totalTime = new protectedStorage.TimeInterval();
291
+
292
+ // Guarantee CPU timers are cleared before the main work function runs.
293
+ // This is necessary because the GPU object has been wrapped to make setTimeout calls to
294
+ // allow for measurement. However, when these timeouts are invoked during capability
295
+ // calculations, they are erroneously measured as CPU time. This can cause CPU time > total time
296
+ // and CPUDensity > 1
297
+ protectedStorage.timers.cpu.reset();
298
+ protectedStorage.timers.webGPU.reset(); // also reset other timers for saftey
299
+ protectedStorage.timers.webGL.reset();
300
+ protectedStorage.unlockTimers();
295
301
  /* Use setTimeout trampoline to
296
302
  * 1. shorten stack
297
303
  * 2. initialize the event loop measurement code
298
304
  */
299
- protectedStorage.setTimeout(() => runWorkFunction_inner(datum, (result) => reportResult(t0, result), (rejection) => reportError(t0, rejection)));
305
+ protectedStorage.setTimeout(() => runWorkFunction_inner(datum, (result) => reportResult(result), (rejection) => reportError(rejection)));
300
306
  }
301
307
  }); /* end of fn */
@@ -64,8 +64,8 @@ self.wrapScriptLoading({ scriptName: 'calculate-capabilities' }, function calcul
64
64
  title: 'DCP-evaluator',
65
65
  visible: false,
66
66
  });
67
-
68
- const adapter = await GPU.requestAdapter({ gpuWindow });
67
+
68
+ const adapter = await GPU.requestAdapter({ window: gpuWindow });
69
69
  await adapter.requestDevice(adapter.extensions);
70
70
  } else {
71
71
  const adapter = await navigator.gpu.requestAdapter();
@@ -102,6 +102,7 @@ self.wrapScriptLoading({ scriptName: 'calculate-capabilities' }, function calcul
102
102
  //Any error in this using an extensions should likely result in specifications for that capability being set to false.
103
103
  offscreenCanvas = false;
104
104
  }
105
+ protectedStorage.timers.webGL.reset() // Testing for webGL != using webGL.
105
106
  }
106
107
 
107
108
  try {
@@ -19,15 +19,24 @@
19
19
  self.wrapScriptLoading({ scriptName: 'event-loop-virtualization' }, function eventLoopVirtualization$$fn(protectedStorage, ring0PostMessage)
20
20
  {
21
21
  (function privateScope(realSetTimeout, realSetInterval, realSetImmediate, realClearTimeout, realClearInterval, realClearImmediate) {
22
- let totalCPUTime = 0;
23
- let startTime;
22
+ const cpuTimer = protectedStorage.timers.cpu;
24
23
  const events = [];
25
24
  events.serial = 0;
25
+ let timersLocked = false;
26
+
27
+ protectedStorage.lockTimers = function lockTimers() { timersLocked = true; }
28
+ protectedStorage.unlockTimers = function unlockTimers() { timersLocked = false; }
29
+
26
30
 
27
31
  function sortEvents() {
28
32
  events.sort(function (a, b) { return a.when - b.when; });
29
33
  }
30
34
 
35
+ /*
36
+ * Assumption: serviceEvents must only be triggered if there is an event waiting to
37
+ * be run. If there are no pending events (or the last one is removed), the trigger
38
+ * to call serviceEvents next should be removed.
39
+ */
31
40
  function serviceEvents()
32
41
  {
33
42
  serviceEvents.timeout = null;
@@ -35,56 +44,37 @@ self.wrapScriptLoading({ scriptName: 'event-loop-virtualization' }, function eve
35
44
  serviceEvents.servicing = true;
36
45
  serviceEvents.sliceIsFinished = false;
37
46
 
38
- startTime = performance.now();
39
- let now = Date.now();
47
+ serviceEvents.interval = new protectedStorage.TimeInterval();
48
+ cpuTimer.push(serviceEvents.interval);
40
49
 
41
50
  sortEvents();
42
- if (events[0].when <= now)
51
+ const event = events.shift();
52
+ if (event.eventType === 'timer')
43
53
  {
44
- const event = events.shift();
45
- if (event.eventType === 'timer')
54
+ serviceEvents.executingTimeout = realSetTimeout(event.fn, 0, event.args);
55
+ if (event.recur)
46
56
  {
47
- serviceEvents.executingTimeout = realSetTimeout(event.fn, 0, event.args);
48
- if (event.recur)
49
- {
50
- event.when = Date.now() + event.recur;
51
- events.push(event);
52
- sortEvents();
53
- }
57
+ event.when = Date.now() + event.recur;
58
+ events.push(event);
59
+ sortEvents();
54
60
  }
55
- // Can add handles for events to the event loop as needed (ie messages)
56
61
  }
62
+ // Can add handles for events to the event loop as needed (ie messages)
57
63
 
58
64
  // Measure the time on the event loop after everything has executed
59
65
  serviceEvents.measurerTimeout = realSetTimeout(endOfRealEventCycle,1);
60
66
  function endOfRealEventCycle()
61
67
  {
62
68
  serviceEvents.servicing = false;
63
- if (!serviceEvents.sliceIsFinished)
69
+ serviceEvents.interval.stop();
70
+
71
+ if (!serviceEvents.sliceIsFinished && events.length)
64
72
  {
65
- const endTime = performance.now();
66
- totalCPUTime += endTime - startTime;
67
-
68
- // Set timeout to rerun this function if there are events remaining that just can't be used yet
69
- if (events.length > 0)
70
- {
71
- serviceEvents.nextTimeout = events[0].when
72
- serviceEvents.timeout = realSetTimeout(serviceEvents, events[0].when - Date.now());
73
- }
73
+ serviceEvents.nextTimeout = events[0].when
74
+ serviceEvents.timeout = realSetTimeout(serviceEvents, events[0].when - Date.now());
74
75
  }
75
76
  }
76
77
  }
77
- protectedStorage.markCPUTimeAsDone = function markCPUTimeAsDone()
78
- {
79
- const endTime = performance.now();
80
- totalCPUTime += endTime - startTime;
81
- serviceEvents.sliceIsFinished = true;
82
- }
83
-
84
- protectedStorage.subtractWebGLTimeFromCPUTime = function subtractCPUTime(time)
85
- {
86
- totalCPUTime -= time;
87
- }
88
78
 
89
79
  /** Execute callback after at least timeout ms.
90
80
  *
@@ -94,6 +84,10 @@ self.wrapScriptLoading({ scriptName: 'event-loop-virtualization' }, function eve
94
84
  * @returns {object} A value which may be used as the timeoutId parameter of clearTimeout()
95
85
  */
96
86
  setTimeout = function eventLoop$$Worker$setTimeout(callback, timeout, arg) {
87
+ // Work function has resolved, Don't let client init any new timeouts.
88
+ if (timersLocked)
89
+ return {};
90
+
97
91
  timeout = timeout || 0;
98
92
  let timer, args;
99
93
  if (typeof callback === 'string') {
@@ -237,34 +231,8 @@ self.wrapScriptLoading({ scriptName: 'event-loop-virtualization' }, function eve
237
231
  serviceEvents.sliceIsFinished = false;
238
232
  }
239
233
 
240
- addEventListener('message', async (event) => {
241
- try {
242
- if (event.request === 'clearTimers') {
243
- clearAllTimers();
244
- ring0PostMessage({
245
- request: 'clearTimersDone',
246
- });
247
- }
248
- else if (event.request === 'resetAndGetCPUTime')
249
- {
250
- const cpuTime = totalCPUTime;
251
- totalCPUTime = 0;
252
- ring0PostMessage({
253
- request: 'totalCPUTime',
254
- CPU: cpuTime
255
- })
256
- }
257
- } catch (error) {
258
- ring0PostMessage({
259
- request: 'error',
260
- error: {
261
- name: error.name,
262
- message: error.message,
263
- stack: error.stack,
264
- },
265
- });
266
- }
267
- });
234
+ protectedStorage.clearAllTimers = clearAllTimers;
235
+
268
236
  })(self.setTimeout, self.setInterval, self.setImmediate, self.clearTimeout, self.clearInterval, self.clearImmediate);
269
237
 
270
238
  self.setTimeout = setTimeout;
@@ -0,0 +1,226 @@
1
+ /**
2
+ * @file timer-classes.js
3
+ * This file creates classes that will be required for timing.
4
+ *
5
+ * The 4 classes defined are:
6
+ * - TimeInterval: measure an interval of time.
7
+ * - TimeThing: generic collection of TimeIntervals
8
+ * - TimeCPU: collection of TimeIntervals, with a reference to the most recent interval.
9
+ * - TimeWebGPU: collection of TimeIntervals, with duration not counting overlap, and awaiting for GPU completion event.
10
+ *
11
+ * TimeInterval is an object shaped like {start: X, stop: Y} with a few additional functions to stop the interval.
12
+ * Start is set to the current time immediately when the interval is created, and stop is set when the `stop` function
13
+ * is called. If the length of an interval is accessed before the interval is stopped, it will throw an error.
14
+ * A TimeThing contains a possibly-overlapping list of TimeIntervals. It adds a duration function to calculate the total
15
+ * time of all intervals (double-counting any overlaps). If the Thing may need to have it's final interval stopped in an
16
+ * area that won't otherwise have a reference to that interval, TimeCPU can be used. TimeCPU only adds a reference to the
17
+ * most recent interval to be added to the list.
18
+ * TimeWebGPU is special due to the challenges of timing a GPU from Javascript. It's duration function is async, since
19
+ * the moment the GPU is finished is unknowable until it reports it's finished. If a work function adds a task to the GPU,
20
+ * but resolves before the GPU is finished, we need to carefully to wait for the GPU to be finished before the duration
21
+ * can be calculated. TimeWebGPU has this built-in to the duration function.
22
+
23
+ * @author Ryan Saweczko <ryansaweczko@kingsds.network>
24
+ * @date Aug 2022
25
+ */
26
+
27
+ // @ts-nocheck
28
+
29
+ self.wrapScriptLoading({ scriptName: 'timer-classes' }, function timerClasses$$fn(protectedStorage)
30
+ {
31
+ /**
32
+ * Time interval class.
33
+ *
34
+ * Contains a start time and end time. The start time is set immediately when the interval is created,
35
+ * and the end time is set when `stop` is called. Once the interval has been stopped, the length
36
+ * can be accessed (getting interval.length before it's stopped is an error).
37
+ */
38
+ function TimeInterval()
39
+ {
40
+ this.start = performance.now();
41
+ this.end = null;
42
+ }
43
+
44
+ Object.defineProperty(TimeInterval.prototype, 'length', {
45
+ get: function length()
46
+ {
47
+ if (!this.end)
48
+ throw new Error("Invalid length: interval hasn't been stopped");
49
+ return this.end - this.start;
50
+ }
51
+ });
52
+
53
+ /**
54
+ * Stop a timer. The `end` time is set.
55
+ */
56
+ TimeInterval.prototype.stop = function stop()
57
+ {
58
+ if (this.end)
59
+ return false
60
+ this.end = performance.now();
61
+ return true;
62
+ }
63
+
64
+ /**
65
+ * Check if the interval has been stopped (end time has been set)
66
+ */
67
+ TimeInterval.prototype.hasEnded = function hasEnded()
68
+ {
69
+ return typeof this.end === 'number';
70
+ };
71
+
72
+ protectedStorage.TimeInterval = TimeInterval;
73
+
74
+ /**
75
+ * Time Thing class
76
+ *
77
+ * Generic collection of time intervals. Contains a list
78
+ * of intervals, and provides a way to get the total time
79
+ * of all intervals.
80
+ */
81
+ function TimeThing()
82
+ {
83
+ this.intervals = [];
84
+ }
85
+
86
+ /**
87
+ * Get the total length of all intervals. If the intervals are overlapping,
88
+ * the overlapping time will be counted twice.
89
+ */
90
+ TimeThing.prototype.duration = function totalDuration()
91
+ {
92
+ let sum = 0;
93
+ for (let interval of this.intervals)
94
+ sum += interval.length;
95
+ return sum;
96
+ }
97
+
98
+ /**
99
+ * Add a new interval.
100
+ *
101
+ * @Param {TimeInterval} interval - new interval to add to the collection
102
+ */
103
+ TimeThing.prototype.push = function push(interval)
104
+ {
105
+ this.intervals.push(interval);
106
+ }
107
+
108
+ /**
109
+ * Reset the interval. Resets the interval list to an empty list.
110
+ */
111
+ TimeThing.prototype.reset = function reset()
112
+ {
113
+ this.intervals = [];
114
+ }
115
+
116
+ /**
117
+ * Time CPU class
118
+ *
119
+ * Inherits from TimeThing, but adds a reference to the most recent interval
120
+ * to be added.
121
+ */
122
+ function TimeCPU()
123
+ {
124
+ TimeThing.call(this);
125
+ this.mostRecentInterval = null;
126
+ }
127
+ TimeCPU.prototype = new TimeThing();
128
+
129
+ /**
130
+ * Add a new interval. `this.mostRecentInterval` is set to this interval.
131
+ *
132
+ * @Param {TimeInterval} interval - new interval to add to the collection
133
+ */
134
+ TimeCPU.prototype.push = function push(ele)
135
+ {
136
+ this.intervals.push(ele);
137
+ this.mostRecentInterval = ele;
138
+ }
139
+
140
+ /**
141
+ * Time WebGPU class
142
+ *
143
+ * Inherits from TimeThing, but adds a reference to the most recent interval,
144
+ * as well as large changes to the duration function to handle overlapping time intervals.
145
+ */
146
+ function TimeWebGPU()
147
+ {
148
+ TimeThing.call(this);
149
+ this.latestWebGPUCall = null;
150
+ }
151
+ TimeWebGPU.prototype = new TimeThing();
152
+
153
+ /**
154
+ * Add a new interval. `this.mostRecentInterval` is set to this interval.
155
+ *
156
+ * @Param {object} interval - {interval: TimeInterval, queueP: Promise}
157
+ * new interval to add to the collection, as well as a corresponding promise
158
+ * for when the most recently submitted webGPU queue is finished.
159
+ */
160
+ TimeWebGPU.prototype.push = function push(obj)
161
+ {
162
+ this.intervals.push(obj.interval);
163
+ this.latestWebGPUCall = obj.queueP;
164
+ }
165
+
166
+ /**
167
+ * Measure time (in ms) spent in the gpu. Timing is done ignoring overlaps -
168
+ * so if interval 1 was from 0-5 seconds and interval 2 was from 3-8 seconds,
169
+ * the returned duration will be 8.
170
+ *
171
+ * Furthermore, since it's impossible to know when webGPU code execution is finished until
172
+ * the GPU reports it's finished, awaiting the last `onSubmittedWorkDone` promise is required.
173
+ * A loop is used here in case the webGPU finishing triggers anything else on the microtask queue
174
+ *
175
+ * @returns - promise that will resolve with the total duration once all webGPU code has run to completion.
176
+ */
177
+ TimeWebGPU.prototype.duration = async function duration()
178
+ {
179
+ var totalTime = 0;
180
+ var previousEnd = 0;
181
+
182
+ while (this.latestWebGPUCall)
183
+ {
184
+ const latestCall = this.latestWebGPUCall;
185
+ await this.latestWebGPUCall;
186
+ if (latestCall === this.latestWebGPUCall)
187
+ this.latestWebGPUCall = null;
188
+ }
189
+
190
+ for (let interval of this.intervals)
191
+ {
192
+ if (previousEnd <= interval.start)
193
+ totalTime += interval.length
194
+ else
195
+ {
196
+ if (!interval.hasEnded())
197
+ throw new Error("Invalid length: interval hasn't been stopped");
198
+ totalTime += interval.end - previousEnd;
199
+ }
200
+ previousEnd = interval.end;
201
+ }
202
+ return totalTime;
203
+ }
204
+
205
+ /**
206
+ * Instantiate a timer for each type of execution we need to measure.
207
+ *
208
+ * cpu: measure total CPU usage of a slice. This measurement is based on
209
+ * the state of the event loop, and when any specific JavaScript code
210
+ * block is executing
211
+ * webGPU: measure the amount of time between `submitting` a command queue, and
212
+ * an onSubmittedWorkDone promise resolving to signal all work sent to the GPU
213
+ * is finished. These measurements are as close as we can get in JavaScript to
214
+ * the exact duration of the execution of code on the GPU using webGPU.
215
+ * webGL: measure the amount of time spent in a webGL function. Since webGL is synchronously
216
+ * executed, this time is indistinguishable from CPU time measured (unless all webGL
217
+ * functions also stop/start new CPU timing), so we have to subtract this measured
218
+ * time from CPU time to get the actual CPU time.
219
+ */
220
+ protectedStorage.timers = {
221
+ cpu: new TimeCPU(),
222
+ webGPU: new TimeWebGPU(),
223
+ webGL: new TimeThing(),
224
+ }
225
+
226
+ })