dcp-client 4.2.7 → 4.2.10

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","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","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","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","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","webgpu-worker-environment.js","bootstrap"],"nodeTesting":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","wrap-event-listeners","event-loop-virtualization","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","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","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"]}
package/index.js CHANGED
@@ -32,10 +32,11 @@ const process = require('process');
32
32
  const kvin = require('kvin');
33
33
  const moduleSystem = require('module');
34
34
  const { spawnSync } = require('child_process');
35
- const { createContext, runInContext } = require('vm');
35
+ const vm = require('vm');
36
36
 
37
37
  exports.debug = false;
38
38
  let initInvoked = false; /* flag to help us detect use of Compute API before init */
39
+ let hadOriginalDcpConfig = globalThis.hasOwnProperty('dcpConfig'); /* flag to determine if the user set their own dcpConfig global variable before init */
39
40
 
40
41
  function debugging(what = 'dcp-client') {
41
42
  const debugSyms = []
@@ -95,6 +96,7 @@ const bundleSandbox = {
95
96
  setImmediate, clearImmediate,
96
97
  crypto: { getRandomValues: require('polyfill-crypto.getrandomvalues') },
97
98
  dcpConfig: {
99
+ build: 'bootstrap',
98
100
  bundleConfig: true,
99
101
  scheduler: {},
100
102
  bank: {
@@ -107,6 +109,30 @@ const bundleSandbox = {
107
109
  },
108
110
  };
109
111
 
112
+ function runSandboxedCode(sandbox, code, options)
113
+ {
114
+ if (process.env.DCP_CLIENT_LEGACY_CONTEXT_SANDBOXING)
115
+ {
116
+ const script = new vm.Script(code, options);
117
+ return script.runInNewContext(vm.createContext(sandbox), options);
118
+ }
119
+
120
+ if (typeof runSandboxedCode.extraGlobalProps === 'undefined')
121
+ runSandboxedCode.extraGlobalProps = {};
122
+
123
+ for (let prop in sandbox)
124
+ {
125
+ if (!globalThis.hasOwnProperty(prop) || runSandboxedCode.extraGlobalProps.hasOwnProperty(prop))
126
+ {
127
+ globalThis[prop] = sandbox[prop];
128
+ runSandboxedCode.extraGlobalProps[prop] = true; /* Memoize global prop list mutation to allow re-mutation */
129
+ }
130
+ }
131
+
132
+ const script = new vm.Script(code, options);
133
+ return script.runInThisContext(options);
134
+ }
135
+
110
136
  /** Evaluate a file in a sandbox without polluting the global object.
111
137
  * @param filename {string} The name of the file to evaluate, relative to
112
138
  * @param sandbox {object} A sandbox object, used for injecting 'global' symbols as needed
@@ -114,7 +140,6 @@ const bundleSandbox = {
114
140
  */
115
141
  function evalScriptInSandbox(filename, sandbox, olFlag) {
116
142
  var code
117
- var context = createContext(sandbox)
118
143
  try {
119
144
  code = fs.readFileSync(path.resolve(distDir, filename), 'utf-8')
120
145
  if (olFlag)
@@ -126,7 +151,8 @@ function evalScriptInSandbox(filename, sandbox, olFlag) {
126
151
  throw e
127
152
  }
128
153
 
129
- return runInContext(code, context, filename, 0);
154
+
155
+ return runSandboxedCode(sandbox, code, { filename, lineNumber: 0 });
130
156
  }
131
157
 
132
158
  /** Evaluate code in a secure sandbox; in this case, the code is the configuration
@@ -134,18 +160,12 @@ function evalScriptInSandbox(filename, sandbox, olFlag) {
134
160
  * during config file processing.
135
161
  *
136
162
  * @param code {string} The code to eval
137
- * @param context {object} An object that has been initialized as a context
138
- * that will act like the context's global object
163
+ * @param sandbox {object} A sandbox object, used for injecting 'global' symbols as needed
139
164
  * @param filename {string} The name of the file we're evaluating for stack-
140
165
  * trace purposes.
141
166
  */
142
- function evalStringInSandbox(
143
- code,
144
- sandbox,
145
- filename = '(dcp-client$$evalStringInSandbox)',
146
- ) {
147
- const context = createContext(sandbox);
148
-
167
+ function evalStringInSandbox(code, sandbox, filename = '(dcp-client$$evalStringInSandbox)')
168
+ {
149
169
  /**
150
170
  * Remove comments and then decide if this config file contains an IIFE. If
151
171
  * not, we need to wrap it as an IIFE to avoid "SyntaxError: Illegal return
@@ -167,7 +187,7 @@ function evalStringInSandbox(
167
187
  * printed.
168
188
  */
169
189
  try {
170
- result = runInContext(code, context, {
190
+ result = runSandboxedCode(sandbox, code, {
171
191
  filename,
172
192
  lineOffset,
173
193
  /**
@@ -301,7 +321,7 @@ function addConfig (existing, neo) {
301
321
  existing[prop] = new (existing[prop].constructor)(neo[prop]);
302
322
  continue;
303
323
  }
304
- if (typeof neo[prop] === 'object' && !Array.isArray(neo[prop]) && ['Function','Object'].includes(neo[prop].constructor.name)) {
324
+ if (typeof neo[prop] === 'object' && neo[prop] !== null && !Array.isArray(neo[prop]) && ['Function','Object'].includes(neo[prop].constructor.name)) {
305
325
  if (typeof existing[prop] === 'undefined') {
306
326
  existing[prop] = {}
307
327
  }
@@ -447,7 +467,11 @@ function addConfigFile(existing /*, file path components ... */) {
447
467
  * becomes the neo config.
448
468
  */
449
469
  async function addConfigRKey(existing, hive, keyTail) {
450
- var neo = await require('./windows-registry').getObject(hive, keyTail);
470
+ var neo;
471
+ // make sure RKey calls do not execute the windows registry calls on non-windows platforms
472
+ if (os.platform() !== 'win32')
473
+ return;
474
+ neo = await require('./windows-registry').getObject(hive, keyTail);
451
475
  debugging() && console.debug(` * Loading configuration from ${hive} ${keyTail}`);
452
476
  if (neo)
453
477
  addConfig(existing, neo);
@@ -466,11 +490,10 @@ function addConfigEnviron(existing, prefix) {
466
490
  continue
467
491
  }
468
492
  if (process.env[v][0] === '{') {
469
- // FIXME(bryan-hoang): fixCase is not defined.
470
493
  let prop = fixCase(v.slice(prefix.length))
471
494
  if (typeof neo[prop] !== 'object') {
472
495
  neo[prop] = {}
473
- addConfig(neo[prop], JSON.parse(process.env[v]))
496
+ addConfig(neo[prop], eval(`"use strict"; (${process.env[v]})`));
474
497
  } else {
475
498
  if (typeof neo[prop] === "object") {
476
499
  throw new Error("Cannot override configuration property " + prop + " with a string (is an object)")
@@ -483,6 +506,18 @@ function addConfigEnviron(existing, prefix) {
483
506
  addConfig(existing, neo);
484
507
  }
485
508
 
509
+ /** Turn UGLY_STRING into uglyString */
510
+ function fixCase(ugly)
511
+ {
512
+ var fixed = ugly.toLowerCase();
513
+ var idx;
514
+
515
+ while ((idx = fixed.indexOf('_')) !== -1)
516
+ fixed = fixed.slice(0, idx) + fixed[idx + 1].toUpperCase() + fixed.slice(idx + 2);
517
+
518
+ return fixed;
519
+ }
520
+
486
521
  /**
487
522
  * Tasks which are run in the early phases of initialization
488
523
  * - plumb in global.XMLHttpRequest which lives forever -- that way KeepAlive etc works.
@@ -575,7 +610,7 @@ function initTail(aggrConfig, finalBundleCode, finalBundleURL) {
575
610
  require('dcp/dcp-url').patchup(aggrConfig);
576
611
 
577
612
  /* 3 */
578
- if (global.dcpConfig) {
613
+ if (hadOriginalDcpConfig) {
579
614
  /* dcpConfig was defined before dcp-client was initialized: assume dev knows what he/she is doing */
580
615
  debugging() && console.debug('Dropping bundle dcp-config in favour of global dcpConfig')
581
616
  Object.assign(require('dcp/dcp-config'), global.dcpConfig);
@@ -587,6 +622,8 @@ function initTail(aggrConfig, finalBundleCode, finalBundleURL) {
587
622
  bundleSandbox.dcpConfig = defaultConfig;
588
623
  }
589
624
 
625
+ bundleSandbox.dcpConfig.build = require('dcp/build').config.build;
626
+
590
627
  Object.defineProperty(exports, 'distDir', {
591
628
  value: function dcpClient$$distDir$getter() {
592
629
  return distDir;
@@ -638,6 +675,7 @@ function initTail(aggrConfig, finalBundleCode, finalBundleURL) {
638
675
  ret = makeInitReturnObject();
639
676
  if (bundle.postInitTailHook) /* for use by auto-update future backwards compat */
640
677
  ret = bundle.postInitTailHook(ret, aggrConfig, bundle, finalBundleLabel, bundleSandbox, injectModule);
678
+ dcpConfig.build = bundleSandbox.dcpConfig.build = require('dcp/build').config.build;
641
679
  return ret;
642
680
  }
643
681
 
@@ -759,13 +797,13 @@ exports.createAggregateConfig = async function dcpClient$$createAggregateConfig(
759
797
  let config = localConfig;
760
798
 
761
799
  /* This follows spec doc line-by-line */
762
- await addConfigRKey(config, 'HKLM', 'dcp-client/dcp-config');
763
- await addConfigFile(config, etc, 'dcp-client/dcp-config.js');
800
+ await addConfigRKey(config, 'HKLM', 'dcp-client/dcp-config');
801
+ addConfigFile(config, etc, 'dcp/dcp-client/dcp-config.js');
764
802
  programName && await addConfigRKey(config, 'HKLM', `dcp-client/${programName}/dcp-config`);
765
- programName && await addConfigFile(config, etc, `dcp-client/${programName}/dcp-config.js`);
766
- await addConfigFile(config, home, '.dcp/dcp-client/dcp-config.js');
767
- programName && await addConfigFile(config, home, `.dcp/dcp-client/${programName}/dcp-config.js`);
768
- await addConfigRKey(config, 'HKCU', `dcp-client/dcp-config`);
803
+ programName && addConfigFile(config, etc, `dcp/dcp-client/${programName}/dcp-config.js`);
804
+ addConfigFile(config, home, '.dcp/dcp-client/dcp-config.js');
805
+ programName && addConfigFile(config, home, `.dcp/dcp-client/${programName}/dcp-config.js`);
806
+ await addConfigRKey(config, 'HKCU', 'dcp-client/dcp-config');
769
807
  programName && await addConfigRKey(config, 'HKCU', `dcp-client/${programName}/dcp-config`);
770
808
 
771
809
  // Sort out polymorphic arguments: 'passed-in configuration'.
@@ -787,9 +825,8 @@ exports.createAggregateConfig = async function dcpClient$$createAggregateConfig(
787
825
  if (initArgv[2])
788
826
  addConfig(localConfig.bundle, { location: new URL(initArgv[2])});
789
827
 
790
- await addConfigEnviron(localConfig, 'DCP_CONFIG_');
791
- await addConfigFile(localConfig, etc, `override/dcp-config.js`);
792
- await addConfigRKey(localConfig, 'HKLM', 'override/dcp-config');
828
+ addConfigEnviron(localConfig, 'DCP_CONFIG_');
829
+ addConfigFile(localConfig, etc, 'dcp/override/dcp-config.js');
793
830
  addConfig(aggrConfig, localConfig);
794
831
 
795
832
  /**
@@ -66,7 +66,7 @@ exports.Evaluator = function Evaluator(inputStream, outputStream, files) {
66
66
  /* Add properties to sandbox global scope */
67
67
  Object.assign(this.sandboxGlobal, {
68
68
  self: this.sandboxGlobal,
69
- die: (exitCode) => { this.destroy(); process.exit(exitCode); },
69
+ die: () => { this.destroy(); },
70
70
  writeln: (string) => { this.writeln(string) },
71
71
  onreadln: (handler) => { this.onreadlnHandler = handler },
72
72
  setTimeout,
@@ -366,7 +366,7 @@ function main() {
366
366
  setupErrorHandlers();
367
367
 
368
368
  const argv = require('yargs')
369
- .usage('Node Evaluator - Copyright (c) 2020-2021 Kings Distributed Systems, Ltd. All Rights Reserved.'
369
+ .usage('Node Evaluator - Copyright (c) 2020-2022 Distributive, Ltd. All Rights Reserved.'
370
370
  + 'Usage: dcp-evaluator [options] [<file.js> <file.js> ...]')
371
371
  .options({
372
372
  port: {
@@ -23,6 +23,8 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
23
23
  'AsyncFunction',
24
24
  'Atomics',
25
25
  'BigInt',
26
+ 'BigInt64Array',
27
+ 'BigUint64Array',
26
28
  'Boolean',
27
29
  'Blob',
28
30
  'bravojs',
@@ -45,7 +47,6 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
45
47
  'Float32Array',
46
48
  'Float64Array',
47
49
  'Function',
48
- 'getWebGLTimer',
49
50
  'Headers',
50
51
  'Infinity',
51
52
  'Int16Array',
@@ -108,7 +109,6 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
108
109
  'WeakSet',
109
110
  'WebAssembly',
110
111
  'WebGL2RenderingContext',
111
- 'webGLOffset',
112
112
  'WebGLTexture',
113
113
  'WorkerGlobalScope',
114
114
  // Our own Webgpu symbols
@@ -123,18 +123,6 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
123
123
  'flushLastLog'
124
124
  ]);
125
125
 
126
- // webGL timer getter to be overwritten in calculate-capabilities
127
- self.getWebGLTimer = (function monkeypatchWebGL() {
128
- let timer = 0;
129
- function getTimer() {
130
- return timer;
131
- }
132
- return getTimer;
133
- })();
134
-
135
- // Offset time for webGL when sandbox is re-used
136
- self.webGLOffset = 0;
137
-
138
126
  // Origin time for performance polyfill
139
127
  const pt0 = new Date().getTime();
140
128
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file worker/evaluator-lib/bootstrap.js
3
- * Copyright (c) 2018, Kings Distributed Systems, Ltd. All Rights Reserved.
3
+ * Copyright (c) 2018-2022, Distributive, Ltd. All Rights Reserved.
4
4
  *
5
5
  * Final evaluator bootstrap code for defining functions to be used in the work function.
6
6
  *
@@ -131,15 +131,16 @@ self.wrapScriptLoading({ scriptName: 'bootstrap', finalScript: true }, function
131
131
  return true;
132
132
  }
133
133
 
134
- function workerBootstrap$work$emit(eventName, value) {
135
- if (typeof eventName !== 'string') {
136
- throw new Error(`Event name passed to work.emit must be a string, not ${eventName}.`);
134
+ function workerBootstrap$work$emit(customEvent, value) {
135
+ if (typeof customEvent !== 'string') {
136
+ throw new Error(`Event name passed to work.emit must be a string, not ${customEvent}.`);
137
137
  }
138
138
 
139
139
  postMessage({
140
140
  request: 'emitEvent',
141
141
  payload: {
142
- eventName,
142
+ eventName: 'custom',
143
+ customEvent,
143
144
  data: value,
144
145
  },
145
146
  });
@@ -2,7 +2,7 @@
2
2
  * This file extends BravoJS, creating a CommonJS Modules/2.0
3
3
  * environment for WebWorkers and similar environments.
4
4
  *
5
- * Copyright (c) 2018, Kings Distributed Systems, Ltd. All Rights Reserved.
5
+ * Copyright (c) 2018-2022, Distributive, Ltd. All Rights Reserved.
6
6
  * Wes Garland, wes@kingsds.network
7
7
  */
8
8
 
@@ -184,12 +184,9 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
184
184
  function reportRejectedGPUandTotal (t0) {
185
185
  try
186
186
  {
187
- const webGLTimer = getWebGLTimer;
188
- const offset = webGLOffset;
189
187
  const total = performance.now() - t0;
190
-
191
- let webGL = webGLTimer() - offset;
192
- self.webGLOffset = offset + webGL;
188
+ const webGL = protectedStorage.getAndResetWebGLTimer();
189
+ protectedStorage.subtractWebGLTimeFromCPUTime(webGL);
193
190
  ring3PostMessage({ request: 'measurement', total, webGL });
194
191
  }
195
192
  catch (error)
@@ -232,12 +229,9 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
232
229
  {
233
230
  try
234
231
  {
235
- const webGLTimer = getWebGLTimer;
236
- const offset = webGLOffset;
237
- const total = performance.now() - t0;
238
-
239
- let webGL = webGLTimer() - offset;
240
- self.webGLOffset = offset + webGL;
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 */
241
235
  ring3PostMessage({ request: 'measurement', total, webGL });
242
236
  ring3PostMessage({ request: 'complete', result });
243
237
  }
@@ -277,6 +271,7 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
277
271
  */
278
272
  try { await tryFlushMicroTaskQueue(); } catch(e) {};
279
273
  try { flushLastLog(); } catch(e) {};
274
+ try { protectedStorage.markCPUTimeAsDone(); } catch(e) {};
280
275
 
281
276
  if (rejection)
282
277
  errorCallback(rejection);
@@ -301,6 +296,6 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
301
296
  * 1. shorten stack
302
297
  * 2. initialize the event loop measurement code
303
298
  */
304
- setTimeout(() => runWorkFunction_inner(datum, (result) => reportResult(t0, result), (rejection) => reportError(t0, rejection)));
299
+ protectedStorage.setTimeout(() => runWorkFunction_inner(datum, (result) => reportResult(t0, result), (rejection) => reportError(t0, rejection)));
305
300
  }
306
301
  }); /* end of fn */
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @file worker/evaluator-lib/bravojs-init.js
3
- * Copyright (c) 2018, Kings Distributed Systems, Ltd. All Rights Reserved.
3
+ * Copyright (c) 2020-2022, Distributive, Ltd. All Rights Reserved.
4
4
  *
5
- * This file sets up the environment for bravojs to load properly.
5
+ * This file sets up the environment for BravoJS to load properly.
6
6
  *
7
7
  * @author Ryan Rossiter, ryan@kingsds.network
8
8
  * @date Sept 2020
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file worker/evaluator-lib/calculate-capabilities.js
3
- * Copyright (c) 2018, Kings Distributed Systems, Ltd. All Rights Reserved.
3
+ * Copyright (c) 2020-2022, Distributive. All Rights Reserved.
4
4
  *
5
5
  * This file provides a message handler for handling capabilities requests.
6
6
  *
@@ -91,47 +91,12 @@ self.wrapScriptLoading({ scriptName: 'calculate-capabilities' }, function calcul
91
91
 
92
92
  // Destroy the WebGL context, from https://www.khronos.org/registry/webgl/extensions/WEBGL_lose_context/
93
93
  // "This is the recommended mechanism for applications to programmatically halt their use of the WebGL API."
94
- gl.getExtension('WEBGL_lose_context').loseContext();
94
+ // gl.getExtension('WEBGL_lose_context').loseContext();
95
95
 
96
96
  bigTexture4096 = textureSize >= 4096;
97
97
  bigTexture8192 = textureSize >= 8192;
98
98
  bigTexture16384 = textureSize >= 16384;
99
99
  bigTexture32768 = textureSize >= 32768;
100
-
101
- // Monkeypatch webGL *after* capability check to verify getContext exists AND not charge every job for GPU checking
102
- getWebGLTimer = (function monkeypatchWebGL() {
103
- let timer = 0;
104
- function getTimer() {
105
- return timer;
106
- }
107
- let oldGetContext = OffscreenCanvas.prototype.getContext;
108
- OffscreenCanvas.prototype.getContext = function(type, options){
109
- let context = oldGetContext(type, options);
110
- for (let key of Object.getOwnPropertyNames(context.__proto__)){
111
- if (typeof context[key] === 'function'){
112
- let func = context[key].bind(context);
113
- context[key] = (...args) => {
114
- let startTimer = performance.now();
115
- // Try using .then (assuming async) and falling back to synchronous
116
- try{
117
- ret = func(...args).then((ret)=>{
118
- let timeRes = performance.now() - startTimer;
119
- timer += timeRes;
120
- return ret;
121
- });
122
- }catch(err){
123
- ret = func(...args);
124
- let timeRes = performance.now() - startTimer;
125
- timer += timeRes;
126
- }
127
- return ret;
128
- };
129
- }
130
- };
131
- return context;
132
- };
133
- return getTimer;
134
- })();
135
100
  }catch(err){
136
101
  //it is possible on some machines, that offscreenCanvas is available but canvas.getContext('webgl' or 'webgl2') results in null
137
102
  //Any error in this using an extensions should likely result in specifications for that capability being set to false.
@@ -20,6 +20,7 @@ self.wrapScriptLoading({ scriptName: 'event-loop-virtualization' }, function eve
20
20
  {
21
21
  (function privateScope(realSetTimeout, realSetInterval, realSetImmediate, realClearTimeout, realClearInterval, realClearImmediate) {
22
22
  let totalCPUTime = 0;
23
+ let startTime;
23
24
  const events = [];
24
25
  events.serial = 0;
25
26
 
@@ -32,8 +33,9 @@ self.wrapScriptLoading({ scriptName: 'event-loop-virtualization' }, function eve
32
33
  serviceEvents.timeout = null;
33
34
  serviceEvents.nextTimeout = null;
34
35
  serviceEvents.servicing = true;
36
+ serviceEvents.sliceIsFinished = false;
35
37
 
36
- const startTime = performance.now();
38
+ startTime = performance.now();
37
39
  let now = Date.now();
38
40
 
39
41
  sortEvents();
@@ -58,17 +60,31 @@ self.wrapScriptLoading({ scriptName: 'event-loop-virtualization' }, function eve
58
60
  function endOfRealEventCycle()
59
61
  {
60
62
  serviceEvents.servicing = false;
61
- const endTime = performance.now();
62
- totalCPUTime += endTime - startTime;
63
-
64
- // Set timeout to rerun this function if there are events remaining that just can't be used yet
65
- if (events.length > 0)
63
+ if (!serviceEvents.sliceIsFinished)
66
64
  {
67
- serviceEvents.nextTimeout = events[0].when
68
- serviceEvents.timeout = realSetTimeout(serviceEvents, events[0].when - Date.now());
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
+ }
69
74
  }
70
75
  }
71
76
  }
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
+ }
72
88
 
73
89
  /** Execute callback after at least timeout ms.
74
90
  *
@@ -124,6 +140,9 @@ self.wrapScriptLoading({ scriptName: 'event-loop-virtualization' }, function eve
124
140
  return timer;
125
141
  }
126
142
 
143
+ /** Ensure our trampoline setTimeout in bravojs-env will have the proper setTimeout, don't allow clients to see or overwrite to prevent measuring time */
144
+ protectedStorage.setTimeout = setTimeout;
145
+
127
146
  /** Remove a timeout from the list of pending timeouts, regardless of its current
128
147
  * status.
129
148
  *
@@ -215,6 +234,7 @@ self.wrapScriptLoading({ scriptName: 'event-loop-virtualization' }, function eve
215
234
  serviceEvents.timeout = null;
216
235
  serviceEvents.nextTimeout = null;
217
236
  serviceEvents.servicing = false;
237
+ serviceEvents.sliceIsFinished = false;
218
238
  }
219
239
 
220
240
  addEventListener('message', async (event) => {
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @file gpu-timers.js
3
+ * Copyright (c) 2022, Distributive, Ltd.
4
+ * All Rights Reserved. Licensed under the terms of the MIT License.
5
+ *
6
+ * This file adds wrappers for webGL and webGPU functions so we can measure their GPU time
7
+ *
8
+ * @author Ryan Saweczko, ryansaweczko@kingsds.network
9
+ * @date July 2022
10
+ */
11
+
12
+ /* global WebGPUWindow GPU */
13
+ // @ts-nocheck
14
+
15
+ self.wrapScriptLoading({ scriptName: 'gpu-timers' }, function gpuTimers$fn(protectedStorage, ring2PostMessage)
16
+ {
17
+ /* Default if we don't have webGL */
18
+ protectedStorage.getAndResetWebGLTimer = function defaultTimer()
19
+ {
20
+ return 0;
21
+ }
22
+
23
+ if (self.OffscreenCanvas && new OffscreenCanvas(1,1))
24
+ {
25
+ let time = 0;
26
+ function getAndResetWebGLTimer()
27
+ {
28
+ const tmp = time;
29
+ time = 0;
30
+ return tmp;
31
+ }
32
+ protectedStorage.getAndResetWebGLTimer = getAndResetWebGLTimer;
33
+
34
+ /* Factory to wrap a function from a context with a timer */
35
+ function timeWebGLFactory(context, prop)
36
+ {
37
+ let originalFn = context[prop].bind(context);
38
+
39
+ context[prop] = function wrappedWebGLFunction(...args)
40
+ {
41
+ var returnValue;
42
+ const start = performance.now();
43
+ try
44
+ {
45
+ returnValue = originalFn(...args);
46
+ time += performance.now() - start;
47
+ }
48
+ catch(e)
49
+ {
50
+ time += performance.now() - start;
51
+ throw e;
52
+ }
53
+ return returnValue;
54
+ }
55
+ }
56
+
57
+ /* Update all functions on the OffscreenCanvas getContext prototype to have timers */
58
+ let oldGetContext = OffscreenCanvas.prototype.getContext;
59
+ OffscreenCanvas.prototype.getContext = function(type, options)
60
+ {
61
+ let context = oldGetContext.bind(this)(type, options);
62
+ for (let key of Object.getOwnPropertyNames(context.__proto__))
63
+ if (typeof context[key] === 'function')
64
+ timeWebGLFactory(context, key);
65
+ return context;
66
+ };
67
+ }
68
+
69
+ });
@@ -51,27 +51,29 @@ self.wrapScriptLoading({ scriptName: 'native-event-loop' }, function nativeEvent
51
51
  function fireTimerCallbacks()
52
52
  {
53
53
  sortTimers();
54
- let timer;
55
- while ((timer = timers.shift()))
54
+ let timer = timers.shift();
55
+ let now = Date.now();
56
+ if (!timer)
57
+ throw new Error('Logic error: trying to run timer when no timer exists') /* should be impossible */
58
+ if (timer.when > now) /* should be impossible, but at least we can handle this */
56
59
  {
57
- let now = Date.now();
58
- if (timer.when > now)
59
- {
60
- timers.push(timer);
61
- break;
62
- }
63
- timer.fn.apply(null, timer.args);
60
+ timers.unshift(timer);
61
+ nextTimer(timers[0].when);
62
+ return;
63
+ }
64
+ timer.fn.apply(null, timer.args);
64
65
 
65
- if (timer.recur)
66
- {
67
- timer.when = Date.now() + timer.recur;
68
- timers.push(timer);
69
- }
70
- sortTimers();
66
+ if (timer.recur)
67
+ {
68
+ timer.when = Date.now() + timer.recur;
69
+ timers.push(timer);
71
70
  }
71
+
72
+ sortTimers();
72
73
  if (timers.length)
73
74
  nextTimer(timers[0].when);
74
75
  }
76
+
75
77
  ontimer(fireTimerCallbacks);
76
78
 
77
79
  /** Execute callback after at least timeout ms.
@@ -135,6 +135,7 @@ try {
135
135
  serialize = newSerializer.serialize
136
136
  deserialize = newSerializer.deserialize
137
137
  outMsg = { type: 'nop', success: true }
138
+ send(outMsg);
138
139
  break
139
140
  case 'workerMessage':
140
141
  // if (inMsg.message.request === 'main') {
@@ -145,6 +146,7 @@ try {
145
146
  // }
146
147
  emitEvent('message', {data: inMsg.message})
147
148
  outMsg.success = true
149
+ send(outMsg)
148
150
  break
149
151
  case 'die':
150
152
  writeln('DIE: ' + Date())
@@ -157,7 +159,6 @@ try {
157
159
  outMsg.success = false
158
160
  outMsg.exception = { name: e.name, message: e.message, fileName: e.fileName, lineNumber: e.lineNumber, stack: e.stack }
159
161
  outMsg.e = e
160
- } finally {
161
162
  send(outMsg)
162
163
  }
163
164
  }) /* receiveLine */