@sdeverywhere/runtime-async 0.2.0 → 0.2.2

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/README.md CHANGED
@@ -1,12 +1,32 @@
1
1
  # @sdeverywhere/runtime-async
2
2
 
3
- This package provides an implementation of the `ModelRunner` interface from
4
- the `@sdeverywhere/runtime` package that uses a Web Worker (in the browser)
5
- or a worker thread (in a Node.js app) to run the model asynchronously off
6
- the main JavaScript thread.
3
+ This package provides an implementation of the `ModelRunner` interface (from the `@sdeverywhere/runtime` package) that uses a Web Worker (in the browser) or a worker thread (in a Node.js app) to run the model asynchronously off the main JavaScript thread.
4
+
5
+ ## Quick Start
6
+
7
+ The best way to get started with SDEverywhere is to follow the [Quick Start](https://github.com/climateinteractive/SDEverywhere#quick-start) instructions.
8
+ If you follow those instructions, the `@sdeverywhere/runtime-async` package will be added to your project automatically, in which case you can skip the next section and jump straight to the ["Usage"](#usage) section below.
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ # npm
14
+ npm install @sdeverywhere/runtime-async
15
+
16
+ # pnpm
17
+ pnpm add @sdeverywhere/runtime-async
18
+
19
+ # yarn
20
+ yarn add @sdeverywhere/runtime-async
21
+ ```
7
22
 
8
23
  ## Usage
9
24
 
25
+ _NOTE:_ If you followed the "Quick Start" instructions and/or used the
26
+ `@sdeverywhere/create` package to generate your project, the initialization
27
+ steps listed below are already implemented for you in the generated `core` package,
28
+ and you can work directly with a `ModelRunner` and/or `ModelScheduler` instance.
29
+
10
30
  ### 1. Initialize the `WasmModel` in a Web Worker
11
31
 
12
32
  In your app project, define a separate JavaScript file, called
package/dist/index.cjs CHANGED
@@ -38,7 +38,18 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
38
38
  const modelWorker2 = await (0, import_threads.spawn)(worker);
39
39
  const initResult = await modelWorker2.initModel();
40
40
  let ioBuffer = initResult.ioBuffer;
41
- const rowLength = initResult.rowLength;
41
+ const runTimeOffsetInBytes = 0;
42
+ const runTimeLengthInElements = 1;
43
+ const runTimeLengthInBytes = runTimeLengthInElements * 8;
44
+ const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
45
+ const inputsLengthInElements = initResult.inputsLength;
46
+ const inputsLengthInBytes = inputsLengthInElements * 8;
47
+ const outputsOffsetInBytes = inputsOffsetInBytes + inputsLengthInBytes;
48
+ const outputsLengthInElements = initResult.outputsLength;
49
+ const outputsLengthInBytes = outputsLengthInElements * 8;
50
+ const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes;
51
+ const indicesLengthInElements = initResult.outputIndicesLength;
52
+ const outputRowLength = initResult.outputRowLength;
42
53
  let running = false;
43
54
  let terminated = false;
44
55
  return {
@@ -53,24 +64,24 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
53
64
  } else {
54
65
  running = true;
55
66
  }
56
- const runTimeLengthInElements = 1;
57
- const runTimeLengthInBytes = runTimeLengthInElements * 8;
58
- const inputsLengthInElements = inputs.length;
59
- const inputsLengthInBytes = inputsLengthInElements * 8;
60
- const inputsArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements);
67
+ const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
61
68
  for (let i = 0; i < inputs.length; i++) {
62
69
  inputsArray[i] = inputs[i].get();
63
70
  }
71
+ if (indicesLengthInElements > 0) {
72
+ const outputSpecs = outputs.varSpecs || [];
73
+ const indicesArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements);
74
+ (0, import_runtime.updateOutputIndices)(indicesArray, outputSpecs);
75
+ }
64
76
  try {
65
77
  ioBuffer = await modelWorker2.runModel((0, import_threads.Transfer)(ioBuffer));
66
78
  } finally {
67
79
  running = false;
68
80
  }
69
- const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements);
70
- outputs.runTimeInMillis = runTimeBufferArray[0];
71
- const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
72
- const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes);
73
- outputs.updateFromBuffer(outputsArray, rowLength);
81
+ const runTimeArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
82
+ outputs.runTimeInMillis = runTimeArray[0];
83
+ const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
84
+ outputs.updateFromBuffer(outputsArray, outputRowLength);
74
85
  return outputs;
75
86
  },
76
87
  terminate: () => {
@@ -91,6 +102,7 @@ var initWasmModel;
91
102
  var wasmModel;
92
103
  var inputsWasmBuffer;
93
104
  var outputsWasmBuffer;
105
+ var outputIndicesWasmBuffer;
94
106
  var modelWorker = {
95
107
  async initModel() {
96
108
  if (wasmModel) {
@@ -100,17 +112,23 @@ var modelWorker = {
100
112
  wasmModel = wasmResult.model;
101
113
  inputsWasmBuffer = wasmResult.inputsBuffer;
102
114
  outputsWasmBuffer = wasmResult.outputsBuffer;
115
+ outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer;
103
116
  const runTimeLength = 8;
104
117
  const inputsLength = inputsWasmBuffer.getArrayView().length;
105
118
  const outputsLength = outputsWasmBuffer.getArrayView().length;
106
- const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength);
119
+ const outputIndicesLength = (outputIndicesWasmBuffer == null ? void 0 : outputIndicesWasmBuffer.getArrayView().length) || 0;
120
+ const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength;
121
+ const ioArray = new Float64Array(totalLength);
107
122
  const ioBuffer = ioArray.buffer;
108
123
  const initResult = {
109
124
  outputVarIds: wasmResult.outputVarIds,
110
125
  startTime: wasmModel.startTime,
111
126
  endTime: wasmModel.endTime,
112
127
  saveFreq: wasmModel.saveFreq,
113
- rowLength: wasmModel.numSavePoints,
128
+ inputsLength,
129
+ outputsLength,
130
+ outputIndicesLength,
131
+ outputRowLength: wasmModel.numSavePoints,
114
132
  ioBuffer
115
133
  };
116
134
  return (0, import_worker.Transfer)(initResult, [ioBuffer]);
@@ -119,21 +137,37 @@ var modelWorker = {
119
137
  if (!wasmModel) {
120
138
  throw new Error("WasmModel must be initialized before running the model in worker");
121
139
  }
140
+ const runTimeOffsetInBytes = 0;
122
141
  const runTimeLengthInElements = 1;
123
142
  const runTimeLengthInBytes = runTimeLengthInElements * 8;
124
143
  const inputsWasmArray = inputsWasmBuffer.getArrayView();
144
+ const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
125
145
  const inputsLengthInElements = inputsWasmArray.length;
126
- const inputsLengthInBytes = inputsLengthInElements * 8;
127
- const inputsBufferArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements);
146
+ const inputsLengthInBytes = inputsWasmArray.byteLength;
147
+ const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
128
148
  inputsWasmArray.set(inputsBufferArray);
149
+ const outputsWasmArray = outputsWasmBuffer.getArrayView();
150
+ const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
151
+ const outputsLengthInBytes = outputsWasmArray.byteLength;
152
+ let useIndices = false;
153
+ if (outputIndicesWasmBuffer) {
154
+ const indicesWasmArray = outputIndicesWasmBuffer.getArrayView();
155
+ const indicesLengthInElements = indicesWasmArray.length;
156
+ const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes;
157
+ const indicesBufferArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements);
158
+ if (indicesBufferArray[0] !== 0) {
159
+ indicesWasmArray.set(indicesBufferArray);
160
+ useIndices = true;
161
+ }
162
+ }
129
163
  const t0 = (0, import_runtime2.perfNow)();
130
- wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer);
164
+ wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : void 0);
131
165
  const elapsed = (0, import_runtime2.perfElapsed)(t0);
132
- const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements);
166
+ const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
133
167
  runTimeBufferArray[0] = elapsed;
134
- const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
135
- const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes);
136
- outputsBufferArray.set(outputsWasmBuffer.getArrayView());
168
+ const outputsLengthInElements = outputsWasmArray.length;
169
+ const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
170
+ outputsBufferArray.set(outputsWasmArray);
137
171
  return (0, import_worker.Transfer)(ioBuffer);
138
172
  }
139
173
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/runner.ts","../src/worker.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nexport { spawnAsyncModelRunner } from './runner'\n\nexport { exposeModelWorker } from './worker'\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport { BlobWorker, spawn, Thread, Transfer, Worker } from 'threads'\n\nimport type { ModelRunner } from '@sdeverywhere/runtime'\nimport { Outputs } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.\n *\n * In your app project, define a JavaScript file, called `worker.js` for example, that\n * initializes the model worker in the context of the Web Worker:\n *\n * ```js\n * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n *\n * async function initWasmModel() {\n * const wasmModules = loadWasm()\n * return initWasmModelAndBuffers(...)\n * }\n *\n * exposeModelWorker(initWasmModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the Web Worker and initialize the `ModelRunner` that communicates\n * with the worker:\n *\n * ```js\n * import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async/runner'\n *\n * async function initApp() {\n * // ...\n * const runner = await spawnAsyncModelRunner({ path: './worker.js' })\n * // ...\n * }\n * ```\n *\n * @param workerSpec Either a `path` to the worker JavaScript file, or the `source`\n * containing the full JavaScript source of the worker.\n */\nexport async function spawnAsyncModelRunner(workerSpec: { path: string } | { source: string }): Promise<ModelRunner> {\n if (workerSpec['path']) {\n return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec['path']))\n } else {\n return spawnAsyncModelRunnerWithWorker(BlobWorker.fromText(workerSpec['source']))\n }\n}\n\n/**\n * @hidden For internal use only\n */\nasync function spawnAsyncModelRunnerWithWorker(worker: Worker): Promise<ModelRunner> {\n // Spawn the given Worker that contains the `ModelWorker`\n const modelWorker = await spawn(worker)\n\n // Wait for the worker to initialize the wasm model (in the worker thread)\n const initResult = await modelWorker.initModel()\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\n\n // The row length is the number of elements in each row of the outputs buffer\n const rowLength = initResult.rowLength\n\n // Use a flag to ensure that only one request is made at a time\n let running = false\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n return {\n createOutputs: () => {\n return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq)\n },\n\n runModel: async (inputs, outputs) => {\n if (terminated) {\n throw new Error('Async model runner has already been terminated')\n } else if (running) {\n throw new Error('Async model runner only supports one `runModel` call at a time')\n } else {\n running = true\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Capture the current set of input values into the reusable buffer\n const inputsLengthInElements = inputs.length\n const inputsLengthInBytes = inputsLengthInElements * 8\n const inputsArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements)\n for (let i = 0; i < inputs.length; i++) {\n inputsArray[i] = inputs[i].get()\n }\n\n // Run the model in the worker. We pass the underlying `ArrayBuffer`\n // instance back to the worker wrapped in a `Transfer` to make it\n // no-copy transferable, and then the worker will return it back\n // to us.\n try {\n ioBuffer = await modelWorker.runModel(Transfer(ioBuffer))\n } finally {\n running = false\n }\n\n // Save the model run time\n const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements)\n outputs.runTimeInMillis = runTimeBufferArray[0]\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes)\n outputs.updateFromBuffer(outputsArray, rowLength)\n\n return outputs\n },\n\n terminate: () => {\n if (terminated) {\n return Promise.resolve()\n } else {\n terminated = true\n return Thread.terminate(modelWorker)\n }\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { TransferDescriptor } from 'threads'\nimport { expose, Transfer } from 'threads/worker'\n\nimport type { WasmBuffer, WasmModel, WasmModelInitResult } from '@sdeverywhere/runtime'\nimport { perfElapsed, perfNow } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initWasmModel: () => Promise<WasmModelInitResult>\n/** @hidden */\nlet wasmModel: WasmModel\n/** @hidden */\nlet inputsWasmBuffer: WasmBuffer\n/** @hidden */\nlet outputsWasmBuffer: WasmBuffer\n\ninterface InitResult {\n outputVarIds: string[]\n startTime: number\n endTime: number\n saveFreq: number\n rowLength: number\n ioBuffer: ArrayBuffer\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<TransferDescriptor<InitResult>> {\n if (wasmModel) {\n throw new Error('WasmModel was already initialized')\n }\n\n // Initialize the wasm model and associated buffers\n const wasmResult = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = wasmResult.model\n inputsWasmBuffer = wasmResult.inputsBuffer\n outputsWasmBuffer = wasmResult.outputsBuffer\n\n // Create a combined array that will hold a copy of the inputs and outputs\n // wasm buffers; this buffer is no-copy transferable, whereas the wasm ones\n // are not allowed to be transferred\n const runTimeLength = 8\n const inputsLength = inputsWasmBuffer.getArrayView().length\n const outputsLength = outputsWasmBuffer.getArrayView().length\n const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength)\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n const initResult: InitResult = {\n outputVarIds: wasmResult.outputVarIds,\n startTime: wasmModel.startTime,\n endTime: wasmModel.endTime,\n saveFreq: wasmModel.saveFreq,\n rowLength: wasmModel.numSavePoints,\n ioBuffer\n }\n return Transfer(initResult, [ioBuffer])\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!wasmModel) {\n throw new Error('WasmModel must be initialized before running the model in worker')\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Copy the inputs into the wasm inputs buffer\n const inputsWasmArray = inputsWasmBuffer.getArrayView()\n const inputsLengthInElements = inputsWasmArray.length\n const inputsLengthInBytes = inputsLengthInElements * 8\n const inputsBufferArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements)\n inputsWasmArray.set(inputsBufferArray)\n\n // Run the model using the wasm buffers\n const t0 = perfNow()\n wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer)\n const elapsed = perfElapsed(t0)\n\n // Write the model run time to the buffer\n const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements)\n runTimeBufferArray[0] = elapsed\n\n // Copy the outputs from the wasm outputs buffer\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes)\n outputsBufferArray.set(outputsWasmBuffer.getArrayView())\n\n // Transfer the buffer back to the runner\n return Transfer(ioBuffer)\n }\n}\n\n/**\n * Expose an object in the current worker thread that communicates with the\n * `ModelRunner` instance running in the main thread. The exposed worker\n * object will take care of running the `WasmModel` on the worker thread\n * and sending the outputs back to the main process.\n *\n * @param init The function that initializes the `WasmModel` instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initWasmModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":"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","names":["modelWorker","import_runtime"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/runner.ts","../src/worker.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nexport { spawnAsyncModelRunner } from './runner'\n\nexport { exposeModelWorker } from './worker'\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport { BlobWorker, spawn, Thread, Transfer, Worker } from 'threads'\n\nimport type { ModelRunner } from '@sdeverywhere/runtime'\nimport { Outputs, updateOutputIndices } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.\n *\n * In your app project, define a JavaScript file, called `worker.js` for example, that\n * initializes the model worker in the context of the Web Worker:\n *\n * ```js\n * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n *\n * async function initWasmModel() {\n * const wasmModules = loadWasm()\n * return initWasmModelAndBuffers(...)\n * }\n *\n * exposeModelWorker(initWasmModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the Web Worker and initialize the `ModelRunner` that communicates\n * with the worker:\n *\n * ```js\n * import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async/runner'\n *\n * async function initApp() {\n * // ...\n * const runner = await spawnAsyncModelRunner({ path: './worker.js' })\n * // ...\n * }\n * ```\n *\n * @param workerSpec Either a `path` to the worker JavaScript file, or the `source`\n * containing the full JavaScript source of the worker.\n */\nexport async function spawnAsyncModelRunner(workerSpec: { path: string } | { source: string }): Promise<ModelRunner> {\n if (workerSpec['path']) {\n return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec['path']))\n } else {\n return spawnAsyncModelRunnerWithWorker(BlobWorker.fromText(workerSpec['source']))\n }\n}\n\n/**\n * @hidden For internal use only\n */\nasync function spawnAsyncModelRunnerWithWorker(worker: Worker): Promise<ModelRunner> {\n // Spawn the given Worker that contains the `ModelWorker`\n const modelWorker = await spawn(worker)\n\n // Wait for the worker to initialize the wasm model (in the worker thread)\n const initResult = await modelWorker.initModel()\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\n\n // The run time is stored in the first 8 bytes of the shared buffer\n const runTimeOffsetInBytes = 0\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // The inputs are stored after the run time in the shared buffer\n const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes\n const inputsLengthInElements: number = initResult.inputsLength\n const inputsLengthInBytes = inputsLengthInElements * 8\n\n // The outputs are stored after the inputs in the shared buffer\n const outputsOffsetInBytes = inputsOffsetInBytes + inputsLengthInBytes\n const outputsLengthInElements: number = initResult.outputsLength\n const outputsLengthInBytes = outputsLengthInElements * 8\n\n // The output indices are (optionally) stored after the outputs in the shared buffer\n const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes\n const indicesLengthInElements: number = initResult.outputIndicesLength\n\n // The row length is the number of elements in each row of the outputs buffer\n const outputRowLength: number = initResult.outputRowLength\n\n // Use a flag to ensure that only one request is made at a time\n let running = false\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n return {\n createOutputs: () => {\n return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq)\n },\n\n runModel: async (inputs, outputs) => {\n if (terminated) {\n throw new Error('Async model runner has already been terminated')\n } else if (running) {\n throw new Error('Async model runner only supports one `runModel` call at a time')\n } else {\n running = true\n }\n\n // Capture the current set of input values into the reusable buffer\n const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements)\n for (let i = 0; i < inputs.length; i++) {\n inputsArray[i] = inputs[i].get()\n }\n\n // Update the output indices, if needed\n if (indicesLengthInElements > 0) {\n const outputSpecs = outputs.varSpecs || []\n const indicesArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements)\n updateOutputIndices(indicesArray, outputSpecs)\n }\n\n // Run the model in the worker. We pass the underlying `ArrayBuffer`\n // instance back to the worker wrapped in a `Transfer` to make it\n // no-copy transferable, and then the worker will return it back\n // to us.\n try {\n ioBuffer = await modelWorker.runModel(Transfer(ioBuffer))\n } finally {\n running = false\n }\n\n // Save the model run time\n const runTimeArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements)\n outputs.runTimeInMillis = runTimeArray[0]\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements)\n outputs.updateFromBuffer(outputsArray, outputRowLength)\n\n return outputs\n },\n\n terminate: () => {\n if (terminated) {\n return Promise.resolve()\n } else {\n terminated = true\n return Thread.terminate(modelWorker)\n }\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { TransferDescriptor } from 'threads'\nimport { expose, Transfer } from 'threads/worker'\n\nimport type { WasmBuffer, WasmModel, WasmModelInitResult } from '@sdeverywhere/runtime'\nimport { perfElapsed, perfNow } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initWasmModel: () => Promise<WasmModelInitResult>\n/** @hidden */\nlet wasmModel: WasmModel\n/** @hidden */\nlet inputsWasmBuffer: WasmBuffer<Float64Array>\n/** @hidden */\nlet outputsWasmBuffer: WasmBuffer<Float64Array>\n/** @hidden */\nlet outputIndicesWasmBuffer: WasmBuffer<Int32Array>\n\ninterface InitResult {\n outputVarIds: string[]\n startTime: number\n endTime: number\n saveFreq: number\n inputsLength: number\n outputsLength: number\n outputIndicesLength: number\n outputRowLength: number\n ioBuffer: ArrayBuffer\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<TransferDescriptor<InitResult>> {\n if (wasmModel) {\n throw new Error('WasmModel was already initialized')\n }\n\n // Initialize the wasm model and associated buffers\n const wasmResult = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = wasmResult.model\n inputsWasmBuffer = wasmResult.inputsBuffer\n outputsWasmBuffer = wasmResult.outputsBuffer\n outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer\n\n // Create a combined array that will hold a copy of the inputs and outputs\n // wasm buffers; this buffer is no-copy transferable, whereas the wasm ones\n // are not allowed to be transferred\n const runTimeLength = 8\n const inputsLength = inputsWasmBuffer.getArrayView().length\n const outputsLength = outputsWasmBuffer.getArrayView().length\n const outputIndicesLength = outputIndicesWasmBuffer?.getArrayView().length || 0\n const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength\n const ioArray = new Float64Array(totalLength)\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n const initResult: InitResult = {\n outputVarIds: wasmResult.outputVarIds,\n startTime: wasmModel.startTime,\n endTime: wasmModel.endTime,\n saveFreq: wasmModel.saveFreq,\n inputsLength,\n outputsLength,\n outputIndicesLength,\n outputRowLength: wasmModel.numSavePoints,\n ioBuffer\n }\n return Transfer(initResult, [ioBuffer])\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!wasmModel) {\n throw new Error('WasmModel must be initialized before running the model in worker')\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeOffsetInBytes = 0\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Copy the inputs into the wasm inputs buffer\n const inputsWasmArray = inputsWasmBuffer.getArrayView()\n const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes\n const inputsLengthInElements = inputsWasmArray.length\n const inputsLengthInBytes = inputsWasmArray.byteLength\n const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements)\n inputsWasmArray.set(inputsBufferArray)\n\n // Copy the output indices into the wasm buffer, if needed\n const outputsWasmArray = outputsWasmBuffer.getArrayView()\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsLengthInBytes = outputsWasmArray.byteLength\n let useIndices = false\n if (outputIndicesWasmBuffer) {\n const indicesWasmArray = outputIndicesWasmBuffer.getArrayView()\n const indicesLengthInElements = indicesWasmArray.length\n const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes\n const indicesBufferArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements)\n if (indicesBufferArray[0] !== 0) {\n // Only use the indices if the first index is non-zero\n indicesWasmArray.set(indicesBufferArray)\n useIndices = true\n }\n }\n\n // Run the model using the wasm buffers\n const t0 = perfNow()\n wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : undefined)\n const elapsed = perfElapsed(t0)\n\n // Write the model run time to the buffer\n const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements)\n runTimeBufferArray[0] = elapsed\n\n // Copy the outputs from the wasm outputs buffer\n const outputsLengthInElements = outputsWasmArray.length\n const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements)\n outputsBufferArray.set(outputsWasmArray)\n\n // Transfer the buffer back to the runner\n return Transfer(ioBuffer)\n }\n}\n\n/**\n * Expose an object in the current worker thread that communicates with the\n * `ModelRunner` instance running in the main thread. The exposed worker\n * object will take care of running the `WasmModel` on the worker thread\n * and sending the outputs back to the main process.\n *\n * @param init The function that initializes the `WasmModel` instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initWasmModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":"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","names":["modelWorker","import_runtime"]}
@@ -0,0 +1,3 @@
1
+ export { spawnAsyncModelRunner } from './runner.cjs';
2
+ export { exposeModelWorker } from './worker.cjs';
3
+ import '@sdeverywhere/runtime';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/runner.ts
2
2
  import { BlobWorker, spawn, Thread, Transfer, Worker } from "threads";
3
- import { Outputs } from "@sdeverywhere/runtime";
3
+ import { Outputs, updateOutputIndices } from "@sdeverywhere/runtime";
4
4
  async function spawnAsyncModelRunner(workerSpec) {
5
5
  if (workerSpec["path"]) {
6
6
  return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec["path"]));
@@ -12,7 +12,18 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
12
12
  const modelWorker2 = await spawn(worker);
13
13
  const initResult = await modelWorker2.initModel();
14
14
  let ioBuffer = initResult.ioBuffer;
15
- const rowLength = initResult.rowLength;
15
+ const runTimeOffsetInBytes = 0;
16
+ const runTimeLengthInElements = 1;
17
+ const runTimeLengthInBytes = runTimeLengthInElements * 8;
18
+ const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
19
+ const inputsLengthInElements = initResult.inputsLength;
20
+ const inputsLengthInBytes = inputsLengthInElements * 8;
21
+ const outputsOffsetInBytes = inputsOffsetInBytes + inputsLengthInBytes;
22
+ const outputsLengthInElements = initResult.outputsLength;
23
+ const outputsLengthInBytes = outputsLengthInElements * 8;
24
+ const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes;
25
+ const indicesLengthInElements = initResult.outputIndicesLength;
26
+ const outputRowLength = initResult.outputRowLength;
16
27
  let running = false;
17
28
  let terminated = false;
18
29
  return {
@@ -27,24 +38,24 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
27
38
  } else {
28
39
  running = true;
29
40
  }
30
- const runTimeLengthInElements = 1;
31
- const runTimeLengthInBytes = runTimeLengthInElements * 8;
32
- const inputsLengthInElements = inputs.length;
33
- const inputsLengthInBytes = inputsLengthInElements * 8;
34
- const inputsArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements);
41
+ const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
35
42
  for (let i = 0; i < inputs.length; i++) {
36
43
  inputsArray[i] = inputs[i].get();
37
44
  }
45
+ if (indicesLengthInElements > 0) {
46
+ const outputSpecs = outputs.varSpecs || [];
47
+ const indicesArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements);
48
+ updateOutputIndices(indicesArray, outputSpecs);
49
+ }
38
50
  try {
39
51
  ioBuffer = await modelWorker2.runModel(Transfer(ioBuffer));
40
52
  } finally {
41
53
  running = false;
42
54
  }
