@sdeverywhere/runtime-async 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(src_exports);
26
26
 
27
27
  // src/runner.ts
28
28
  var import_threads = require("threads");
29
+ var import_runtime = require("@sdeverywhere/runtime");
29
30
  async function spawnAsyncModelRunner(workerSpec) {
30
31
  if (workerSpec["path"]) {
31
32
  return spawnAsyncModelRunnerWithWorker(new import_threads.Worker(workerSpec["path"]));
@@ -36,11 +37,14 @@ async function spawnAsyncModelRunner(workerSpec) {
36
37
  async function spawnAsyncModelRunnerWithWorker(worker) {
37
38
  const modelWorker2 = await (0, import_threads.spawn)(worker);
38
39
  const initResult = await modelWorker2.initModel();
39
- const rowLength = initResult.rowLength;
40
40
  let ioBuffer = initResult.ioBuffer;
41
+ const rowLength = initResult.rowLength;
41
42
  let running = false;
42
43
  let terminated = false;
43
44
  return {
45
+ createOutputs: () => {
46
+ return new import_runtime.Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq);
47
+ },
44
48
  runModel: async (inputs, outputs) => {
45
49
  if (terminated) {
46
50
  throw new Error("Async model runner has already been terminated");
@@ -82,7 +86,7 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
82
86
 
83
87
  // src/worker.ts
84
88
  var import_worker = require("threads/worker");
85
- var import_runtime = require("@sdeverywhere/runtime");
89
+ var import_runtime2 = require("@sdeverywhere/runtime");
86
90
  var initWasmModel;
87
91
  var wasmModel;
88
92
  var inputsWasmBuffer;
@@ -92,17 +96,24 @@ var modelWorker = {
92
96
  if (wasmModel) {
93
97
  throw new Error("WasmModel was already initialized");
94
98
  }
95
- const result = await initWasmModel();
96
- wasmModel = result.model;
97
- inputsWasmBuffer = result.inputsBuffer;
98
- outputsWasmBuffer = result.outputsBuffer;
99
+ const wasmResult = await initWasmModel();
100
+ wasmModel = wasmResult.model;
101
+ inputsWasmBuffer = wasmResult.inputsBuffer;
102
+ outputsWasmBuffer = wasmResult.outputsBuffer;
99
103
  const runTimeLength = 8;
100
104
  const inputsLength = inputsWasmBuffer.getArrayView().length;
101
105
  const outputsLength = outputsWasmBuffer.getArrayView().length;
102
106
  const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength);
103
- const rowLength = result.endTime - result.startTime + 1;
104
107
  const ioBuffer = ioArray.buffer;
105
- return (0, import_worker.Transfer)({ rowLength, ioBuffer }, [ioBuffer]);
108
+ const initResult = {
109
+ outputVarIds: wasmResult.outputVarIds,
110
+ startTime: wasmModel.startTime,
111
+ endTime: wasmModel.endTime,
112
+ saveFreq: wasmModel.saveFreq,
113
+ rowLength: wasmModel.numSavePoints,
114
+ ioBuffer
115
+ };
116
+ return (0, import_worker.Transfer)(initResult, [ioBuffer]);
106
117
  },
107
118
  runModel(ioBuffer) {
108
119
  if (!wasmModel) {
@@ -115,9 +126,9 @@ var modelWorker = {
115
126
  const inputsLengthInBytes = inputsLengthInElements * 8;
116
127
  const inputsBufferArray = new Float64Array(ioBuffer, runTimeLengthInBytes, inputsLengthInElements);
117
128
  inputsWasmArray.set(inputsBufferArray);
118
- const t0 = (0, import_runtime.perfNow)();
129
+ const t0 = (0, import_runtime2.perfNow)();
119
130
  wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer);
120
- const elapsed = (0, import_runtime.perfElapsed)(t0);
131
+ const elapsed = (0, import_runtime2.perfElapsed)(t0);
121
132
  const runTimeBufferArray = new Float64Array(ioBuffer, 0, runTimeLengthInElements);
122
133
  runTimeBufferArray[0] = elapsed;
123
134
  const outputsOffsetInBytes = runTimeLengthInBytes + inputsLengthInBytes;
@@ -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'\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 const rowLength: number = initResult.rowLength\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\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 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 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 result = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = result.model\n inputsWasmBuffer = result.inputsBuffer\n outputsWasmBuffer = result.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 // The row length is the number of elements in each row of the outputs buffer\n const rowLength = result.endTime - result.startTime + 1\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n return Transfer({ rowLength, ioBuffer }, [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":[]}
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"]}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/runner.ts
2
2
  import { BlobWorker, spawn, Thread, Transfer, Worker } from "threads";
3
+ import { Outputs } from "@sdeverywhere/runtime";
3
4
  async function spawnAsyncModelRunner(workerSpec) {
4
5
  if (workerSpec["path"]) {
5
6
  return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec["path"]));
@@ -10,11 +11,14 @@ async function spawnAsyncModelRunner(workerSpec) {
10
11
  async function spawnAsyncModelRunnerWithWorker(worker) {
11
12
  const modelWorker2 = await spawn(worker);
12
13
  const initResult = await modelWorker2.initModel();
13
- const rowLength = initResult.rowLength;
14
14
  let ioBuffer = initResult.ioBuffer;
15
+ const rowLength = initResult.rowLength;
15
16
  let running = false;
16
17
  let terminated = false;
17
18
  return {
19
+ createOutputs: () => {
20
+ return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq);
21
+ },
18
22
  runModel: async (inputs, outputs) => {
19
23
  if (terminated) {
20
24
  throw new Error("Async model runner has already been terminated");
@@ -66,17 +70,24 @@ var modelWorker = {
66
70
  if (wasmModel) {
67
71
  throw new Error("WasmModel was already initialized");
68
72
  }
69
- const result = await initWasmModel();
70
- wasmModel = result.model;
71
- inputsWasmBuffer = result.inputsBuffer;
72
- outputsWasmBuffer = result.outputsBuffer;
73
+ const wasmResult = await initWasmModel();
74
+ wasmModel = wasmResult.model;
75
+ inputsWasmBuffer = wasmResult.inputsBuffer;
76
+ outputsWasmBuffer = wasmResult.outputsBuffer;
73
77
  const runTimeLength = 8;
74
78
  const inputsLength = inputsWasmBuffer.getArrayView().length;
75
79
  const outputsLength = outputsWasmBuffer.getArrayView().length;
76
80
  const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength);
77
- const rowLength = result.endTime - result.startTime + 1;
78
81
  const ioBuffer = ioArray.buffer;
79
- return Transfer2({ rowLength, ioBuffer }, [ioBuffer]);
82
+ const initResult = {
83
+ outputVarIds: wasmResult.outputVarIds,
84
+ startTime: wasmModel.startTime,
85
+ endTime: wasmModel.endTime,
86
+ saveFreq: wasmModel.saveFreq,
87
+ rowLength: wasmModel.numSavePoints,
88
+ ioBuffer
89
+ };
90
+ return Transfer2(initResult, [ioBuffer]);
80
91
  },
81
92
  runModel(ioBuffer) {
82
93
  if (!wasmModel) {
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'\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 const rowLength: number = initResult.rowLength\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\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 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 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 result = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = result.model\n inputsWasmBuffer = result.inputsBuffer\n outputsWasmBuffer = result.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 // The row length is the number of elements in each row of the outputs buffer\n const rowLength = result.endTime - result.startTime + 1\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n return Transfer({ rowLength, ioBuffer }, [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":[]}
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"]}
package/dist/runner.cjs CHANGED
@@ -23,6 +23,7 @@ __export(runner_exports, {
23
23
  });
24
24
  module.exports = __toCommonJS(runner_exports);
25
25
  var import_threads = require("threads");
26
+ var import_runtime = require("@sdeverywhere/runtime");
26
27
  async function spawnAsyncModelRunner(workerSpec) {
27
28
  if (workerSpec["path"]) {
28
29
  return spawnAsyncModelRunnerWithWorker(new import_threads.Worker(workerSpec["path"]));
@@ -33,11 +34,14 @@ async function spawnAsyncModelRunner(workerSpec) {
33
34
  async function spawnAsyncModelRunnerWithWorker(worker) {
34
35
  const modelWorker = await (0, import_threads.spawn)(worker);
35
36
  const initResult = await modelWorker.initModel();
36
- const rowLength = initResult.rowLength;
37
37
  let ioBuffer = initResult.ioBuffer;
38
+ const rowLength = initResult.rowLength;
38
39
  let running = false;
39
40
  let terminated = false;
40
41
  return {
42
+ createOutputs: () => {
43
+ return new import_runtime.Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq);
44
+ },
41
45
  runModel: async (inputs, outputs) => {
42
46
  if (terminated) {
43
47
  throw new Error("Async model runner has already been terminated");
@@ -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'\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 const rowLength: number = initResult.rowLength\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\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 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;AAuC5D,qCAA4C,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,+CAA+C,QAAsC;AAEnF,QAAM,cAAc,MAAM,0BAAM,MAAM;AAGtC,QAAM,aAAa,MAAM,YAAY,UAAU;AAC/C,QAAM,YAAoB,WAAW;AACrC,MAAI,WAAwB,WAAW;AAGvC,MAAI,UAAU;AAGd,MAAI,aAAa;AAEjB,SAAO;AAAA,IACL,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,6BAAS,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 } 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":[]}
package/dist/runner.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/runner.ts
2
2
  import { BlobWorker, spawn, Thread, Transfer, Worker } from "threads";
3
+ import { Outputs } from "@sdeverywhere/runtime";
3
4
  async function spawnAsyncModelRunner(workerSpec) {
4
5
  if (workerSpec["path"]) {
5
6
  return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec["path"]));
@@ -10,11 +11,14 @@ async function spawnAsyncModelRunner(workerSpec) {
10
11
  async function spawnAsyncModelRunnerWithWorker(worker) {
11
12
  const modelWorker = await spawn(worker);
12
13
  const initResult = await modelWorker.initModel();
13
- const rowLength = initResult.rowLength;
14
14
  let ioBuffer = initResult.ioBuffer;
15
+ const rowLength = initResult.rowLength;
15
16
  let running = false;
16
17
  let terminated = false;
17
18
  return {
19
+ createOutputs: () => {
20
+ return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq);
21
+ },
18
22
  runModel: async (inputs, outputs) => {
19
23
  if (terminated) {
20
24
  throw new Error("Async model runner has already been terminated");
@@ -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'\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 const rowLength: number = initResult.rowLength\n let ioBuffer: ArrayBuffer = initResult.ioBuffer\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 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;AAuCA,qCAA4C,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,+CAA+C,QAAsC;AAEnF,QAAM,cAAc,MAAM,MAAM,MAAM;AAGtC,QAAM,aAAa,MAAM,YAAY,UAAU;AAC/C,QAAM,YAAoB,WAAW;AACrC,MAAI,WAAwB,WAAW;AAGvC,MAAI,UAAU;AAGd,MAAI,aAAa;AAEjB,SAAO;AAAA,IACL,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 } 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":[]}
package/dist/worker.cjs CHANGED
@@ -33,17 +33,24 @@ var modelWorker = {
33
33
  if (wasmModel) {
34
34
  throw new Error("WasmModel was already initialized");
35
35
  }
36
- const result = await initWasmModel();
37
- wasmModel = result.model;
38
- inputsWasmBuffer = result.inputsBuffer;
39
- outputsWasmBuffer = result.outputsBuffer;
36
+ const wasmResult = await initWasmModel();
37
+ wasmModel = wasmResult.model;
38
+ inputsWasmBuffer = wasmResult.inputsBuffer;
39
+ outputsWasmBuffer = wasmResult.outputsBuffer;
40
40
  const runTimeLength = 8;
41
41
  const inputsLength = inputsWasmBuffer.getArrayView().length;
42
42
  const outputsLength = outputsWasmBuffer.getArrayView().length;
43
43
  const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength);
44
- const rowLength = result.endTime - result.startTime + 1;
45
44
  const ioBuffer = ioArray.buffer;
46
- return (0, import_worker.Transfer)({ rowLength, ioBuffer }, [ioBuffer]);
45
+ const initResult = {
46
+ outputVarIds: wasmResult.outputVarIds,
47
+ startTime: wasmModel.startTime,
48
+ endTime: wasmModel.endTime,
49
+ saveFreq: wasmModel.saveFreq,
50
+ rowLength: wasmModel.numSavePoints,
51
+ ioBuffer
52
+ };
53
+ return (0, import_worker.Transfer)(initResult, [ioBuffer]);
47
54
  },
48
55
  runModel(ioBuffer) {
49
56
  if (!wasmModel) {
@@ -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 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 result = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = result.model\n inputsWasmBuffer = result.inputsBuffer\n outputsWasmBuffer = result.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 // The row length is the number of elements in each row of the outputs buffer\n const rowLength = result.endTime - result.startTime + 1\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n return Transfer({ rowLength, ioBuffer }, [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;AAQJ,IAAM,cAAc;AAAA,EAClB,MAAM,YAAqD;AACzD,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAGA,UAAM,SAAS,MAAM,cAAc;AAGnC,gBAAY,OAAO;AACnB,uBAAmB,OAAO;AAC1B,wBAAoB,OAAO;AAK3B,UAAM,gBAAgB;AACtB,UAAM,eAAe,iBAAiB,aAAa,EAAE;AACrD,UAAM,gBAAgB,kBAAkB,aAAa,EAAE;AACvD,UAAM,UAAU,IAAI,aAAa,gBAAgB,eAAe,aAAa;AAG7E,UAAM,YAAY,OAAO,UAAU,OAAO,YAAY;AAGtD,UAAM,WAAW,QAAQ;AACzB,WAAO,4BAAS,EAAE,WAAW,SAAS,GAAG,CAAC,QAAQ,CAAC;AAAA,EACrD;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,4BAAQ;AACnB,cAAU,SAAS,kBAAkB,iBAAiB;AACtD,UAAM,UAAU,gCAAY,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,4BAAS,QAAQ;AAAA,EAC1B;AACF;AAWO,2BAA2B,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\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":[]}
package/dist/worker.js CHANGED
@@ -10,17 +10,24 @@ var modelWorker = {
10
10
  if (wasmModel) {
11
11
  throw new Error("WasmModel was already initialized");
12
12
  }
13
- const result = await initWasmModel();
14
- wasmModel = result.model;
15
- inputsWasmBuffer = result.inputsBuffer;
16
- outputsWasmBuffer = result.outputsBuffer;
13
+ const wasmResult = await initWasmModel();
14
+ wasmModel = wasmResult.model;
15
+ inputsWasmBuffer = wasmResult.inputsBuffer;
16
+ outputsWasmBuffer = wasmResult.outputsBuffer;
17
17
  const runTimeLength = 8;
18
18
  const inputsLength = inputsWasmBuffer.getArrayView().length;
19
19
  const outputsLength = outputsWasmBuffer.getArrayView().length;
20
20
  const ioArray = new Float64Array(runTimeLength + inputsLength + outputsLength);
21
- const rowLength = result.endTime - result.startTime + 1;
22
21
  const ioBuffer = ioArray.buffer;
23
- return Transfer({ rowLength, ioBuffer }, [ioBuffer]);
22
+ const initResult = {
23
+ outputVarIds: wasmResult.outputVarIds,
24
+ startTime: wasmModel.startTime,
25
+ endTime: wasmModel.endTime,
26
+ saveFreq: wasmModel.saveFreq,
27
+ rowLength: wasmModel.numSavePoints,
28
+ ioBuffer
29
+ };
30
+ return Transfer(initResult, [ioBuffer]);
24
31
  },
25
32
  runModel(ioBuffer) {
26
33
  if (!wasmModel) {
@@ -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 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 result = await initWasmModel()\n\n // Capture the `WasmModel` instance and `WasmBuffer` instances\n wasmModel = result.model\n inputsWasmBuffer = result.inputsBuffer\n outputsWasmBuffer = result.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 // The row length is the number of elements in each row of the outputs buffer\n const rowLength = result.endTime - result.startTime + 1\n\n // Transfer the underlying buffer to the runner\n const ioBuffer = ioArray.buffer\n return Transfer({ rowLength, ioBuffer }, [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;AAGA;AAGA,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAEJ,IAAI;AAQJ,IAAM,cAAc;AAAA,EAClB,MAAM,YAAqD;AACzD,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAGA,UAAM,SAAS,MAAM,cAAc;AAGnC,gBAAY,OAAO;AACnB,uBAAmB,OAAO;AAC1B,wBAAoB,OAAO;AAK3B,UAAM,gBAAgB;AACtB,UAAM,eAAe,iBAAiB,aAAa,EAAE;AACrD,UAAM,gBAAgB,kBAAkB,aAAa,EAAE;AACvD,UAAM,UAAU,IAAI,aAAa,gBAAgB,eAAe,aAAa;AAG7E,UAAM,YAAY,OAAO,UAAU,OAAO,YAAY;AAGtD,UAAM,WAAW,QAAQ;AACzB,WAAO,SAAS,EAAE,WAAW,SAAS,GAAG,CAAC,QAAQ,CAAC;AAAA,EACrD;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,2BAA2B,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\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/runtime-async",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "files": [
5
5
  "dist/**"
6
6
  ],
@@ -26,7 +26,7 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "@sdeverywhere/runtime": "^0.1.0",
29
+ "@sdeverywhere/runtime": "^0.2.0",
30
30
  "threads": "1.7.0"
31
31
  },
32
32
  "author": "Climate Interactive",