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.
- package/dist/dcp-client-bundle.js +1 -1
- package/generated/sandbox-definitions.json +1 -1
- package/libexec/sandbox/access-lists.js +30 -23
- package/libexec/sandbox/bravojs-env.js +52 -46
- package/libexec/sandbox/calculate-capabilities.js +3 -2
- package/libexec/sandbox/event-loop-virtualization.js +32 -64
- package/libexec/sandbox/timer-classes.js +226 -0
- package/libexec/sandbox/unique-timing.js +163 -0
- package/libexec/sandbox/webgpu-worker-environment.js +3 -1
- package/package.json +1 -1
- package/libexec/sandbox/gpu-timers.js +0 -84
|
@@ -1 +1 @@
|
|
|
1
|
-
{"browser":["deny-node","kvin/kvin.js","script-load-wrapper","wrap-event-listeners","event-loop-virtualization","
|
|
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 = {},
|
|
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,
|
|
767
|
+
applyAccessLists(g, allowList, blockList, polyfills);
|
|
779
768
|
}
|
|
780
769
|
|
|
781
|
-
if (typeof
|
|
782
|
-
|
|
770
|
+
if (typeof navigator === 'undefined')
|
|
771
|
+
{
|
|
772
|
+
navigator = {
|
|
783
773
|
userAgent: 'not a browser',
|
|
784
|
-
gpu: _GPU,
|
|
774
|
+
gpu: _GPU,
|
|
785
775
|
};
|
|
786
|
-
}
|
|
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
|
-
|
|
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
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
230
|
+
function reportResult (result)
|
|
229
231
|
{
|
|
230
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
47
|
+
serviceEvents.interval = new protectedStorage.TimeInterval();
|
|
48
|
+
cpuTimer.push(serviceEvents.interval);
|
|
40
49
|
|
|
41
50
|
sortEvents();
|
|
42
|
-
|
|
51
|
+
const event = events.shift();
|
|
52
|
+
if (event.eventType === 'timer')
|
|
43
53
|
{
|
|
44
|
-
|
|
45
|
-
if (event.
|
|
54
|
+
serviceEvents.executingTimeout = realSetTimeout(event.fn, 0, event.args);
|
|
55
|
+
if (event.recur)
|
|
46
56
|
{
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
69
|
+
serviceEvents.interval.stop();
|
|
70
|
+
|
|
71
|
+
if (!serviceEvents.sliceIsFinished && events.length)
|
|
64
72
|
{
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
241
|
-
|
|
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
|
+
})
|