43
- const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements);
44
- outputs.runTimeInMillis = runTimeBufferArray[0];
45
- const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
46
- const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes);
47
- outputs.updateFromBuffer(outputsArray, rowLength);
55
+ const runTimeArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
56
+ outputs.runTimeInMillis = runTimeArray[0];
57
+ const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
58
+ outputs.updateFromBuffer(outputsArray, outputRowLength);
48
59
  return outputs;
49
60
  },
50
61
  terminate: () => {
@@ -65,6 +76,7 @@ var initWasmModel;
65
76
  var wasmModel;
66
77
  var inputsWasmBuffer;
67
78
  var outputsWasmBuffer;
79
+ var outputIndicesWasmBuffer;
68
80
  var modelWorker = {
69
81
  async initModel() {
70
82
  if (wasmModel) {
@@ -74,17 +86,23 @@ var modelWorker = {
74
86
  wasmModel = wasmResult.model;
75
87
  inputsWasmBuffer = wasmResult.inputsBuffer;
76
88
  outputsWasmBuffer = wasmResult.outputsBuffer;
89
+ outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer;
77
90
  const runTimeLength = 8;
78
91
  const inputsLength = inputsWasmBuffer.getArrayView().length;
79
92
  const outputsLength = outputsWasmBuffer.getArrayView().length;
80
- const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength);
93
+ const outputIndicesLength = (outputIndicesWasmBuffer == null ? void 0 : outputIndicesWasmBuffer.getArrayView().length) || 0;
94
+ const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength;
95
+ const ioArray = new Float64Array(totalLength);
81
96
  const ioBuffer = ioArray.buffer;
82
97
  const initResult = {
83
98
  outputVarIds: wasmResult.outputVarIds,
84
99
  startTime: wasmModel.startTime,
85
100
  endTime: wasmModel.endTime,
86
101
  saveFreq: wasmModel.saveFreq,
87
- rowLength: wasmModel.numSavePoints,
102
+ inputsLength,
103
+ outputsLength,
104
+ outputIndicesLength,
105
+ outputRowLength: wasmModel.numSavePoints,
88
106
  ioBuffer
89
107
  };
90
108
  return Transfer2(initResult, [ioBuffer]);
@@ -93,21 +111,37 @@ var modelWorker = {
93
111
  if (!wasmModel) {
94
112
  throw new Error("WasmModel must be initialized before running the model in worker");
95
113
  }
114
+ const runTimeOffsetInBytes = 0;
96
115
  const runTimeLengthInElements = 1;
97
116
  const runTimeLengthInBytes = runTimeLengthInElements * 8;
98
117
  const inputsWasmArray = inputsWasmBuffer.getArrayView();
118
+ const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
99
119
  const inputsLengthInElements = inputsWasmArray.length;
100
- const inputsLengthInBytes = inputsLengthInElements * 8;
101
- const inputsBufferArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements);
120
+ const inputsLengthInBytes = inputsWasmArray.byteLength;
121
+ const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
102
122
  inputsWasmArray.set(inputsBufferArray);
123
+ const outputsWasmArray = outputsWasmBuffer.getArrayView();
124
+ const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
125
+ const outputsLengthInBytes = outputsWasmArray.byteLength;
126
+ let useIndices = false;
127
+ if (outputIndicesWasmBuffer) {
128
+ const indicesWasmArray = outputIndicesWasmBuffer.getArrayView();
129
+ const indicesLengthInElements = indicesWasmArray.length;
130
+ const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes;
131
+ const indicesBufferArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements);
132
+ if (indicesBufferArray[0] !== 0) {
133
+ indicesWasmArray.set(indicesBufferArray);
134
+ useIndices = true;
135
+ }
136
+ }
103
137
  const t0 = perfNow();
104
- wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer);
138
+ wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : void 0);
105
139
  const elapsed = perfElapsed(t0);
106
- const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements);
140
+ const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
107
141
  runTimeBufferArray[0] = elapsed;
108
- const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
109
- const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes);
110
- outputsBufferArray.set(outputsWasmBuffer.getArrayView());
142
+ const outputsLengthInElements = outputsWasmArray.length;
143
+ const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
144
+ outputsBufferArray.set(outputsWasmArray);
111
145
  return Transfer2(ioBuffer);
112
146
  }
113
147
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runner.ts","../src/worker.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport { BlobWorker, spawn, Thread, Transfer, Worker } from 'threads'\n\nimport type { ModelRunner } from '@sdeverywhere/runtime'\nimport { Outputs } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.\n *\n * In your app project, define a JavaScript file, called `worker.js` for example, that\n * initializes the model worker in the context of the Web Worker:\n *\n * ```js\n * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n *\n * async function initWasmModel() {\n * const wasmModules = loadWasm()\n * return initWasmModelAndBuffers(...)\n * }\n *\n * exposeModelWorker(initWasmModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the Web Worker and initialize the `ModelRunner` that communicates\n * with the worker:\n *\n * ```js\n * import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async/runner'\n *\n * async function initApp() {\n * // ...\n * const runner = await spawnAsyncModelRunner({ path: './worker.js' })\n * // ...\n * }\n * ```\n *\n * @param workerSpec Either a `path` to the worker JavaScript file, or the `source`\n * containing the full JavaScript source of the worker.\n */\nexport async function spawnAsyncModelRunner(workerSpec: { path: string } | { source: string }): Promise<ModelRunner> {\n if (workerSpec['path']) {\n return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec['path']))\n } else {\n return spawnAsyncModelRunnerWithWorker(BlobWorker.fromText(workerSpec['source']))\n }\n}\n\n/**\n * @hidden For internal use only\n */\nasync function spawnAsyncModelRunnerWithWorker(worker: Worker): Promise<ModelRunner> {\n // Spawn the given Worker that contains the `ModelWorker`\n const modelWorker = await spawn(worker)\n\n // Wait for the worker to initialize the wasm model (in the worker thread)\n const initResult = await modelWorker.initModel()\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\n\n // The row length is the number of elements in each row of the outputs buffer\n const rowLength = initResult.rowLength\n\n // Use a flag to ensure that only one request is made at a time\n let running = false\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n return {\n createOutputs: () => {\n return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq)\n },\n\n runModel: async (inputs, outputs) => {\n if (terminated) {\n throw new Error('Async model runner has already been terminated')\n } else if (running) {\n throw new Error('Async model runner only supports one `runModel` call at a time')\n } else {\n running = true\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Capture the current set of input values into the reusable buffer\n const inputsLengthInElements = inputs.length\n const inputsLengthInBytes = inputsLengthInElements * 8\n const inputsArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements)\n for (let i = 0; i < inputs.length; i++) {\n inputsArray[i] = inputs[i].get()\n }\n\n // Run the model in the worker. We pass the underlying `ArrayBuffer`\n // instance back to the worker wrapped in a `Transfer` to make it\n // no-copy transferable, and then the worker will return it back\n // to us.\n try {\n ioBuffer = await modelWorker.runModel(Transfer(ioBuffer))\n } finally {\n running = false\n }\n\n // Save the model run time\n const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements)\n outputs.runTimeInMillis = runTimeBufferArray[0]\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes)\n outputs.updateFromBuffer(outputsArray, rowLength)\n\n return outputs\n },\n\n terminate: () => {\n if (terminated) {\n return Promise.resolve()\n } else {\n terminated = true\n return Thread.terminate(modelWorker)\n }\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { TransferDescriptor } from 'threads'\nimport { expose, Transfer } from 'threads/worker'\n\nimport type { WasmBuffer, WasmModel, WasmModelInitResult } from '@sdeverywhere/runtime'\nimport { perfElapsed, perfNow } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initWasmModel: () => Promise<WasmModelInitResult>\n/** @hidden */\nlet wasmModel: WasmModel\n/** @hidden */\nlet inputsWasmBuffer: WasmBuffer\n/** @hidden */\nlet outputsWasmBuffer: WasmBuffer\n\ninterface InitResult {\n outputVarIds: string[]\n startTime: number\n endTime: number\n saveFreq: number\n rowLength: number\n ioBuffer: ArrayBuffer\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<TransferDescriptor<InitResult>> {\n if (wasmModel) {\n throw new Error('WasmModel was already initialized')\n }\n\n // Initialize the wasm model and associated buffers\n const wasmResult = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = wasmResult.model\n inputsWasmBuffer = wasmResult.inputsBuffer\n outputsWasmBuffer = wasmResult.outputsBuffer\n\n // Create a combined array that will hold a copy of the inputs and outputs\n // wasm buffers; this buffer is no-copy transferable, whereas the wasm ones\n // are not allowed to be transferred\n const runTimeLength = 8\n const inputsLength = inputsWasmBuffer.getArrayView().length\n const outputsLength = outputsWasmBuffer.getArrayView().length\n const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength)\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n const initResult: InitResult = {\n outputVarIds: wasmResult.outputVarIds,\n startTime: wasmModel.startTime,\n endTime: wasmModel.endTime,\n saveFreq: wasmModel.saveFreq,\n rowLength: wasmModel.numSavePoints,\n ioBuffer\n }\n return Transfer(initResult, [ioBuffer])\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!wasmModel) {\n throw new Error('WasmModel must be initialized before running the model in worker')\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Copy the inputs into the wasm inputs buffer\n const inputsWasmArray = inputsWasmBuffer.getArrayView()\n const inputsLengthInElements = inputsWasmArray.length\n const inputsLengthInBytes = inputsLengthInElements * 8\n const inputsBufferArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements)\n inputsWasmArray.set(inputsBufferArray)\n\n // Run the model using the wasm buffers\n const t0 = perfNow()\n wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer)\n const elapsed = perfElapsed(t0)\n\n // Write the model run time to the buffer\n const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements)\n runTimeBufferArray[0] = elapsed\n\n // Copy the outputs from the wasm outputs buffer\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes)\n outputsBufferArray.set(outputsWasmBuffer.getArrayView())\n\n // Transfer the buffer back to the runner\n return Transfer(ioBuffer)\n }\n}\n\n/**\n * Expose an object in the current worker thread that communicates with the\n * `ModelRunner` instance running in the main thread. The exposed worker\n * object will take care of running the `WasmModel` on the worker thread\n * and sending the outputs back to the main process.\n *\n * @param init The function that initializes the `WasmModel` instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initWasmModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":"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","names":["modelWorker","Transfer"]}
1
+ {"version":3,"sources":["../src/runner.ts","../src/worker.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport { BlobWorker, spawn, Thread, Transfer, Worker } from 'threads'\n\nimport type { ModelRunner } from '@sdeverywhere/runtime'\nimport { Outputs, updateOutputIndices } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.\n *\n * In your app project, define a JavaScript file, called `worker.js` for example, that\n * initializes the model worker in the context of the Web Worker:\n *\n * ```js\n * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n *\n * async function initWasmModel() {\n * const wasmModules = loadWasm()\n * return initWasmModelAndBuffers(...)\n * }\n *\n * exposeModelWorker(initWasmModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the Web Worker and initialize the `ModelRunner` that communicates\n * with the worker:\n *\n * ```js\n * import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async/runner'\n *\n * async function initApp() {\n * // ...\n * const runner = await spawnAsyncModelRunner({ path: './worker.js' })\n * // ...\n * }\n * ```\n *\n * @param workerSpec Either a `path` to the worker JavaScript file, or the `source`\n * containing the full JavaScript source of the worker.\n */\nexport async function spawnAsyncModelRunner(workerSpec: { path: string } | { source: string }): Promise<ModelRunner> {\n if (workerSpec['path']) {\n return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec['path']))\n } else {\n return spawnAsyncModelRunnerWithWorker(BlobWorker.fromText(workerSpec['source']))\n }\n}\n\n/**\n * @hidden For internal use only\n */\nasync function spawnAsyncModelRunnerWithWorker(worker: Worker): Promise<ModelRunner> {\n // Spawn the given Worker that contains the `ModelWorker`\n const modelWorker = await spawn(worker)\n\n // Wait for the worker to initialize the wasm model (in the worker thread)\n const initResult = await modelWorker.initModel()\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\n\n // The run time is stored in the first 8 bytes of the shared buffer\n const runTimeOffsetInBytes = 0\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // The inputs are stored after the run time in the shared buffer\n const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes\n const inputsLengthInElements: number = initResult.inputsLength\n const inputsLengthInBytes = inputsLengthInElements * 8\n\n // The outputs are stored after the inputs in the shared buffer\n const outputsOffsetInBytes = inputsOffsetInBytes + inputsLengthInBytes\n const outputsLengthInElements: number = initResult.outputsLength\n const outputsLengthInBytes = outputsLengthInElements * 8\n\n // The output indices are (optionally) stored after the outputs in the shared buffer\n const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes\n const indicesLengthInElements: number = initResult.outputIndicesLength\n\n // The row length is the number of elements in each row of the outputs buffer\n const outputRowLength: number = initResult.outputRowLength\n\n // Use a flag to ensure that only one request is made at a time\n let running = false\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n return {\n createOutputs: () => {\n return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq)\n },\n\n runModel: async (inputs, outputs) => {\n if (terminated) {\n throw new Error('Async model runner has already been terminated')\n } else if (running) {\n throw new Error('Async model runner only supports one `runModel` call at a time')\n } else {\n running = true\n }\n\n // Capture the current set of input values into the reusable buffer\n const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements)\n for (let i = 0; i < inputs.length; i++) {\n inputsArray[i] = inputs[i].get()\n }\n\n // Update the output indices, if needed\n if (indicesLengthInElements > 0) {\n const outputSpecs = outputs.varSpecs || []\n const indicesArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements)\n updateOutputIndices(indicesArray, outputSpecs)\n }\n\n // Run the model in the worker. We pass the underlying `ArrayBuffer`\n // instance back to the worker wrapped in a `Transfer` to make it\n // no-copy transferable, and then the worker will return it back\n // to us.\n try {\n ioBuffer = await modelWorker.runModel(Transfer(ioBuffer))\n } finally {\n running = false\n }\n\n // Save the model run time\n const runTimeArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements)\n outputs.runTimeInMillis = runTimeArray[0]\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements)\n outputs.updateFromBuffer(outputsArray, outputRowLength)\n\n return outputs\n },\n\n terminate: () => {\n if (terminated) {\n return Promise.resolve()\n } else {\n terminated = true\n return Thread.terminate(modelWorker)\n }\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { TransferDescriptor } from 'threads'\nimport { expose, Transfer } from 'threads/worker'\n\nimport type { WasmBuffer, WasmModel, WasmModelInitResult } from '@sdeverywhere/runtime'\nimport { perfElapsed, perfNow } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initWasmModel: () => Promise<WasmModelInitResult>\n/** @hidden */\nlet wasmModel: WasmModel\n/** @hidden */\nlet inputsWasmBuffer: WasmBuffer<Float64Array>\n/** @hidden */\nlet outputsWasmBuffer: WasmBuffer<Float64Array>\n/** @hidden */\nlet outputIndicesWasmBuffer: WasmBuffer<Int32Array>\n\ninterface InitResult {\n outputVarIds: string[]\n startTime: number\n endTime: number\n saveFreq: number\n inputsLength: number\n outputsLength: number\n outputIndicesLength: number\n outputRowLength: number\n ioBuffer: ArrayBuffer\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<TransferDescriptor<InitResult>> {\n if (wasmModel) {\n throw new Error('WasmModel was already initialized')\n }\n\n // Initialize the wasm model and associated buffers\n const wasmResult = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = wasmResult.model\n inputsWasmBuffer = wasmResult.inputsBuffer\n outputsWasmBuffer = wasmResult.outputsBuffer\n outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer\n\n // Create a combined array that will hold a copy of the inputs and outputs\n // wasm buffers; this buffer is no-copy transferable, whereas the wasm ones\n // are not allowed to be transferred\n const runTimeLength = 8\n const inputsLength = inputsWasmBuffer.getArrayView().length\n const outputsLength = outputsWasmBuffer.getArrayView().length\n const outputIndicesLength = outputIndicesWasmBuffer?.getArrayView().length || 0\n const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength\n const ioArray = new Float64Array(totalLength)\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n const initResult: InitResult = {\n outputVarIds: wasmResult.outputVarIds,\n startTime: wasmModel.startTime,\n endTime: wasmModel.endTime,\n saveFreq: wasmModel.saveFreq,\n inputsLength,\n outputsLength,\n outputIndicesLength,\n outputRowLength: wasmModel.numSavePoints,\n ioBuffer\n }\n return Transfer(initResult, [ioBuffer])\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!wasmModel) {\n throw new Error('WasmModel must be initialized before running the model in worker')\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeOffsetInBytes = 0\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Copy the inputs into the wasm inputs buffer\n const inputsWasmArray = inputsWasmBuffer.getArrayView()\n const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes\n const inputsLengthInElements = inputsWasmArray.length\n const inputsLengthInBytes = inputsWasmArray.byteLength\n const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements)\n inputsWasmArray.set(inputsBufferArray)\n\n // Copy the output indices into the wasm buffer, if needed\n const outputsWasmArray = outputsWasmBuffer.getArrayView()\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsLengthInBytes = outputsWasmArray.byteLength\n let useIndices = false\n if (outputIndicesWasmBuffer) {\n const indicesWasmArray = outputIndicesWasmBuffer.getArrayView()\n const indicesLengthInElements = indicesWasmArray.length\n const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes\n const indicesBufferArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements)\n if (indicesBufferArray[0] !== 0) {\n // Only use the indices if the first index is non-zero\n indicesWasmArray.set(indicesBufferArray)\n useIndices = true\n }\n }\n\n // Run the model using the wasm buffers\n const t0 = perfNow()\n wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : undefined)\n const elapsed = perfElapsed(t0)\n\n // Write the model run time to the buffer\n const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements)\n runTimeBufferArray[0] = elapsed\n\n // Copy the outputs from the wasm outputs buffer\n const outputsLengthInElements = outputsWasmArray.length\n const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements)\n outputsBufferArray.set(outputsWasmArray)\n\n // Transfer the buffer back to the runner\n return Transfer(ioBuffer)\n }\n}\n\n/**\n * Expose an object in the current worker thread that communicates with the\n * `ModelRunner` instance running in the main thread. The exposed worker\n * object will take care of running the `WasmModel` on the worker thread\n * and sending the outputs back to the main process.\n *\n * @param init The function that initializes the `WasmModel` instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initWasmModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":";AAEA,SAAS,YAAY,OAAO,QAAQ,UAAU,cAAc;AAG5D,SAAS,SAAS,2BAA2B;AAqC7C,eAAsB,sBAAsB,YAAyE;AACnH,MAAI,WAAW,MAAM,GAAG;AACtB,WAAO,gCAAgC,IAAI,OAAO,WAAW,MAAM,CAAC,CAAC;AAAA,EACvE,OAAO;AACL,WAAO,gCAAgC,WAAW,SAAS,WAAW,QAAQ,CAAC,CAAC;AAAA,EAClF;AACF;AAKA,eAAe,gCAAgC,QAAsC;AAEnF,QAAMA,eAAc,MAAM,MAAM,MAAM;AAGtC,QAAM,aAAa,MAAMA,aAAY,UAAU;AAC/C,MAAI,WAAwB,WAAW;AAGvC,QAAM,uBAAuB;AAC7B,QAAM,0BAA0B;AAChC,QAAM,uBAAuB,0BAA0B;AAGvD,QAAM,sBAAsB,uBAAuB;AACnD,QAAM,yBAAiC,WAAW;AAClD,QAAM,sBAAsB,yBAAyB;AAGrD,QAAM,uBAAuB,sBAAsB;AACnD,QAAM,0BAAkC,WAAW;AACnD,QAAM,uBAAuB,0BAA0B;AAGvD,QAAM,uBAAuB,uBAAuB;AACpD,QAAM,0BAAkC,WAAW;AAGnD,QAAM,kBAA0B,WAAW;AAG3C,MAAI,UAAU;AAGd,MAAI,aAAa;AAEjB,SAAO;AAAA,IACL,eAAe,MAAM;AACnB,aAAO,IAAI,QAAQ,WAAW,cAAc,WAAW,WAAW,WAAW,SAAS,WAAW,QAAQ;AAAA,IAC3G;AAAA,IAEA,UAAU,OAAO,QAAQ,YAAY;AACnC,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE,WAAW,SAAS;AAClB,cAAM,IAAI,MAAM,gEAAgE;AAAA,MAClF,OAAO;AACL,kBAAU;AAAA,MACZ;AAGA,YAAM,cAAc,IAAI,aAAa,UAAU,qBAAqB,sBAAsB;AAC1F,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,oBAAY,CAAC,IAAI,OAAO,CAAC,EAAE,IAAI;AAAA,MACjC;AAGA,UAAI,0BAA0B,GAAG;AAC/B,cAAM,cAAc,QAAQ,YAAY,CAAC;AACzC,cAAM,eAAe,IAAI,WAAW,UAAU,sBAAsB,uBAAuB;AAC3F,4BAAoB,cAAc,WAAW;AAAA,MAC/C;AAMA,UAAI;AACF,mBAAW,MAAMA,aAAY,SAAS,SAAS,QAAQ,CAAC;AAAA,MAC1D,UAAE;AACA,kBAAU;AAAA,MACZ;AAGA,YAAM,eAAe,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AAC7F,cAAQ,kBAAkB,aAAa,CAAC;AAIxC,YAAM,eAAe,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AAC7F,cAAQ,iBAAiB,cAAc,eAAe;AAEtD,aAAO;AAAA,IACT;AAAA,IAEA,WAAW,MAAM;AACf,UAAI,YAAY;AACd,eAAO,QAAQ,QAAQ;AAAA,MACzB,OAAO;AACL,qBAAa;AACb,eAAO,OAAO,UAAUA,YAAW;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;;;AChJA,SAAS,QAAQ,YAAAC,iBAAgB;AAGjC,SAAS,aAAa,eAAe;AAGrC,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAeJ,IAAM,cAAc;AAAA,EAClB,MAAM,YAAqD;AACzD,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAGA,UAAM,aAAa,MAAM,cAAc;AAGvC,gBAAY,WAAW;AACvB,uBAAmB,WAAW;AAC9B,wBAAoB,WAAW;AAC/B,8BAA0B,WAAW;AAKrC,UAAM,gBAAgB;AACtB,UAAM,eAAe,iBAAiB,aAAa,EAAE;AACrD,UAAM,gBAAgB,kBAAkB,aAAa,EAAE;AACvD,UAAM,uBAAsB,mEAAyB,eAAe,WAAU;AAC9E,UAAM,cAAc,gBAAgB,eAAe,gBAAgB;AACnE,UAAM,UAAU,IAAI,aAAa,WAAW;AAG5C,UAAM,WAAW,QAAQ;AACzB,UAAM,aAAyB;AAAA,MAC7B,cAAc,WAAW;AAAA,MACzB,WAAW,UAAU;AAAA,MACrB,SAAS,UAAU;AAAA,MACnB,UAAU,UAAU;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA,iBAAiB,UAAU;AAAA,MAC3B;AAAA,IACF;AACA,WAAOA,UAAS,YAAY,CAAC,QAAQ,CAAC;AAAA,EACxC;AAAA,EAEA,SAAS,UAAwD;AAC/D,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACpF;AAGA,UAAM,uBAAuB;AAC7B,UAAM,0BAA0B;AAChC,UAAM,uBAAuB,0BAA0B;AAGvD,UAAM,kBAAkB,iBAAiB,aAAa;AACtD,UAAM,sBAAsB,uBAAuB;AACnD,UAAM,yBAAyB,gBAAgB;AAC/C,UAAM,sBAAsB,gBAAgB;AAC5C,UAAM,oBAAoB,IAAI,aAAa,UAAU,qBAAqB,sBAAsB;AAChG,oBAAgB,IAAI,iBAAiB;AAGrC,UAAM,mBAAmB,kBAAkB,aAAa;AACxD,UAAM,uBAAuB,uBAAuB;AACpD,UAAM,uBAAuB,iBAAiB;AAC9C,QAAI,aAAa;AACjB,QAAI,yBAAyB;AAC3B,YAAM,mBAAmB,wBAAwB,aAAa;AAC9D,YAAM,0BAA0B,iBAAiB;AACjD,YAAM,uBAAuB,uBAAuB;AACpD,YAAM,qBAAqB,IAAI,WAAW,UAAU,sBAAsB,uBAAuB;AACjG,UAAI,mBAAmB,CAAC,MAAM,GAAG;AAE/B,yBAAiB,IAAI,kBAAkB;AACvC,qBAAa;AAAA,MACf;AAAA,IACF;AAGA,UAAM,KAAK,QAAQ;AACnB,cAAU,SAAS,kBAAkB,mBAAmB,aAAa,0BAA0B,MAAS;AACxG,UAAM,UAAU,YAAY,EAAE;AAG9B,UAAM,qBAAqB,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AACnG,uBAAmB,CAAC,IAAI;AAGxB,UAAM,0BAA0B,iBAAiB;AACjD,UAAM,qBAAqB,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AACnG,uBAAmB,IAAI,gBAAgB;AAGvC,WAAOA,UAAS,QAAQ;AAAA,EAC1B;AACF;AAWO,SAAS,kBAAkB,MAAgD;AAGhF,kBAAgB;AAGhB,SAAO,WAAW;AACpB;","names":["modelWorker","Transfer"]}
package/dist/runner.cjs CHANGED
@@ -35,7 +35,18 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
35
35
  const modelWorker = await (0, import_threads.spawn)(worker);
36
36
  const initResult = await modelWorker.initModel();
37
37
  let ioBuffer = initResult.ioBuffer;
38
- const rowLength = initResult.rowLength;
38
+ const runTimeOffsetInBytes = 0;
39
+ const runTimeLengthInElements = 1;
40
+ const runTimeLengthInBytes = runTimeLengthInElements * 8;
41
+ const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
42
+ const inputsLengthInElements = initResult.inputsLength;
43
+ const inputsLengthInBytes = inputsLengthInElements * 8;
44
+ const outputsOffsetInBytes = inputsOffsetInBytes + inputsLengthInBytes;
45
+ const outputsLengthInElements = initResult.outputsLength;
46
+ const outputsLengthInBytes = outputsLengthInElements * 8;
47
+ const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes;
48
+ const indicesLengthInElements = initResult.outputIndicesLength;
49
+ const outputRowLength = initResult.outputRowLength;
39
50
  let running = false;
40
51
  let terminated = false;
41
52
  return {
@@ -50,24 +61,24 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
50
61
  } else {
51
62
  running = true;
52
63
  }
53
- const runTimeLengthInElements = 1;
54
- const runTimeLengthInBytes = runTimeLengthInElements * 8;
55
- const inputsLengthInElements = inputs.length;
56
- const inputsLengthInBytes = inputsLengthInElements * 8;
57
- const inputsArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements);
64
+ const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
58
65
  for (let i = 0; i < inputs.length; i++) {
59
66
  inputsArray[i] = inputs[i].get();
60
67
  }
68
+ if (indicesLengthInElements > 0) {
69
+ const outputSpecs = outputs.varSpecs || [];
70
+ const indicesArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements);
71
+ (0, import_runtime.updateOutputIndices)(indicesArray, outputSpecs);
72
+ }
61
73
  try {
62
74
  ioBuffer = await modelWorker.runModel((0, import_threads.Transfer)(ioBuffer));
63
75
  } finally {
64
76
  running = false;
65
77
  }
66
- const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements);
67
- outputs.runTimeInMillis = runTimeBufferArray[0];
68
- const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
69
- const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes);
70
- outputs.updateFromBuffer(outputsArray, rowLength);
78
+ const runTimeArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
79
+ outputs.runTimeInMillis = runTimeArray[0];
80
+ const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
81
+ outputs.updateFromBuffer(outputsArray, outputRowLength);
71
82
  return outputs;
72
83
  },
73
84
  terminate: () => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runner.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport { BlobWorker, spawn, Thread, Transfer, Worker } from 'threads'\n\nimport type { ModelRunner } from '@sdeverywhere/runtime'\nimport { Outputs } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.\n *\n * In your app project, define a JavaScript file, called `worker.js` for example, that\n * initializes the model worker in the context of the Web Worker:\n *\n * ```js\n * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n *\n * async function initWasmModel() {\n * const wasmModules = loadWasm()\n * return initWasmModelAndBuffers(...)\n * }\n *\n * exposeModelWorker(initWasmModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the Web Worker and initialize the `ModelRunner` that communicates\n * with the worker:\n *\n * ```js\n * import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async/runner'\n *\n * async function initApp() {\n * // ...\n * const runner = await spawnAsyncModelRunner({ path: './worker.js' })\n * // ...\n * }\n * ```\n *\n * @param workerSpec Either a `path` to the worker JavaScript file, or the `source`\n * containing the full JavaScript source of the worker.\n */\nexport async function spawnAsyncModelRunner(workerSpec: { path: string } | { source: string }): Promise<ModelRunner> {\n if (workerSpec['path']) {\n return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec['path']))\n } else {\n return spawnAsyncModelRunnerWithWorker(BlobWorker.fromText(workerSpec['source']))\n }\n}\n\n/**\n * @hidden For internal use only\n */\nasync function spawnAsyncModelRunnerWithWorker(worker: Worker): Promise<ModelRunner> {\n // Spawn the given Worker that contains the `ModelWorker`\n const modelWorker = await spawn(worker)\n\n // Wait for the worker to initialize the wasm model (in the worker thread)\n const initResult = await modelWorker.initModel()\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\n\n // The row length is the number of elements in each row of the outputs buffer\n const rowLength = initResult.rowLength\n\n // Use a flag to ensure that only one request is made at a time\n let running = false\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n return {\n createOutputs: () => {\n return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq)\n },\n\n runModel: async (inputs, outputs) => {\n if (terminated) {\n throw new Error('Async model runner has already been terminated')\n } else if (running) {\n throw new Error('Async model runner only supports one `runModel` call at a time')\n } else {\n running = true\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Capture the current set of input values into the reusable buffer\n const inputsLengthInElements = inputs.length\n const inputsLengthInBytes = inputsLengthInElements * 8\n const inputsArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements)\n for (let i = 0; i < inputs.length; i++) {\n inputsArray[i] = inputs[i].get()\n }\n\n // Run the model in the worker. We pass the underlying `ArrayBuffer`\n // instance back to the worker wrapped in a `Transfer` to make it\n // no-copy transferable, and then the worker will return it back\n // to us.\n try {\n ioBuffer = await modelWorker.runModel(Transfer(ioBuffer))\n } finally {\n running = false\n }\n\n // Save the model run time\n const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements)\n outputs.runTimeInMillis = runTimeBufferArray[0]\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes)\n outputs.updateFromBuffer(outputsArray, rowLength)\n\n return outputs\n },\n\n terminate: () => {\n if (terminated) {\n return Promise.resolve()\n } else {\n terminated = true\n return Thread.terminate(modelWorker)\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,qBAA4D;AAG5D,qBAAwB;AAqCxB,eAAsB,sBAAsB,YAAyE;AACnH,MAAI,WAAW,SAAS;AACtB,WAAO,gCAAgC,IAAI,sBAAO,WAAW,OAAO,CAAC;AAAA,EACvE,OAAO;AACL,WAAO,gCAAgC,0BAAW,SAAS,WAAW,SAAS,CAAC;AAAA,EAClF;AACF;AAKA,eAAe,gCAAgC,QAAsC;AAEnF,QAAM,cAAc,UAAM,sBAAM,MAAM;AAGtC,QAAM,aAAa,MAAM,YAAY,UAAU;AAC/C,MAAI,WAAwB,WAAW;AAGvC,QAAM,YAAY,WAAW;AAG7B,MAAI,UAAU;AAGd,MAAI,aAAa;AAEjB,SAAO;AAAA,IACL,eAAe,MAAM;AACnB,aAAO,IAAI,uBAAQ,WAAW,cAAc,WAAW,WAAW,WAAW,SAAS,WAAW,QAAQ;AAAA,IAC3G;AAAA,IAEA,UAAU,OAAO,QAAQ,YAAY;AACnC,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE,WAAW,SAAS;AAClB,cAAM,IAAI,MAAM,gEAAgE;AAAA,MAClF,OAAO;AACL,kBAAU;AAAA,MACZ;AAGA,YAAM,0BAA0B;AAChC,YAAM,uBAAuB,0BAA0B;AAGvD,YAAM,yBAAyB,OAAO;AACtC,YAAM,sBAAsB,yBAAyB;AACrD,YAAM,cAAc,IAAI,aAAa,UAAU,sBAAsB,sBAAsB;AAC3F,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,oBAAY,KAAK,OAAO,GAAG,IAAI;AAAA,MACjC;AAMA,UAAI;AACF,mBAAW,MAAM,YAAY,aAAS,yBAAS,QAAQ,CAAC;AAAA,MAC1D,UAAE;AACA,kBAAU;AAAA,MACZ;AAGA,YAAM,qBAAqB,IAAI,aAAa,UAAU,GAAG,uBAAuB;AAChF,cAAQ,kBAAkB,mBAAmB;AAI7C,YAAM,uBAAuB,uBAAuB;AACpD,YAAM,eAAe,IAAI,aAAa,UAAU,oBAAoB;AACpE,cAAQ,iBAAiB,cAAc,SAAS;AAEhD,aAAO;AAAA,IACT;AAAA,IAEA,WAAW,MAAM;AACf,UAAI,YAAY;AACd,eAAO,QAAQ,QAAQ;AAAA,MACzB,OAAO;AACL,qBAAa;AACb,eAAO,sBAAO,UAAU,WAAW;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/runner.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport { BlobWorker, spawn, Thread, Transfer, Worker } from 'threads'\n\nimport type { ModelRunner } from '@sdeverywhere/runtime'\nimport { Outputs, updateOutputIndices } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.\n *\n * In your app project, define a JavaScript file, called `worker.js` for example, that\n * initializes the model worker in the context of the Web Worker:\n *\n * ```js\n * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n *\n * async function initWasmModel() {\n * const wasmModules = loadWasm()\n * return initWasmModelAndBuffers(...)\n * }\n *\n * exposeModelWorker(initWasmModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the Web Worker and initialize the `ModelRunner` that communicates\n * with the worker:\n *\n * ```js\n * import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async/runner'\n *\n * async function initApp() {\n * // ...\n * const runner = await spawnAsyncModelRunner({ path: './worker.js' })\n * // ...\n * }\n * ```\n *\n * @param workerSpec Either a `path` to the worker JavaScript file, or the `source`\n * containing the full JavaScript source of the worker.\n */\nexport async function spawnAsyncModelRunner(workerSpec: { path: string } | { source: string }): Promise<ModelRunner> {\n if (workerSpec['path']) {\n return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec['path']))\n } else {\n return spawnAsyncModelRunnerWithWorker(BlobWorker.fromText(workerSpec['source']))\n }\n}\n\n/**\n * @hidden For internal use only\n */\nasync function spawnAsyncModelRunnerWithWorker(worker: Worker): Promise<ModelRunner> {\n // Spawn the given Worker that contains the `ModelWorker`\n const modelWorker = await spawn(worker)\n\n // Wait for the worker to initialize the wasm model (in the worker thread)\n const initResult = await modelWorker.initModel()\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\n\n // The run time is stored in the first 8 bytes of the shared buffer\n const runTimeOffsetInBytes = 0\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // The inputs are stored after the run time in the shared buffer\n const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes\n const inputsLengthInElements: number = initResult.inputsLength\n const inputsLengthInBytes = inputsLengthInElements * 8\n\n // The outputs are stored after the inputs in the shared buffer\n const outputsOffsetInBytes = inputsOffsetInBytes + inputsLengthInBytes\n const outputsLengthInElements: number = initResult.outputsLength\n const outputsLengthInBytes = outputsLengthInElements * 8\n\n // The output indices are (optionally) stored after the outputs in the shared buffer\n const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes\n const indicesLengthInElements: number = initResult.outputIndicesLength\n\n // The row length is the number of elements in each row of the outputs buffer\n const outputRowLength: number = initResult.outputRowLength\n\n // Use a flag to ensure that only one request is made at a time\n let running = false\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n return {\n createOutputs: () => {\n return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq)\n },\n\n runModel: async (inputs, outputs) => {\n if (terminated) {\n throw new Error('Async model runner has already been terminated')\n } else if (running) {\n throw new Error('Async model runner only supports one `runModel` call at a time')\n } else {\n running = true\n }\n\n // Capture the current set of input values into the reusable buffer\n const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements)\n for (let i = 0; i < inputs.length; i++) {\n inputsArray[i] = inputs[i].get()\n }\n\n // Update the output indices, if needed\n if (indicesLengthInElements > 0) {\n const outputSpecs = outputs.varSpecs || []\n const indicesArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements)\n updateOutputIndices(indicesArray, outputSpecs)\n }\n\n // Run the model in the worker. We pass the underlying `ArrayBuffer`\n // instance back to the worker wrapped in a `Transfer` to make it\n // no-copy transferable, and then the worker will return it back\n // to us.\n try {\n ioBuffer = await modelWorker.runModel(Transfer(ioBuffer))\n } finally {\n running = false\n }\n\n // Save the model run time\n const runTimeArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements)\n outputs.runTimeInMillis = runTimeArray[0]\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements)\n outputs.updateFromBuffer(outputsArray, outputRowLength)\n\n return outputs\n },\n\n terminate: () => {\n if (terminated) {\n return Promise.resolve()\n } else {\n terminated = true\n return Thread.terminate(modelWorker)\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,qBAA4D;AAG5D,qBAA6C;AAqC7C,eAAsB,sBAAsB,YAAyE;AACnH,MAAI,WAAW,MAAM,GAAG;AACtB,WAAO,gCAAgC,IAAI,sBAAO,WAAW,MAAM,CAAC,CAAC;AAAA,EACvE,OAAO;AACL,WAAO,gCAAgC,0BAAW,SAAS,WAAW,QAAQ,CAAC,CAAC;AAAA,EAClF;AACF;AAKA,eAAe,gCAAgC,QAAsC;AAEnF,QAAM,cAAc,UAAM,sBAAM,MAAM;AAGtC,QAAM,aAAa,MAAM,YAAY,UAAU;AAC/C,MAAI,WAAwB,WAAW;AAGvC,QAAM,uBAAuB;AAC7B,QAAM,0BAA0B;AAChC,QAAM,uBAAuB,0BAA0B;AAGvD,QAAM,sBAAsB,uBAAuB;AACnD,QAAM,yBAAiC,WAAW;AAClD,QAAM,sBAAsB,yBAAyB;AAGrD,QAAM,uBAAuB,sBAAsB;AACnD,QAAM,0BAAkC,WAAW;AACnD,QAAM,uBAAuB,0BAA0B;AAGvD,QAAM,uBAAuB,uBAAuB;AACpD,QAAM,0BAAkC,WAAW;AAGnD,QAAM,kBAA0B,WAAW;AAG3C,MAAI,UAAU;AAGd,MAAI,aAAa;AAEjB,SAAO;AAAA,IACL,eAAe,MAAM;AACnB,aAAO,IAAI,uBAAQ,WAAW,cAAc,WAAW,WAAW,WAAW,SAAS,WAAW,QAAQ;AAAA,IAC3G;AAAA,IAEA,UAAU,OAAO,QAAQ,YAAY;AACnC,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE,WAAW,SAAS;AAClB,cAAM,IAAI,MAAM,gEAAgE;AAAA,MAClF,OAAO;AACL,kBAAU;AAAA,MACZ;AAGA,YAAM,cAAc,IAAI,aAAa,UAAU,qBAAqB,sBAAsB;AAC1F,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,oBAAY,CAAC,IAAI,OAAO,CAAC,EAAE,IAAI;AAAA,MACjC;AAGA,UAAI,0BAA0B,GAAG;AAC/B,cAAM,cAAc,QAAQ,YAAY,CAAC;AACzC,cAAM,eAAe,IAAI,WAAW,UAAU,sBAAsB,uBAAuB;AAC3F,gDAAoB,cAAc,WAAW;AAAA,MAC/C;AAMA,UAAI;AACF,mBAAW,MAAM,YAAY,aAAS,yBAAS,QAAQ,CAAC;AAAA,MAC1D,UAAE;AACA,kBAAU;AAAA,MACZ;AAGA,YAAM,eAAe,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AAC7F,cAAQ,kBAAkB,aAAa,CAAC;AAIxC,YAAM,eAAe,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AAC7F,cAAQ,iBAAiB,cAAc,eAAe;AAEtD,aAAO;AAAA,IACT;AAAA,IAEA,WAAW,MAAM;AACf,UAAI,YAAY;AACd,eAAO,QAAQ,QAAQ;AAAA,MACzB,OAAO;AACL,qBAAa;AACb,eAAO,sBAAO,UAAU,WAAW;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,44 @@
1
+ import { ModelRunner } from '@sdeverywhere/runtime';
2
+
3
+ /**
4
+ * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.
5
+ *
6
+ * In your app project, define a JavaScript file, called `worker.js` for example, that
7
+ * initializes the model worker in the context of the Web Worker:
8
+ *
9
+ * ```js
10
+ * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'
11
+ * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'
12
+ *
13
+ * async function initWasmModel() {
14
+ * const wasmModules = loadWasm()
15
+ * return initWasmModelAndBuffers(...)
16
+ * }
17
+ *
18
+ * exposeModelWorker(initWasmModel)
19
+ * ```
20
+ *
21
+ * Then, in your web app, call the `spawnAsyncModelRunner` function, which
22
+ * will spawn the Web Worker and initialize the `ModelRunner` that communicates
23
+ * with the worker:
24
+ *
25
+ * ```js
26
+ * import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async/runner'
27
+ *
28
+ * async function initApp() {
29
+ * // ...
30
+ * const runner = await spawnAsyncModelRunner({ path: './worker.js' })
31
+ * // ...
32
+ * }
33
+ * ```
34
+ *
35
+ * @param workerSpec Either a `path` to the worker JavaScript file, or the `source`
36
+ * containing the full JavaScript source of the worker.
37
+ */
38
+ declare function spawnAsyncModelRunner(workerSpec: {
39
+ path: string;
40
+ } | {
41
+ source: string;
42
+ }): Promise<ModelRunner>;
43
+
44
+ export { spawnAsyncModelRunner };
package/dist/runner.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/runner.ts
2
2
  import { BlobWorker, spawn, Thread, Transfer, Worker } from "threads";
3
- import { Outputs } from "@sdeverywhere/runtime";
3
+ import { Outputs, updateOutputIndices } from "@sdeverywhere/runtime";
4
4
  async function spawnAsyncModelRunner(workerSpec) {
5
5
  if (workerSpec["path"]) {
6
6
  return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec["path"]));
@@ -12,7 +12,18 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
12
12
  const modelWorker = await spawn(worker);
13
13
  const initResult = await modelWorker.initModel();
14
14
  let ioBuffer = initResult.ioBuffer;
15
- const rowLength = initResult.rowLength;
15
+ const runTimeOffsetInBytes = 0;
16
+ const runTimeLengthInElements = 1;
17
+ const runTimeLengthInBytes = runTimeLengthInElements * 8;
18
+ const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
19
+ const inputsLengthInElements = initResult.inputsLength;
20
+ const inputsLengthInBytes = inputsLengthInElements * 8;
21
+ const outputsOffsetInBytes = inputsOffsetInBytes + inputsLengthInBytes;
22
+ const outputsLengthInElements = initResult.outputsLength;
23
+ const outputsLengthInBytes = outputsLengthInElements * 8;
24
+ const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes;
25
+ const indicesLengthInElements = initResult.outputIndicesLength;
26
+ const outputRowLength = initResult.outputRowLength;
16
27
  let running = false;
17
28
  let terminated = false;
18
29
  return {
@@ -27,24 +38,24 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
27
38
  } else {
28
39
  running = true;
29
40
  }
30
- const runTimeLengthInElements = 1;
31
- const runTimeLengthInBytes = runTimeLengthInElements * 8;
32
- const inputsLengthInElements = inputs.length;
33
- const inputsLengthInBytes = inputsLengthInElements * 8;
34
- const inputsArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements);
41
+ const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
35
42
  for (let i = 0; i < inputs.length; i++) {
36
43
  inputsArray[i] = inputs[i].get();
37
44
  }
45
+ if (indicesLengthInElements > 0) {
46
+ const outputSpecs = outputs.varSpecs || [];
47
+ const indicesArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements);
48
+ updateOutputIndices(indicesArray, outputSpecs);
49
+ }
38
50
  try {
39
51
  ioBuffer = await modelWorker.runModel(Transfer(ioBuffer));
40
52
  } finally {
41
53
  running = false;
42
54
  }
43
- const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements);
44
- outputs.runTimeInMillis = runTimeBufferArray[0];
45
- const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
46
- const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes);
47
- outputs.updateFromBuffer(outputsArray, rowLength);
55
+ const runTimeArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
56
+ outputs.runTimeInMillis = runTimeArray[0];
57
+ const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
58
+ outputs.updateFromBuffer(outputsArray, outputRowLength);
48
59
  return outputs;
49
60
  },
50
61
  terminate: () => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runner.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport { BlobWorker, spawn, Thread, Transfer, Worker } from 'threads'\n\nimport type { ModelRunner } from '@sdeverywhere/runtime'\nimport { Outputs } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.\n *\n * In your app project, define a JavaScript file, called `worker.js` for example, that\n * initializes the model worker in the context of the Web Worker:\n *\n * ```js\n * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n *\n * async function initWasmModel() {\n * const wasmModules = loadWasm()\n * return initWasmModelAndBuffers(...)\n * }\n *\n * exposeModelWorker(initWasmModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the Web Worker and initialize the `ModelRunner` that communicates\n * with the worker:\n *\n * ```js\n * import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async/runner'\n *\n * async function initApp() {\n * // ...\n * const runner = await spawnAsyncModelRunner({ path: './worker.js' })\n * // ...\n * }\n * ```\n *\n * @param workerSpec Either a `path` to the worker JavaScript file, or the `source`\n * containing the full JavaScript source of the worker.\n */\nexport async function spawnAsyncModelRunner(workerSpec: { path: string } | { source: string }): Promise<ModelRunner> {\n if (workerSpec['path']) {\n return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec['path']))\n } else {\n return spawnAsyncModelRunnerWithWorker(BlobWorker.fromText(workerSpec['source']))\n }\n}\n\n/**\n * @hidden For internal use only\n */\nasync function spawnAsyncModelRunnerWithWorker(worker: Worker): Promise<ModelRunner> {\n // Spawn the given Worker that contains the `ModelWorker`\n const modelWorker = await spawn(worker)\n\n // Wait for the worker to initialize the wasm model (in the worker thread)\n const initResult = await modelWorker.initModel()\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\n\n // The row length is the number of elements in each row of the outputs buffer\n const rowLength = initResult.rowLength\n\n // Use a flag to ensure that only one request is made at a time\n let running = false\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n return {\n createOutputs: () => {\n return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq)\n },\n\n runModel: async (inputs, outputs) => {\n if (terminated) {\n throw new Error('Async model runner has already been terminated')\n } else if (running) {\n throw new Error('Async model runner only supports one `runModel` call at a time')\n } else {\n running = true\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Capture the current set of input values into the reusable buffer\n const inputsLengthInElements = inputs.length\n const inputsLengthInBytes = inputsLengthInElements * 8\n const inputsArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements)\n for (let i = 0; i < inputs.length; i++) {\n inputsArray[i] = inputs[i].get()\n }\n\n // Run the model in the worker. We pass the underlying `ArrayBuffer`\n // instance back to the worker wrapped in a `Transfer` to make it\n // no-copy transferable, and then the worker will return it back\n // to us.\n try {\n ioBuffer = await modelWorker.runModel(Transfer(ioBuffer))\n } finally {\n running = false\n }\n\n // Save the model run time\n const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements)\n outputs.runTimeInMillis = runTimeBufferArray[0]\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes)\n outputs.updateFromBuffer(outputsArray, rowLength)\n\n return outputs\n },\n\n terminate: () => {\n if (terminated) {\n return Promise.resolve()\n } else {\n terminated = true\n return Thread.terminate(modelWorker)\n }\n }\n }\n}\n"],"mappings":";AAEA,SAAS,YAAY,OAAO,QAAQ,UAAU,cAAc;AAG5D,SAAS,eAAe;AAqCxB,eAAsB,sBAAsB,YAAyE;AACnH,MAAI,WAAW,SAAS;AACtB,WAAO,gCAAgC,IAAI,OAAO,WAAW,OAAO,CAAC;AAAA,EACvE,OAAO;AACL,WAAO,gCAAgC,WAAW,SAAS,WAAW,SAAS,CAAC;AAAA,EAClF;AACF;AAKA,eAAe,gCAAgC,QAAsC;AAEnF,QAAM,cAAc,MAAM,MAAM,MAAM;AAGtC,QAAM,aAAa,MAAM,YAAY,UAAU;AAC/C,MAAI,WAAwB,WAAW;AAGvC,QAAM,YAAY,WAAW;AAG7B,MAAI,UAAU;AAGd,MAAI,aAAa;AAEjB,SAAO;AAAA,IACL,eAAe,MAAM;AACnB,aAAO,IAAI,QAAQ,WAAW,cAAc,WAAW,WAAW,WAAW,SAAS,WAAW,QAAQ;AAAA,IAC3G;AAAA,IAEA,UAAU,OAAO,QAAQ,YAAY;AACnC,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE,WAAW,SAAS;AAClB,cAAM,IAAI,MAAM,gEAAgE;AAAA,MAClF,OAAO;AACL,kBAAU;AAAA,MACZ;AAGA,YAAM,0BAA0B;AAChC,YAAM,uBAAuB,0BAA0B;AAGvD,YAAM,yBAAyB,OAAO;AACtC,YAAM,sBAAsB,yBAAyB;AACrD,YAAM,cAAc,IAAI,aAAa,UAAU,sBAAsB,sBAAsB;AAC3F,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,oBAAY,KAAK,OAAO,GAAG,IAAI;AAAA,MACjC;AAMA,UAAI;AACF,mBAAW,MAAM,YAAY,SAAS,SAAS,QAAQ,CAAC;AAAA,MAC1D,UAAE;AACA,kBAAU;AAAA,MACZ;AAGA,YAAM,qBAAqB,IAAI,aAAa,UAAU,GAAG,uBAAuB;AAChF,cAAQ,kBAAkB,mBAAmB;AAI7C,YAAM,uBAAuB,uBAAuB;AACpD,YAAM,eAAe,IAAI,aAAa,UAAU,oBAAoB;AACpE,cAAQ,iBAAiB,cAAc,SAAS;AAEhD,aAAO;AAAA,IACT;AAAA,IAEA,WAAW,MAAM;AACf,UAAI,YAAY;AACd,eAAO,QAAQ,QAAQ;AAAA,MACzB,OAAO;AACL,qBAAa;AACb,eAAO,OAAO,UAAU,WAAW;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/runner.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport { BlobWorker, spawn, Thread, Transfer, Worker } from 'threads'\n\nimport type { ModelRunner } from '@sdeverywhere/runtime'\nimport { Outputs, updateOutputIndices } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.\n *\n * In your app project, define a JavaScript file, called `worker.js` for example, that\n * initializes the model worker in the context of the Web Worker:\n *\n * ```js\n * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n *\n * async function initWasmModel() {\n * const wasmModules = loadWasm()\n * return initWasmModelAndBuffers(...)\n * }\n *\n * exposeModelWorker(initWasmModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the Web Worker and initialize the `ModelRunner` that communicates\n * with the worker:\n *\n * ```js\n * import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async/runner'\n *\n * async function initApp() {\n * // ...\n * const runner = await spawnAsyncModelRunner({ path: './worker.js' })\n * // ...\n * }\n * ```\n *\n * @param workerSpec Either a `path` to the worker JavaScript file, or the `source`\n * containing the full JavaScript source of the worker.\n */\nexport async function spawnAsyncModelRunner(workerSpec: { path: string } | { source: string }): Promise<ModelRunner> {\n if (workerSpec['path']) {\n return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec['path']))\n } else {\n return spawnAsyncModelRunnerWithWorker(BlobWorker.fromText(workerSpec['source']))\n }\n}\n\n/**\n * @hidden For internal use only\n */\nasync function spawnAsyncModelRunnerWithWorker(worker: Worker): Promise<ModelRunner> {\n // Spawn the given Worker that contains the `ModelWorker`\n const modelWorker = await spawn(worker)\n\n // Wait for the worker to initialize the wasm model (in the worker thread)\n const initResult = await modelWorker.initModel()\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\n\n // The run time is stored in the first 8 bytes of the shared buffer\n const runTimeOffsetInBytes = 0\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // The inputs are stored after the run time in the shared buffer\n const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes\n const inputsLengthInElements: number = initResult.inputsLength\n const inputsLengthInBytes = inputsLengthInElements * 8\n\n // The outputs are stored after the inputs in the shared buffer\n const outputsOffsetInBytes = inputsOffsetInBytes + inputsLengthInBytes\n const outputsLengthInElements: number = initResult.outputsLength\n const outputsLengthInBytes = outputsLengthInElements * 8\n\n // The output indices are (optionally) stored after the outputs in the shared buffer\n const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes\n const indicesLengthInElements: number = initResult.outputIndicesLength\n\n // The row length is the number of elements in each row of the outputs buffer\n const outputRowLength: number = initResult.outputRowLength\n\n // Use a flag to ensure that only one request is made at a time\n let running = false\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n return {\n createOutputs: () => {\n return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq)\n },\n\n runModel: async (inputs, outputs) => {\n if (terminated) {\n throw new Error('Async model runner has already been terminated')\n } else if (running) {\n throw new Error('Async model runner only supports one `runModel` call at a time')\n } else {\n running = true\n }\n\n // Capture the current set of input values into the reusable buffer\n const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements)\n for (let i = 0; i < inputs.length; i++) {\n inputsArray[i] = inputs[i].get()\n }\n\n // Update the output indices, if needed\n if (indicesLengthInElements > 0) {\n const outputSpecs = outputs.varSpecs || []\n const indicesArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements)\n updateOutputIndices(indicesArray, outputSpecs)\n }\n\n // Run the model in the worker. We pass the underlying `ArrayBuffer`\n // instance back to the worker wrapped in a `Transfer` to make it\n // no-copy transferable, and then the worker will return it back\n // to us.\n try {\n ioBuffer = await modelWorker.runModel(Transfer(ioBuffer))\n } finally {\n running = false\n }\n\n // Save the model run time\n const runTimeArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements)\n outputs.runTimeInMillis = runTimeArray[0]\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n const outputsArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements)\n outputs.updateFromBuffer(outputsArray, outputRowLength)\n\n return outputs\n },\n\n terminate: () => {\n if (terminated) {\n return Promise.resolve()\n } else {\n terminated = true\n return Thread.terminate(modelWorker)\n }\n }\n }\n}\n"],"mappings":";AAEA,SAAS,YAAY,OAAO,QAAQ,UAAU,cAAc;AAG5D,SAAS,SAAS,2BAA2B;AAqC7C,eAAsB,sBAAsB,YAAyE;AACnH,MAAI,WAAW,MAAM,GAAG;AACtB,WAAO,gCAAgC,IAAI,OAAO,WAAW,MAAM,CAAC,CAAC;AAAA,EACvE,OAAO;AACL,WAAO,gCAAgC,WAAW,SAAS,WAAW,QAAQ,CAAC,CAAC;AAAA,EAClF;AACF;AAKA,eAAe,gCAAgC,QAAsC;AAEnF,QAAM,cAAc,MAAM,MAAM,MAAM;AAGtC,QAAM,aAAa,MAAM,YAAY,UAAU;AAC/C,MAAI,WAAwB,WAAW;AAGvC,QAAM,uBAAuB;AAC7B,QAAM,0BAA0B;AAChC,QAAM,uBAAuB,0BAA0B;AAGvD,QAAM,sBAAsB,uBAAuB;AACnD,QAAM,yBAAiC,WAAW;AAClD,QAAM,sBAAsB,yBAAyB;AAGrD,QAAM,uBAAuB,sBAAsB;AACnD,QAAM,0BAAkC,WAAW;AACnD,QAAM,uBAAuB,0BAA0B;AAGvD,QAAM,uBAAuB,uBAAuB;AACpD,QAAM,0BAAkC,WAAW;AAGnD,QAAM,kBAA0B,WAAW;AAG3C,MAAI,UAAU;AAGd,MAAI,aAAa;AAEjB,SAAO;AAAA,IACL,eAAe,MAAM;AACnB,aAAO,IAAI,QAAQ,WAAW,cAAc,WAAW,WAAW,WAAW,SAAS,WAAW,QAAQ;AAAA,IAC3G;AAAA,IAEA,UAAU,OAAO,QAAQ,YAAY;AACnC,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE,WAAW,SAAS;AAClB,cAAM,IAAI,MAAM,gEAAgE;AAAA,MAClF,OAAO;AACL,kBAAU;AAAA,MACZ;AAGA,YAAM,cAAc,IAAI,aAAa,UAAU,qBAAqB,sBAAsB;AAC1F,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,oBAAY,CAAC,IAAI,OAAO,CAAC,EAAE,IAAI;AAAA,MACjC;AAGA,UAAI,0BAA0B,GAAG;AAC/B,cAAM,cAAc,QAAQ,YAAY,CAAC;AACzC,cAAM,eAAe,IAAI,WAAW,UAAU,sBAAsB,uBAAuB;AAC3F,4BAAoB,cAAc,WAAW;AAAA,MAC/C;AAMA,UAAI;AACF,mBAAW,MAAM,YAAY,SAAS,SAAS,QAAQ,CAAC;AAAA,MAC1D,UAAE;AACA,kBAAU;AAAA,MACZ;AAGA,YAAM,eAAe,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AAC7F,cAAQ,kBAAkB,aAAa,CAAC;AAIxC,YAAM,eAAe,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AAC7F,cAAQ,iBAAiB,cAAc,eAAe;AAEtD,aAAO;AAAA,IACT;AAAA,IAEA,WAAW,MAAM;AACf,UAAI,YAAY;AACd,eAAO,QAAQ,QAAQ;AAAA,MACzB,OAAO;AACL,qBAAa;AACb,eAAO,OAAO,UAAU,WAAW;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
package/dist/worker.cjs CHANGED
@@ -28,6 +28,7 @@ var initWasmModel;
28
28
  var wasmModel;
29
29
  var inputsWasmBuffer;
30
30
  var outputsWasmBuffer;
31
+ var outputIndicesWasmBuffer;
31
32
  var modelWorker = {
32
33
  async initModel() {
33
34
  if (wasmModel) {
@@ -37,17 +38,23 @@ var modelWorker = {
37
38
  wasmModel = wasmResult.model;
38
39
  inputsWasmBuffer = wasmResult.inputsBuffer;
39
40
  outputsWasmBuffer = wasmResult.outputsBuffer;
41
+ outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer;
40
42
  const runTimeLength = 8;
41
43
  const inputsLength = inputsWasmBuffer.getArrayView().length;
42
44
  const outputsLength = outputsWasmBuffer.getArrayView().length;
43
- const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength);
45
+ const outputIndicesLength = (outputIndicesWasmBuffer == null ? void 0 : outputIndicesWasmBuffer.getArrayView().length) || 0;
46
+ const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength;
47
+ const ioArray = new Float64Array(totalLength);
44
48
  const ioBuffer = ioArray.buffer;
45
49
  const initResult = {
46
50
  outputVarIds: wasmResult.outputVarIds,
47
51
  startTime: wasmModel.startTime,
48
52
  endTime: wasmModel.endTime,
49
53
  saveFreq: wasmModel.saveFreq,
50
- rowLength: wasmModel.numSavePoints,
54
+ inputsLength,
55
+ outputsLength,
56
+ outputIndicesLength,
57
+ outputRowLength: wasmModel.numSavePoints,
51
58
  ioBuffer
52
59
  };
53
60
  return (0, import_worker.Transfer)(initResult, [ioBuffer]);
@@ -56,21 +63,37 @@ var modelWorker = {
56
63
  if (!wasmModel) {
57
64
  throw new Error("WasmModel must be initialized before running the model in worker");
58
65
  }
66
+ const runTimeOffsetInBytes = 0;
59
67
  const runTimeLengthInElements = 1;
60
68
  const runTimeLengthInBytes = runTimeLengthInElements * 8;
61
69
  const inputsWasmArray = inputsWasmBuffer.getArrayView();
70
+ const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
62
71
  const inputsLengthInElements = inputsWasmArray.length;
63
- const inputsLengthInBytes = inputsLengthInElements * 8;
64
- const inputsBufferArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements);
72
+ const inputsLengthInBytes = inputsWasmArray.byteLength;
73
+ const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
65
74
  inputsWasmArray.set(inputsBufferArray);
75
+ const outputsWasmArray = outputsWasmBuffer.getArrayView();
76
+ const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
77
+ const outputsLengthInBytes = outputsWasmArray.byteLength;
78
+ let useIndices = false;
79
+ if (outputIndicesWasmBuffer) {
80
+ const indicesWasmArray = outputIndicesWasmBuffer.getArrayView();
81
+ const indicesLengthInElements = indicesWasmArray.length;
82
+ const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes;
83
+ const indicesBufferArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements);
84
+ if (indicesBufferArray[0] !== 0) {
85
+ indicesWasmArray.set(indicesBufferArray);
86
+ useIndices = true;
87
+ }
88
+ }
66
89
  const t0 = (0, import_runtime.perfNow)();
67
- wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer);
90
+ wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : void 0);
68
91
  const elapsed = (0, import_runtime.perfElapsed)(t0);
69
- const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements);
92
+ const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
70
93
  runTimeBufferArray[0] = elapsed;
71
- const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
72
- const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes);
73
- outputsBufferArray.set(outputsWasmBuffer.getArrayView());
94
+ const outputsLengthInElements = outputsWasmArray.length;
95
+ const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
96
+ outputsBufferArray.set(outputsWasmArray);
74
97
  return (0, import_worker.Transfer)(ioBuffer);
75
98
  }
76
99
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/worker.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { TransferDescriptor } from 'threads'\nimport { expose, Transfer } from 'threads/worker'\n\nimport type { WasmBuffer, WasmModel, WasmModelInitResult } from '@sdeverywhere/runtime'\nimport { perfElapsed, perfNow } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initWasmModel: () => Promise<WasmModelInitResult>\n/** @hidden */\nlet wasmModel: WasmModel\n/** @hidden */\nlet inputsWasmBuffer: WasmBuffer\n/** @hidden */\nlet outputsWasmBuffer: WasmBuffer\n\ninterface InitResult {\n outputVarIds: string[]\n startTime: number\n endTime: number\n saveFreq: number\n rowLength: number\n ioBuffer: ArrayBuffer\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<TransferDescriptor<InitResult>> {\n if (wasmModel) {\n throw new Error('WasmModel was already initialized')\n }\n\n // Initialize the wasm model and associated buffers\n const wasmResult = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = wasmResult.model\n inputsWasmBuffer = wasmResult.inputsBuffer\n outputsWasmBuffer = wasmResult.outputsBuffer\n\n // Create a combined array that will hold a copy of the inputs and outputs\n // wasm buffers; this buffer is no-copy transferable, whereas the wasm ones\n // are not allowed to be transferred\n const runTimeLength = 8\n const inputsLength = inputsWasmBuffer.getArrayView().length\n const outputsLength = outputsWasmBuffer.getArrayView().length\n const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength)\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n const initResult: InitResult = {\n outputVarIds: wasmResult.outputVarIds,\n startTime: wasmModel.startTime,\n endTime: wasmModel.endTime,\n saveFreq: wasmModel.saveFreq,\n rowLength: wasmModel.numSavePoints,\n ioBuffer\n }\n return Transfer(initResult, [ioBuffer])\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!wasmModel) {\n throw new Error('WasmModel must be initialized before running the model in worker')\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Copy the inputs into the wasm inputs buffer\n const inputsWasmArray = inputsWasmBuffer.getArrayView()\n const inputsLengthInElements = inputsWasmArray.length\n const inputsLengthInBytes = inputsLengthInElements * 8\n const inputsBufferArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements)\n inputsWasmArray.set(inputsBufferArray)\n\n // Run the model using the wasm buffers\n const t0 = perfNow()\n wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer)\n const elapsed = perfElapsed(t0)\n\n // Write the model run time to the buffer\n const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements)\n runTimeBufferArray[0] = elapsed\n\n // Copy the outputs from the wasm outputs buffer\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes)\n outputsBufferArray.set(outputsWasmBuffer.getArrayView())\n\n // Transfer the buffer back to the runner\n return Transfer(ioBuffer)\n }\n}\n\n/**\n * Expose an object in the current worker thread that communicates with the\n * `ModelRunner` instance running in the main thread. The exposed worker\n * object will take care of running the `WasmModel` on the worker thread\n * and sending the outputs back to the main process.\n *\n * @param init The function that initializes the `WasmModel` instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initWasmModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAAiC;AAGjC,qBAAqC;AAGrC,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAYJ,IAAM,cAAc;AAAA,EAClB,MAAM,YAAqD;AACzD,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAGA,UAAM,aAAa,MAAM,cAAc;AAGvC,gBAAY,WAAW;AACvB,uBAAmB,WAAW;AAC9B,wBAAoB,WAAW;AAK/B,UAAM,gBAAgB;AACtB,UAAM,eAAe,iBAAiB,aAAa,EAAE;AACrD,UAAM,gBAAgB,kBAAkB,aAAa,EAAE;AACvD,UAAM,UAAU,IAAI,aAAa,gBAAgB,eAAe,aAAa;AAG7E,UAAM,WAAW,QAAQ;AACzB,UAAM,aAAyB;AAAA,MAC7B,cAAc,WAAW;AAAA,MACzB,WAAW,UAAU;AAAA,MACrB,SAAS,UAAU;AAAA,MACnB,UAAU,UAAU;AAAA,MACpB,WAAW,UAAU;AAAA,MACrB;AAAA,IACF;AACA,eAAO,wBAAS,YAAY,CAAC,QAAQ,CAAC;AAAA,EACxC;AAAA,EAEA,SAAS,UAAwD;AAC/D,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACpF;AAGA,UAAM,0BAA0B;AAChC,UAAM,uBAAuB,0BAA0B;AAGvD,UAAM,kBAAkB,iBAAiB,aAAa;AACtD,UAAM,yBAAyB,gBAAgB;AAC/C,UAAM,sBAAsB,yBAAyB;AACrD,UAAM,oBAAoB,IAAI,aAAa,UAAU,sBAAsB,sBAAsB;AACjG,oBAAgB,IAAI,iBAAiB;AAGrC,UAAM,SAAK,wBAAQ;AACnB,cAAU,SAAS,kBAAkB,iBAAiB;AACtD,UAAM,cAAU,4BAAY,EAAE;AAG9B,UAAM,qBAAqB,IAAI,aAAa,UAAU,GAAG,uBAAuB;AAChF,uBAAmB,KAAK;AAGxB,UAAM,uBAAuB,uBAAuB;AACpD,UAAM,qBAAqB,IAAI,aAAa,UAAU,oBAAoB;AAC1E,uBAAmB,IAAI,kBAAkB,aAAa,CAAC;AAGvD,eAAO,wBAAS,QAAQ;AAAA,EAC1B;AACF;AAWO,SAAS,kBAAkB,MAAgD;AAGhF,kBAAgB;AAGhB,4BAAO,WAAW;AACpB;","names":[]}
1
+ {"version":3,"sources":["../src/worker.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { TransferDescriptor } from 'threads'\nimport { expose, Transfer } from 'threads/worker'\n\nimport type { WasmBuffer, WasmModel, WasmModelInitResult } from '@sdeverywhere/runtime'\nimport { perfElapsed, perfNow } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initWasmModel: () => Promise<WasmModelInitResult>\n/** @hidden */\nlet wasmModel: WasmModel\n/** @hidden */\nlet inputsWasmBuffer: WasmBuffer<Float64Array>\n/** @hidden */\nlet outputsWasmBuffer: WasmBuffer<Float64Array>\n/** @hidden */\nlet outputIndicesWasmBuffer: WasmBuffer<Int32Array>\n\ninterface InitResult {\n outputVarIds: string[]\n startTime: number\n endTime: number\n saveFreq: number\n inputsLength: number\n outputsLength: number\n outputIndicesLength: number\n outputRowLength: number\n ioBuffer: ArrayBuffer\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<TransferDescriptor<InitResult>> {\n if (wasmModel) {\n throw new Error('WasmModel was already initialized')\n }\n\n // Initialize the wasm model and associated buffers\n const wasmResult = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = wasmResult.model\n inputsWasmBuffer = wasmResult.inputsBuffer\n outputsWasmBuffer = wasmResult.outputsBuffer\n outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer\n\n // Create a combined array that will hold a copy of the inputs and outputs\n // wasm buffers; this buffer is no-copy transferable, whereas the wasm ones\n // are not allowed to be transferred\n const runTimeLength = 8\n const inputsLength = inputsWasmBuffer.getArrayView().length\n const outputsLength = outputsWasmBuffer.getArrayView().length\n const outputIndicesLength = outputIndicesWasmBuffer?.getArrayView().length || 0\n const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength\n const ioArray = new Float64Array(totalLength)\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n const initResult: InitResult = {\n outputVarIds: wasmResult.outputVarIds,\n startTime: wasmModel.startTime,\n endTime: wasmModel.endTime,\n saveFreq: wasmModel.saveFreq,\n inputsLength,\n outputsLength,\n outputIndicesLength,\n outputRowLength: wasmModel.numSavePoints,\n ioBuffer\n }\n return Transfer(initResult, [ioBuffer])\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!wasmModel) {\n throw new Error('WasmModel must be initialized before running the model in worker')\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeOffsetInBytes = 0\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Copy the inputs into the wasm inputs buffer\n const inputsWasmArray = inputsWasmBuffer.getArrayView()\n const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes\n const inputsLengthInElements = inputsWasmArray.length\n const inputsLengthInBytes = inputsWasmArray.byteLength\n const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements)\n inputsWasmArray.set(inputsBufferArray)\n\n // Copy the output indices into the wasm buffer, if needed\n const outputsWasmArray = outputsWasmBuffer.getArrayView()\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsLengthInBytes = outputsWasmArray.byteLength\n let useIndices = false\n if (outputIndicesWasmBuffer) {\n const indicesWasmArray = outputIndicesWasmBuffer.getArrayView()\n const indicesLengthInElements = indicesWasmArray.length\n const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes\n const indicesBufferArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements)\n if (indicesBufferArray[0] !== 0) {\n // Only use the indices if the first index is non-zero\n indicesWasmArray.set(indicesBufferArray)\n useIndices = true\n }\n }\n\n // Run the model using the wasm buffers\n const t0 = perfNow()\n wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : undefined)\n const elapsed = perfElapsed(t0)\n\n // Write the model run time to the buffer\n const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements)\n runTimeBufferArray[0] = elapsed\n\n // Copy the outputs from the wasm outputs buffer\n const outputsLengthInElements = outputsWasmArray.length\n const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements)\n outputsBufferArray.set(outputsWasmArray)\n\n // Transfer the buffer back to the runner\n return Transfer(ioBuffer)\n }\n}\n\n/**\n * Expose an object in the current worker thread that communicates with the\n * `ModelRunner` instance running in the main thread. The exposed worker\n * object will take care of running the `WasmModel` on the worker thread\n * and sending the outputs back to the main process.\n *\n * @param init The function that initializes the `WasmModel` instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initWasmModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAAiC;AAGjC,qBAAqC;AAGrC,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAeJ,IAAM,cAAc;AAAA,EAClB,MAAM,YAAqD;AACzD,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAGA,UAAM,aAAa,MAAM,cAAc;AAGvC,gBAAY,WAAW;AACvB,uBAAmB,WAAW;AAC9B,wBAAoB,WAAW;AAC/B,8BAA0B,WAAW;AAKrC,UAAM,gBAAgB;AACtB,UAAM,eAAe,iBAAiB,aAAa,EAAE;AACrD,UAAM,gBAAgB,kBAAkB,aAAa,EAAE;AACvD,UAAM,uBAAsB,mEAAyB,eAAe,WAAU;AAC9E,UAAM,cAAc,gBAAgB,eAAe,gBAAgB;AACnE,UAAM,UAAU,IAAI,aAAa,WAAW;AAG5C,UAAM,WAAW,QAAQ;AACzB,UAAM,aAAyB;AAAA,MAC7B,cAAc,WAAW;AAAA,MACzB,WAAW,UAAU;AAAA,MACrB,SAAS,UAAU;AAAA,MACnB,UAAU,UAAU;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA,iBAAiB,UAAU;AAAA,MAC3B;AAAA,IACF;AACA,eAAO,wBAAS,YAAY,CAAC,QAAQ,CAAC;AAAA,EACxC;AAAA,EAEA,SAAS,UAAwD;AAC/D,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACpF;AAGA,UAAM,uBAAuB;AAC7B,UAAM,0BAA0B;AAChC,UAAM,uBAAuB,0BAA0B;AAGvD,UAAM,kBAAkB,iBAAiB,aAAa;AACtD,UAAM,sBAAsB,uBAAuB;AACnD,UAAM,yBAAyB,gBAAgB;AAC/C,UAAM,sBAAsB,gBAAgB;AAC5C,UAAM,oBAAoB,IAAI,aAAa,UAAU,qBAAqB,sBAAsB;AAChG,oBAAgB,IAAI,iBAAiB;AAGrC,UAAM,mBAAmB,kBAAkB,aAAa;AACxD,UAAM,uBAAuB,uBAAuB;AACpD,UAAM,uBAAuB,iBAAiB;AAC9C,QAAI,aAAa;AACjB,QAAI,yBAAyB;AAC3B,YAAM,mBAAmB,wBAAwB,aAAa;AAC9D,YAAM,0BAA0B,iBAAiB;AACjD,YAAM,uBAAuB,uBAAuB;AACpD,YAAM,qBAAqB,IAAI,WAAW,UAAU,sBAAsB,uBAAuB;AACjG,UAAI,mBAAmB,CAAC,MAAM,GAAG;AAE/B,yBAAiB,IAAI,kBAAkB;AACvC,qBAAa;AAAA,MACf;AAAA,IACF;AAGA,UAAM,SAAK,wBAAQ;AACnB,cAAU,SAAS,kBAAkB,mBAAmB,aAAa,0BAA0B,MAAS;AACxG,UAAM,cAAU,4BAAY,EAAE;AAG9B,UAAM,qBAAqB,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AACnG,uBAAmB,CAAC,IAAI;AAGxB,UAAM,0BAA0B,iBAAiB;AACjD,UAAM,qBAAqB,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AACnG,uBAAmB,IAAI,gBAAgB;AAGvC,eAAO,wBAAS,QAAQ;AAAA,EAC1B;AACF;AAWO,SAAS,kBAAkB,MAAgD;AAGhF,kBAAgB;AAGhB,4BAAO,WAAW;AACpB;","names":[]}
@@ -0,0 +1,14 @@
1
+ import { WasmModelInitResult } from '@sdeverywhere/runtime';
2
+
3
+ /**
4
+ * Expose an object in the current worker thread that communicates with the
5
+ * `ModelRunner` instance running in the main thread. The exposed worker
6
+ * object will take care of running the `WasmModel` on the worker thread
7
+ * and sending the outputs back to the main process.
8
+ *
9
+ * @param init The function that initializes the `WasmModel` instance that
10
+ * is used in the worker thread.
11
+ */
12
+ declare function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void;
13
+
14
+ export { exposeModelWorker };
package/dist/worker.js CHANGED
@@ -5,6 +5,7 @@ var initWasmModel;
5
5
  var wasmModel;
6
6
  var inputsWasmBuffer;
7
7
  var outputsWasmBuffer;
8
+ var outputIndicesWasmBuffer;
8
9
  var modelWorker = {
9
10
  async initModel() {
10
11
  if (wasmModel) {
@@ -14,17 +15,23 @@ var modelWorker = {
14
15
  wasmModel = wasmResult.model;
15
16
  inputsWasmBuffer = wasmResult.inputsBuffer;
16
17
  outputsWasmBuffer = wasmResult.outputsBuffer;
18
+ outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer;
17
19
  const runTimeLength = 8;
18
20
  const inputsLength = inputsWasmBuffer.getArrayView().length;
19
21
  const outputsLength = outputsWasmBuffer.getArrayView().length;
20
- const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength);
22
+ const outputIndicesLength = (outputIndicesWasmBuffer == null ? void 0 : outputIndicesWasmBuffer.getArrayView().length) || 0;
23
+ const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength;
24
+ const ioArray = new Float64Array(totalLength);
21
25
  const ioBuffer = ioArray.buffer;
22
26
  const initResult = {
23
27
  outputVarIds: wasmResult.outputVarIds,
24
28
  startTime: wasmModel.startTime,
25
29
  endTime: wasmModel.endTime,
26
30
  saveFreq: wasmModel.saveFreq,
27
- rowLength: wasmModel.numSavePoints,
31
+ inputsLength,
32
+ outputsLength,
33
+ outputIndicesLength,
34
+ outputRowLength: wasmModel.numSavePoints,
28
35
  ioBuffer
29
36
  };
30
37
  return Transfer(initResult, [ioBuffer]);
@@ -33,21 +40,37 @@ var modelWorker = {
33
40
  if (!wasmModel) {
34
41
  throw new Error("WasmModel must be initialized before running the model in worker");
35
42
  }
43
+ const runTimeOffsetInBytes = 0;
36
44
  const runTimeLengthInElements = 1;
37
45
  const runTimeLengthInBytes = runTimeLengthInElements * 8;
38
46
  const inputsWasmArray = inputsWasmBuffer.getArrayView();
47
+ const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
39
48
  const inputsLengthInElements = inputsWasmArray.length;
40
- const inputsLengthInBytes = inputsLengthInElements * 8;
41
- const inputsBufferArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements);
49
+ const inputsLengthInBytes = inputsWasmArray.byteLength;
50
+ const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
42
51
  inputsWasmArray.set(inputsBufferArray);
52
+ const outputsWasmArray = outputsWasmBuffer.getArrayView();
53
+ const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
54
+ const outputsLengthInBytes = outputsWasmArray.byteLength;
55
+ let useIndices = false;
56
+ if (outputIndicesWasmBuffer) {
57
+ const indicesWasmArray = outputIndicesWasmBuffer.getArrayView();
58
+ const indicesLengthInElements = indicesWasmArray.length;
59
+ const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes;
60
+ const indicesBufferArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements);
61
+ if (indicesBufferArray[0] !== 0) {
62
+ indicesWasmArray.set(indicesBufferArray);
63
+ useIndices = true;
64
+ }
65
+ }
43
66
  const t0 = perfNow();
44
- wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer);
67
+ wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : void 0);
45
68
  const elapsed = perfElapsed(t0);
46
- const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements);
69
+ const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
47
70
  runTimeBufferArray[0] = elapsed;
48
- const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
49
- const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes);
50
- outputsBufferArray.set(outputsWasmBuffer.getArrayView());
71
+ const outputsLengthInElements = outputsWasmArray.length;
72
+ const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
73
+ outputsBufferArray.set(outputsWasmArray);
51
74
  return Transfer(ioBuffer);
52
75
  }
53
76
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/worker.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { TransferDescriptor } from 'threads'\nimport { expose, Transfer } from 'threads/worker'\n\nimport type { WasmBuffer, WasmModel, WasmModelInitResult } from '@sdeverywhere/runtime'\nimport { perfElapsed, perfNow } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initWasmModel: () => Promise<WasmModelInitResult>\n/** @hidden */\nlet wasmModel: WasmModel\n/** @hidden */\nlet inputsWasmBuffer: WasmBuffer\n/** @hidden */\nlet outputsWasmBuffer: WasmBuffer\n\ninterface InitResult {\n outputVarIds: string[]\n startTime: number\n endTime: number\n saveFreq: number\n rowLength: number\n ioBuffer: ArrayBuffer\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<TransferDescriptor<InitResult>> {\n if (wasmModel) {\n throw new Error('WasmModel was already initialized')\n }\n\n // Initialize the wasm model and associated buffers\n const wasmResult = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = wasmResult.model\n inputsWasmBuffer = wasmResult.inputsBuffer\n outputsWasmBuffer = wasmResult.outputsBuffer\n\n // Create a combined array that will hold a copy of the inputs and outputs\n // wasm buffers; this buffer is no-copy transferable, whereas the wasm ones\n // are not allowed to be transferred\n const runTimeLength = 8\n const inputsLength = inputsWasmBuffer.getArrayView().length\n const outputsLength = outputsWasmBuffer.getArrayView().length\n const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength)\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n const initResult: InitResult = {\n outputVarIds: wasmResult.outputVarIds,\n startTime: wasmModel.startTime,\n endTime: wasmModel.endTime,\n saveFreq: wasmModel.saveFreq,\n rowLength: wasmModel.numSavePoints,\n ioBuffer\n }\n return Transfer(initResult, [ioBuffer])\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!wasmModel) {\n throw new Error('WasmModel must be initialized before running the model in worker')\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Copy the inputs into the wasm inputs buffer\n const inputsWasmArray = inputsWasmBuffer.getArrayView()\n const inputsLengthInElements = inputsWasmArray.length\n const inputsLengthInBytes = inputsLengthInElements * 8\n const inputsBufferArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements)\n inputsWasmArray.set(inputsBufferArray)\n\n // Run the model using the wasm buffers\n const t0 = perfNow()\n wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer)\n const elapsed = perfElapsed(t0)\n\n // Write the model run time to the buffer\n const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements)\n runTimeBufferArray[0] = elapsed\n\n // Copy the outputs from the wasm outputs buffer\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes)\n outputsBufferArray.set(outputsWasmBuffer.getArrayView())\n\n // Transfer the buffer back to the runner\n return Transfer(ioBuffer)\n }\n}\n\n/**\n * Expose an object in the current worker thread that communicates with the\n * `ModelRunner` instance running in the main thread. The exposed worker\n * object will take care of running the `WasmModel` on the worker thread\n * and sending the outputs back to the main process.\n *\n * @param init The function that initializes the `WasmModel` instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initWasmModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":";AAGA,SAAS,QAAQ,gBAAgB;AAGjC,SAAS,aAAa,eAAe;AAGrC,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAYJ,IAAM,cAAc;AAAA,EAClB,MAAM,YAAqD;AACzD,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAGA,UAAM,aAAa,MAAM,cAAc;AAGvC,gBAAY,WAAW;AACvB,uBAAmB,WAAW;AAC9B,wBAAoB,WAAW;AAK/B,UAAM,gBAAgB;AACtB,UAAM,eAAe,iBAAiB,aAAa,EAAE;AACrD,UAAM,gBAAgB,kBAAkB,aAAa,EAAE;AACvD,UAAM,UAAU,IAAI,aAAa,gBAAgB,eAAe,aAAa;AAG7E,UAAM,WAAW,QAAQ;AACzB,UAAM,aAAyB;AAAA,MAC7B,cAAc,WAAW;AAAA,MACzB,WAAW,UAAU;AAAA,MACrB,SAAS,UAAU;AAAA,MACnB,UAAU,UAAU;AAAA,MACpB,WAAW,UAAU;AAAA,MACrB;AAAA,IACF;AACA,WAAO,SAAS,YAAY,CAAC,QAAQ,CAAC;AAAA,EACxC;AAAA,EAEA,SAAS,UAAwD;AAC/D,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACpF;AAGA,UAAM,0BAA0B;AAChC,UAAM,uBAAuB,0BAA0B;AAGvD,UAAM,kBAAkB,iBAAiB,aAAa;AACtD,UAAM,yBAAyB,gBAAgB;AAC/C,UAAM,sBAAsB,yBAAyB;AACrD,UAAM,oBAAoB,IAAI,aAAa,UAAU,sBAAsB,sBAAsB;AACjG,oBAAgB,IAAI,iBAAiB;AAGrC,UAAM,KAAK,QAAQ;AACnB,cAAU,SAAS,kBAAkB,iBAAiB;AACtD,UAAM,UAAU,YAAY,EAAE;AAG9B,UAAM,qBAAqB,IAAI,aAAa,UAAU,GAAG,uBAAuB;AAChF,uBAAmB,KAAK;AAGxB,UAAM,uBAAuB,uBAAuB;AACpD,UAAM,qBAAqB,IAAI,aAAa,UAAU,oBAAoB;AAC1E,uBAAmB,IAAI,kBAAkB,aAAa,CAAC;AAGvD,WAAO,SAAS,QAAQ;AAAA,EAC1B;AACF;AAWO,SAAS,kBAAkB,MAAgD;AAGhF,kBAAgB;AAGhB,SAAO,WAAW;AACpB;","names":[]}
1
+ {"version":3,"sources":["../src/worker.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { TransferDescriptor } from 'threads'\nimport { expose, Transfer } from 'threads/worker'\n\nimport type { WasmBuffer, WasmModel, WasmModelInitResult } from '@sdeverywhere/runtime'\nimport { perfElapsed, perfNow } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initWasmModel: () => Promise<WasmModelInitResult>\n/** @hidden */\nlet wasmModel: WasmModel\n/** @hidden */\nlet inputsWasmBuffer: WasmBuffer<Float64Array>\n/** @hidden */\nlet outputsWasmBuffer: WasmBuffer<Float64Array>\n/** @hidden */\nlet outputIndicesWasmBuffer: WasmBuffer<Int32Array>\n\ninterface InitResult {\n outputVarIds: string[]\n startTime: number\n endTime: number\n saveFreq: number\n inputsLength: number\n outputsLength: number\n outputIndicesLength: number\n outputRowLength: number\n ioBuffer: ArrayBuffer\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<TransferDescriptor<InitResult>> {\n if (wasmModel) {\n throw new Error('WasmModel was already initialized')\n }\n\n // Initialize the wasm model and associated buffers\n const wasmResult = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = wasmResult.model\n inputsWasmBuffer = wasmResult.inputsBuffer\n outputsWasmBuffer = wasmResult.outputsBuffer\n outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer\n\n // Create a combined array that will hold a copy of the inputs and outputs\n // wasm buffers; this buffer is no-copy transferable, whereas the wasm ones\n // are not allowed to be transferred\n const runTimeLength = 8\n const inputsLength = inputsWasmBuffer.getArrayView().length\n const outputsLength = outputsWasmBuffer.getArrayView().length\n const outputIndicesLength = outputIndicesWasmBuffer?.getArrayView().length || 0\n const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength\n const ioArray = new Float64Array(totalLength)\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n const initResult: InitResult = {\n outputVarIds: wasmResult.outputVarIds,\n startTime: wasmModel.startTime,\n endTime: wasmModel.endTime,\n saveFreq: wasmModel.saveFreq,\n inputsLength,\n outputsLength,\n outputIndicesLength,\n outputRowLength: wasmModel.numSavePoints,\n ioBuffer\n }\n return Transfer(initResult, [ioBuffer])\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!wasmModel) {\n throw new Error('WasmModel must be initialized before running the model in worker')\n }\n\n // The run time is stored in the first 8 bytes\n const runTimeOffsetInBytes = 0\n const runTimeLengthInElements = 1\n const runTimeLengthInBytes = runTimeLengthInElements * 8\n\n // Copy the inputs into the wasm inputs buffer\n const inputsWasmArray = inputsWasmBuffer.getArrayView()\n const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes\n const inputsLengthInElements = inputsWasmArray.length\n const inputsLengthInBytes = inputsWasmArray.byteLength\n const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements)\n inputsWasmArray.set(inputsBufferArray)\n\n // Copy the output indices into the wasm buffer, if needed\n const outputsWasmArray = outputsWasmBuffer.getArrayView()\n const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes\n const outputsLengthInBytes = outputsWasmArray.byteLength\n let useIndices = false\n if (outputIndicesWasmBuffer) {\n const indicesWasmArray = outputIndicesWasmBuffer.getArrayView()\n const indicesLengthInElements = indicesWasmArray.length\n const indicesOffsetInBytes = outputsOffsetInBytes + outputsLengthInBytes\n const indicesBufferArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements)\n if (indicesBufferArray[0] !== 0) {\n // Only use the indices if the first index is non-zero\n indicesWasmArray.set(indicesBufferArray)\n useIndices = true\n }\n }\n\n // Run the model using the wasm buffers\n const t0 = perfNow()\n wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : undefined)\n const elapsed = perfElapsed(t0)\n\n // Write the model run time to the buffer\n const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements)\n runTimeBufferArray[0] = elapsed\n\n // Copy the outputs from the wasm outputs buffer\n const outputsLengthInElements = outputsWasmArray.length\n const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements)\n outputsBufferArray.set(outputsWasmArray)\n\n // Transfer the buffer back to the runner\n return Transfer(ioBuffer)\n }\n}\n\n/**\n * Expose an object in the current worker thread that communicates with the\n * `ModelRunner` instance running in the main thread. The exposed worker\n * object will take care of running the `WasmModel` on the worker thread\n * and sending the outputs back to the main process.\n *\n * @param init The function that initializes the `WasmModel` instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initWasmModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":";AAGA,SAAS,QAAQ,gBAAgB;AAGjC,SAAS,aAAa,eAAe;AAGrC,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAeJ,IAAM,cAAc;AAAA,EAClB,MAAM,YAAqD;AACzD,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAGA,UAAM,aAAa,MAAM,cAAc;AAGvC,gBAAY,WAAW;AACvB,uBAAmB,WAAW;AAC9B,wBAAoB,WAAW;AAC/B,8BAA0B,WAAW;AAKrC,UAAM,gBAAgB;AACtB,UAAM,eAAe,iBAAiB,aAAa,EAAE;AACrD,UAAM,gBAAgB,kBAAkB,aAAa,EAAE;AACvD,UAAM,uBAAsB,mEAAyB,eAAe,WAAU;AAC9E,UAAM,cAAc,gBAAgB,eAAe,gBAAgB;AACnE,UAAM,UAAU,IAAI,aAAa,WAAW;AAG5C,UAAM,WAAW,QAAQ;AACzB,UAAM,aAAyB;AAAA,MAC7B,cAAc,WAAW;AAAA,MACzB,WAAW,UAAU;AAAA,MACrB,SAAS,UAAU;AAAA,MACnB,UAAU,UAAU;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA,iBAAiB,UAAU;AAAA,MAC3B;AAAA,IACF;AACA,WAAO,SAAS,YAAY,CAAC,QAAQ,CAAC;AAAA,EACxC;AAAA,EAEA,SAAS,UAAwD;AAC/D,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACpF;AAGA,UAAM,uBAAuB;AAC7B,UAAM,0BAA0B;AAChC,UAAM,uBAAuB,0BAA0B;AAGvD,UAAM,kBAAkB,iBAAiB,aAAa;AACtD,UAAM,sBAAsB,uBAAuB;AACnD,UAAM,yBAAyB,gBAAgB;AAC/C,UAAM,sBAAsB,gBAAgB;AAC5C,UAAM,oBAAoB,IAAI,aAAa,UAAU,qBAAqB,sBAAsB;AAChG,oBAAgB,IAAI,iBAAiB;AAGrC,UAAM,mBAAmB,kBAAkB,aAAa;AACxD,UAAM,uBAAuB,uBAAuB;AACpD,UAAM,uBAAuB,iBAAiB;AAC9C,QAAI,aAAa;AACjB,QAAI,yBAAyB;AAC3B,YAAM,mBAAmB,wBAAwB,aAAa;AAC9D,YAAM,0BAA0B,iBAAiB;AACjD,YAAM,uBAAuB,uBAAuB;AACpD,YAAM,qBAAqB,IAAI,WAAW,UAAU,sBAAsB,uBAAuB;AACjG,UAAI,mBAAmB,CAAC,MAAM,GAAG;AAE/B,yBAAiB,IAAI,kBAAkB;AACvC,qBAAa;AAAA,MACf;AAAA,IACF;AAGA,UAAM,KAAK,QAAQ;AACnB,cAAU,SAAS,kBAAkB,mBAAmB,aAAa,0BAA0B,MAAS;AACxG,UAAM,UAAU,YAAY,EAAE;AAG9B,UAAM,qBAAqB,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AACnG,uBAAmB,CAAC,IAAI;AAGxB,UAAM,0BAA0B,iBAAiB;AACjD,UAAM,qBAAqB,IAAI,aAAa,UAAU,sBAAsB,uBAAuB;AACnG,uBAAmB,IAAI,gBAAgB;AAGvC,WAAO,SAAS,QAAQ;AAAA,EAC1B;AACF;AAWO,SAAS,kBAAkB,MAAgD;AAGhF,kBAAgB;AAGhB,SAAO,WAAW;AACpB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/runtime-async",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "files": [
5
5
  "dist/**"
6
6
  ],
@@ -26,7 +26,7 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "@sdeverywhere/runtime": "^0.2.0",
29
+ "@sdeverywhere/runtime": "^0.2.2",
30
30
  "threads": "1.7.0"
31
31
  },
32
32
  "author": "Climate Interactive",