@sdeverywhere/runtime-async 0.2.2 → 0.2.3

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
@@ -27,22 +27,17 @@ _NOTE:_ If you followed the "Quick Start" instructions and/or used the
27
27
  steps listed below are already implemented for you in the generated `core` package,
28
28
  and you can work directly with a `ModelRunner` and/or `ModelScheduler` instance.
29
29
 
30
- ### 1. Initialize the `WasmModel` in a Web Worker
30
+ ### 1. Initialize your generated model in a Web Worker
31
31
 
32
32
  In your app project, define a separate JavaScript file, called
33
- `worker.js` for example, that initializes the model worker in the
33
+ `worker.js` for example, that initializes the generated model in the
34
34
  context of the Web Worker:
35
35
 
36
36
  ```js
37
- import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'
38
37
  import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'
38
+ import loadGeneratedModel from './sde-prep/generated-model.js'
39
39
 
40
- async function initWasmModel() {
41
- const wasmModules = loadWasm()
42
- return initWasmModelAndBuffers(...)
43
- }
44
-
45
- exposeModelWorker(initWasmModel)
40
+ exposeModelWorker(loadGeneratedModel)
46
41
  ```
47
42
 
48
43
  ### 2. Spawn the worker
package/dist/index.cjs CHANGED
@@ -37,26 +37,15 @@ async function spawnAsyncModelRunner(workerSpec) {
37
37
  async function spawnAsyncModelRunnerWithWorker(worker) {
38
38
  const modelWorker2 = await (0, import_threads.spawn)(worker);
39
39
  const initResult = await modelWorker2.initModel();
40
- let ioBuffer = initResult.ioBuffer;
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;
40
+ const modelListing = initResult.modelListing ? new import_runtime.ModelListing(initResult.modelListing) : void 0;
41
+ const params2 = new import_runtime.BufferedRunModelParams(modelListing);
53
42
  let running = false;
54
43
  let terminated = false;
55
44
  return {
56
45
  createOutputs: () => {
57
46
  return new import_runtime.Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq);
58
47
  },
59
- runModel: async (inputs, outputs) => {
48
+ runModel: async (inputs, outputs, options) => {
60
49
  if (terminated) {
61
50
  throw new Error("Async model runner has already been terminated");
62
51
  } else if (running) {
@@ -64,24 +53,15 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
64
53
  } else {
65
54
  running = true;
66
55
  }
67
- const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
68
- for (let i = 0; i < inputs.length; i++) {
69
- inputsArray[i] = inputs[i].get();
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
- }
56
+ params2.updateFromParams(inputs, outputs, options);
57
+ let ioBuffer;
76
58
  try {
77
- ioBuffer = await modelWorker2.runModel((0, import_threads.Transfer)(ioBuffer));
59
+ ioBuffer = await modelWorker2.runModel((0, import_threads.Transfer)(params2.getEncodedBuffer()));
78
60
  } finally {
79
61
  running = false;
80
62
  }
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);
63
+ params2.updateFromEncodedBuffer(ioBuffer);
64
+ params2.finalizeOutputs(outputs);
85
65
  return outputs;
86
66
  },
87
67
  terminate: () => {
@@ -98,81 +78,36 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
98
78
  // src/worker.ts
99
79
  var import_worker = require("threads/worker");
100
80
  var import_runtime2 = require("@sdeverywhere/runtime");
101
- var initWasmModel;
102
- var wasmModel;
103
- var inputsWasmBuffer;
104
- var outputsWasmBuffer;
105
- var outputIndicesWasmBuffer;
81
+ var initGeneratedModel;
82
+ var runnableModel;
83
+ var params = new import_runtime2.BufferedRunModelParams();
106
84
  var modelWorker = {
107
85
  async initModel() {
108
- if (wasmModel) {
109
- throw new Error("WasmModel was already initialized");
86
+ if (runnableModel) {
87
+ throw new Error("RunnableModel was already initialized");
110
88
  }
111
- const wasmResult = await initWasmModel();
112
- wasmModel = wasmResult.model;
113
- inputsWasmBuffer = wasmResult.inputsBuffer;
114
- outputsWasmBuffer = wasmResult.outputsBuffer;
115
- outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer;
116
- const runTimeLength = 8;
117
- const inputsLength = inputsWasmBuffer.getArrayView().length;
118
- const outputsLength = outputsWasmBuffer.getArrayView().length;
119
- const outputIndicesLength = (outputIndicesWasmBuffer == null ? void 0 : outputIndicesWasmBuffer.getArrayView().length) || 0;
120
- const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength;
121
- const ioArray = new Float64Array(totalLength);
122
- const ioBuffer = ioArray.buffer;
123
- const initResult = {
124
- outputVarIds: wasmResult.outputVarIds,
125
- startTime: wasmModel.startTime,
126
- endTime: wasmModel.endTime,
127
- saveFreq: wasmModel.saveFreq,
128
- inputsLength,
129
- outputsLength,
130
- outputIndicesLength,
131
- outputRowLength: wasmModel.numSavePoints,
132
- ioBuffer
89
+ const generatedModel = await initGeneratedModel();
90
+ runnableModel = (0, import_runtime2.createRunnableModel)(generatedModel);
91
+ return {
92
+ outputVarIds: runnableModel.outputVarIds,
93
+ modelListing: runnableModel.modelListing,
94
+ startTime: runnableModel.startTime,
95
+ endTime: runnableModel.endTime,
96
+ saveFreq: runnableModel.saveFreq,
97
+ outputRowLength: runnableModel.numSavePoints
133
98
  };
134
- return (0, import_worker.Transfer)(initResult, [ioBuffer]);
135
99
  },
136
100
  runModel(ioBuffer) {
137
- if (!wasmModel) {
138
- throw new Error("WasmModel must be initialized before running the model in worker");
139
- }
140
- const runTimeOffsetInBytes = 0;
141
- const runTimeLengthInElements = 1;
142
- const runTimeLengthInBytes = runTimeLengthInElements * 8;
143
- const inputsWasmArray = inputsWasmBuffer.getArrayView();
144
- const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
145
- const inputsLengthInElements = inputsWasmArray.length;
146
- const inputsLengthInBytes = inputsWasmArray.byteLength;
147
- const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
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
- }
101
+ if (!runnableModel) {
102
+ throw new Error("RunnableModel must be initialized before running the model in worker");
162
103
  }
163
- const t0 = (0, import_runtime2.perfNow)();
164
- wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : void 0);
165
- const elapsed = (0, import_runtime2.perfElapsed)(t0);
166
- const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
167
- runTimeBufferArray[0] = elapsed;
168
- const outputsLengthInElements = outputsWasmArray.length;
169
- const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
170
- outputsBufferArray.set(outputsWasmArray);
104
+ params.updateFromEncodedBuffer(ioBuffer);
105
+ runnableModel.runModel(params);
171
106
  return (0, import_worker.Transfer)(ioBuffer);
172
107
  }
173
108
  };
174
109
  function exposeModelWorker(init) {
175
- initWasmModel = init;
110
+ initGeneratedModel = init;
176
111
  (0, import_worker.expose)(modelWorker);
177
112
  }
178
113
  // Annotate the CommonJS export names for ESM import in node:
@@ -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, 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"]}
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 { BufferedRunModelParams, ModelListing, Outputs } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker\n * (a Web Worker when running in a browser environment, or a worker thread\n * when running in a Node.js environment).\n *\n * In your app project, define a JavaScript file, called `worker.js` for example,\n * that initializes the generated model in the context of a worker thread:\n *\n * ```js\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n * import loadGeneratedModel from './sde-prep/generated-model.js'\n *\n * exposeModelWorker(loadGeneratedModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the worker thread 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\n // Create a `ModelListing` instance if the listing was defined in the generated model\n const modelListing = initResult.modelListing ? new ModelListing(initResult.modelListing) : undefined\n\n // Maintain a `BufferedRunModelParams` instance that holds the I/O parameters\n const params = new BufferedRunModelParams(modelListing)\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, options) => {\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 // Update the I/O parameters\n params.updateFromParams(inputs, outputs, options)\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 let ioBuffer: ArrayBuffer\n try {\n ioBuffer = await modelWorker.runModel(Transfer(params.getEncodedBuffer()))\n } finally {\n running = false\n }\n\n // Once the buffer is transferred to the worker, the buffer in the\n // `BufferedRunModelParams` becomes \"detached\" and is no longer usable.\n // After the buffer is transferred back from the worker, we need to\n // restore the state of the object to use the new buffer.\n params.updateFromEncodedBuffer(ioBuffer)\n\n // Copy the output values and elapsed time from the buffer to the\n // `Outputs` instance\n params.finalizeOutputs(outputs)\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 { GeneratedModel, RunnableModel } from '@sdeverywhere/runtime'\nimport { BufferedRunModelParams, createRunnableModel } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initGeneratedModel: () => Promise<GeneratedModel>\n\n/** @hidden */\nlet runnableModel: RunnableModel\n\n/**\n * Maintain a `BufferedRunModelParams` instance that wraps the transferable buffer\n * containing the I/O parameters.\n * @hidden\n */\nconst params = new BufferedRunModelParams()\n\ninterface InitResult {\n outputVarIds: string[]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n modelListing?: /*ModelListingSpecs*/ any\n startTime: number\n endTime: number\n saveFreq: number\n outputRowLength: number\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<InitResult> {\n if (runnableModel) {\n throw new Error('RunnableModel was already initialized')\n }\n\n // Initialize the runnable model\n const generatedModel = await initGeneratedModel()\n runnableModel = createRunnableModel(generatedModel)\n\n // Transfer the model metadata to the runner\n return {\n outputVarIds: runnableModel.outputVarIds,\n modelListing: runnableModel.modelListing,\n startTime: runnableModel.startTime,\n endTime: runnableModel.endTime,\n saveFreq: runnableModel.saveFreq,\n outputRowLength: runnableModel.numSavePoints\n }\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!runnableModel) {\n throw new Error('RunnableModel must be initialized before running the model in worker')\n }\n\n // Update the `BufferedRunModelParams` to use the values in the buffer that was transferred\n // from the runner to the worker\n params.updateFromEncodedBuffer(ioBuffer)\n\n // Run the model synchronously on the worker thread using those I/O parameters\n runnableModel.runModel(params)\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 model on the worker thread and\n * sending the outputs back to the main thread.\n *\n * @param init The function that initializes the generated model instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<GeneratedModel>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initGeneratedModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,qBAA4D;AAG5D,qBAA8D;AAkC9D,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,QAAMA,eAAc,UAAM,sBAAM,MAAM;AAGtC,QAAM,aAAa,MAAMA,aAAY,UAAU;AAG/C,QAAM,eAAe,WAAW,eAAe,IAAI,4BAAa,WAAW,YAAY,IAAI;AAG3F,QAAMC,UAAS,IAAI,sCAAuB,YAAY;AAGtD,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,SAAS,YAAY;AAC5C,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE,WAAW,SAAS;AAClB,cAAM,IAAI,MAAM,gEAAgE;AAAA,MAClF,OAAO;AACL,kBAAU;AAAA,MACZ;AAGA,MAAAA,QAAO,iBAAiB,QAAQ,SAAS,OAAO;AAMhD,UAAI;AACJ,UAAI;AACF,mBAAW,MAAMD,aAAY,aAAS,yBAASC,QAAO,iBAAiB,CAAC,CAAC;AAAA,MAC3E,UAAE;AACA,kBAAU;AAAA,MACZ;AAMA,MAAAA,QAAO,wBAAwB,QAAQ;AAIvC,MAAAA,QAAO,gBAAgB,OAAO;AAE9B,aAAO;AAAA,IACT;AAAA,IAEA,WAAW,MAAM;AACf,UAAI,YAAY;AACd,eAAO,QAAQ,QAAQ;AAAA,MACzB,OAAO;AACL,qBAAa;AACb,eAAO,sBAAO,UAAUD,YAAW;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;;;ACpHA,oBAAiC;AAGjC,IAAAE,kBAA4D;AAG5D,IAAI;AAGJ,IAAI;AAOJ,IAAM,SAAS,IAAI,uCAAuB;AAa1C,IAAM,cAAc;AAAA,EAClB,MAAM,YAAiC;AACrC,QAAI,eAAe;AACjB,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAGA,UAAM,iBAAiB,MAAM,mBAAmB;AAChD,wBAAgB,qCAAoB,cAAc;AAGlD,WAAO;AAAA,MACL,cAAc,cAAc;AAAA,MAC5B,cAAc,cAAc;AAAA,MAC5B,WAAW,cAAc;AAAA,MACzB,SAAS,cAAc;AAAA,MACvB,UAAU,cAAc;AAAA,MACxB,iBAAiB,cAAc;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,SAAS,UAAwD;AAC/D,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,sEAAsE;AAAA,IACxF;AAIA,WAAO,wBAAwB,QAAQ;AAGvC,kBAAc,SAAS,MAAM;AAG7B,eAAO,wBAAS,QAAQ;AAAA,EAC1B;AACF;AAWO,SAAS,kBAAkB,MAA2C;AAG3E,uBAAqB;AAGrB,4BAAO,WAAW;AACpB;","names":["modelWorker","params","import_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, updateOutputIndices } from "@sdeverywhere/runtime";
3
+ import { BufferedRunModelParams, ModelListing, Outputs } from "@sdeverywhere/runtime";
4
4
  async function spawnAsyncModelRunner(workerSpec) {
5
5
  if (workerSpec["path"]) {
6
6
  return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec["path"]));
@@ -11,26 +11,15 @@ async function spawnAsyncModelRunner(workerSpec) {
11
11
  async function spawnAsyncModelRunnerWithWorker(worker) {
12
12
  const modelWorker2 = await spawn(worker);
13
13
  const initResult = await modelWorker2.initModel();
14
- let ioBuffer = initResult.ioBuffer;
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;
14
+ const modelListing = initResult.modelListing ? new ModelListing(initResult.modelListing) : void 0;
15
+ const params2 = new BufferedRunModelParams(modelListing);
27
16
  let running = false;
28
17
  let terminated = false;
29
18
  return {
30
19
  createOutputs: () => {
31
20
  return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq);
32
21
  },
33
- runModel: async (inputs, outputs) => {
22
+ runModel: async (inputs, outputs, options) => {
34
23
  if (terminated) {
35
24
  throw new Error("Async model runner has already been terminated");
36
25
  } else if (running) {
@@ -38,24 +27,15 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
38
27
  } else {
39
28
  running = true;
40
29
  }
41
- const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
42
- for (let i = 0; i < inputs.length; i++) {
43
- inputsArray[i] = inputs[i].get();
44
- }
45
- if (indicesLengthInElements > 0) {
46
- const outputSpecs = outputs.varSpecs || [];
47
- const indicesArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements);
48
- updateOutputIndices(indicesArray, outputSpecs);
49
- }
30
+ params2.updateFromParams(inputs, outputs, options);
31
+ let ioBuffer;
50
32
  try {
51
- ioBuffer = await modelWorker2.runModel(Transfer(ioBuffer));
33
+ ioBuffer = await modelWorker2.runModel(Transfer(params2.getEncodedBuffer()));
52
34
  } finally {
53
35
  running = false;
54
36
  }
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);
37
+ params2.updateFromEncodedBuffer(ioBuffer);
38
+ params2.finalizeOutputs(outputs);
59
39
  return outputs;
60
40
  },
61
41
  terminate: () => {
@@ -71,82 +51,37 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
71
51
 
72
52
  // src/worker.ts
73
53
  import { expose, Transfer as Transfer2 } from "threads/worker";
74
- import { perfElapsed, perfNow } from "@sdeverywhere/runtime";
75
- var initWasmModel;
76
- var wasmModel;
77
- var inputsWasmBuffer;
78
- var outputsWasmBuffer;
79
- var outputIndicesWasmBuffer;
54
+ import { BufferedRunModelParams as BufferedRunModelParams2, createRunnableModel } from "@sdeverywhere/runtime";
55
+ var initGeneratedModel;
56
+ var runnableModel;
57
+ var params = new BufferedRunModelParams2();
80
58
  var modelWorker = {
81
59
  async initModel() {
82
- if (wasmModel) {
83
- throw new Error("WasmModel was already initialized");
60
+ if (runnableModel) {
61
+ throw new Error("RunnableModel was already initialized");
84
62
  }
85
- const wasmResult = await initWasmModel();
86
- wasmModel = wasmResult.model;
87
- inputsWasmBuffer = wasmResult.inputsBuffer;
88
- outputsWasmBuffer = wasmResult.outputsBuffer;
89
- outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer;
90
- const runTimeLength = 8;
91
- const inputsLength = inputsWasmBuffer.getArrayView().length;
92
- const outputsLength = outputsWasmBuffer.getArrayView().length;
93
- const outputIndicesLength = (outputIndicesWasmBuffer == null ? void 0 : outputIndicesWasmBuffer.getArrayView().length) || 0;
94
- const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength;
95
- const ioArray = new Float64Array(totalLength);
96
- const ioBuffer = ioArray.buffer;
97
- const initResult = {
98
- outputVarIds: wasmResult.outputVarIds,
99
- startTime: wasmModel.startTime,
100
- endTime: wasmModel.endTime,
101
- saveFreq: wasmModel.saveFreq,
102
- inputsLength,
103
- outputsLength,
104
- outputIndicesLength,
105
- outputRowLength: wasmModel.numSavePoints,
106
- ioBuffer
63
+ const generatedModel = await initGeneratedModel();
64
+ runnableModel = createRunnableModel(generatedModel);
65
+ return {
66
+ outputVarIds: runnableModel.outputVarIds,
67
+ modelListing: runnableModel.modelListing,
68
+ startTime: runnableModel.startTime,
69
+ endTime: runnableModel.endTime,
70
+ saveFreq: runnableModel.saveFreq,
71
+ outputRowLength: runnableModel.numSavePoints
107
72
  };
108
- return Transfer2(initResult, [ioBuffer]);
109
73
  },
110
74
  runModel(ioBuffer) {
111
- if (!wasmModel) {
112
- throw new Error("WasmModel must be initialized before running the model in worker");
113
- }
114
- const runTimeOffsetInBytes = 0;
115
- const runTimeLengthInElements = 1;
116
- const runTimeLengthInBytes = runTimeLengthInElements * 8;
117
- const inputsWasmArray = inputsWasmBuffer.getArrayView();
118
- const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
119
- const inputsLengthInElements = inputsWasmArray.length;
120
- const inputsLengthInBytes = inputsWasmArray.byteLength;
121
- const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
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
- }
75
+ if (!runnableModel) {
76
+ throw new Error("RunnableModel must be initialized before running the model in worker");
136
77
  }
137
- const t0 = perfNow();
138
- wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : void 0);
139
- const elapsed = perfElapsed(t0);
140
- const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
141
- runTimeBufferArray[0] = elapsed;
142
- const outputsLengthInElements = outputsWasmArray.length;
143
- const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
144
- outputsBufferArray.set(outputsWasmArray);
78
+ params.updateFromEncodedBuffer(ioBuffer);
79
+ runnableModel.runModel(params);
145
80
  return Transfer2(ioBuffer);
146
81
  }
147
82
  };
148
83
  function exposeModelWorker(init) {
149
- initWasmModel = init;
84
+ initGeneratedModel = init;
150
85
  expose(modelWorker);
151
86
  }
152
87
  export {
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, 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","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 { BufferedRunModelParams, ModelListing, Outputs } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker\n * (a Web Worker when running in a browser environment, or a worker thread\n * when running in a Node.js environment).\n *\n * In your app project, define a JavaScript file, called `worker.js` for example,\n * that initializes the generated model in the context of a worker thread:\n *\n * ```js\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n * import loadGeneratedModel from './sde-prep/generated-model.js'\n *\n * exposeModelWorker(loadGeneratedModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the worker thread 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\n // Create a `ModelListing` instance if the listing was defined in the generated model\n const modelListing = initResult.modelListing ? new ModelListing(initResult.modelListing) : undefined\n\n // Maintain a `BufferedRunModelParams` instance that holds the I/O parameters\n const params = new BufferedRunModelParams(modelListing)\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, options) => {\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 // Update the I/O parameters\n params.updateFromParams(inputs, outputs, options)\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 let ioBuffer: ArrayBuffer\n try {\n ioBuffer = await modelWorker.runModel(Transfer(params.getEncodedBuffer()))\n } finally {\n running = false\n }\n\n // Once the buffer is transferred to the worker, the buffer in the\n // `BufferedRunModelParams` becomes \"detached\" and is no longer usable.\n // After the buffer is transferred back from the worker, we need to\n // restore the state of the object to use the new buffer.\n params.updateFromEncodedBuffer(ioBuffer)\n\n // Copy the output values and elapsed time from the buffer to the\n // `Outputs` instance\n params.finalizeOutputs(outputs)\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 { GeneratedModel, RunnableModel } from '@sdeverywhere/runtime'\nimport { BufferedRunModelParams, createRunnableModel } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initGeneratedModel: () => Promise<GeneratedModel>\n\n/** @hidden */\nlet runnableModel: RunnableModel\n\n/**\n * Maintain a `BufferedRunModelParams` instance that wraps the transferable buffer\n * containing the I/O parameters.\n * @hidden\n */\nconst params = new BufferedRunModelParams()\n\ninterface InitResult {\n outputVarIds: string[]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n modelListing?: /*ModelListingSpecs*/ any\n startTime: number\n endTime: number\n saveFreq: number\n outputRowLength: number\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<InitResult> {\n if (runnableModel) {\n throw new Error('RunnableModel was already initialized')\n }\n\n // Initialize the runnable model\n const generatedModel = await initGeneratedModel()\n runnableModel = createRunnableModel(generatedModel)\n\n // Transfer the model metadata to the runner\n return {\n outputVarIds: runnableModel.outputVarIds,\n modelListing: runnableModel.modelListing,\n startTime: runnableModel.startTime,\n endTime: runnableModel.endTime,\n saveFreq: runnableModel.saveFreq,\n outputRowLength: runnableModel.numSavePoints\n }\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!runnableModel) {\n throw new Error('RunnableModel must be initialized before running the model in worker')\n }\n\n // Update the `BufferedRunModelParams` to use the values in the buffer that was transferred\n // from the runner to the worker\n params.updateFromEncodedBuffer(ioBuffer)\n\n // Run the model synchronously on the worker thread using those I/O parameters\n runnableModel.runModel(params)\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 model on the worker thread and\n * sending the outputs back to the main thread.\n *\n * @param init The function that initializes the generated model instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<GeneratedModel>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initGeneratedModel = 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,wBAAwB,cAAc,eAAe;AAkC9D,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;AAG/C,QAAM,eAAe,WAAW,eAAe,IAAI,aAAa,WAAW,YAAY,IAAI;AAG3F,QAAMC,UAAS,IAAI,uBAAuB,YAAY;AAGtD,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,SAAS,YAAY;AAC5C,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE,WAAW,SAAS;AAClB,cAAM,IAAI,MAAM,gEAAgE;AAAA,MAClF,OAAO;AACL,kBAAU;AAAA,MACZ;AAGA,MAAAA,QAAO,iBAAiB,QAAQ,SAAS,OAAO;AAMhD,UAAI;AACJ,UAAI;AACF,mBAAW,MAAMD,aAAY,SAAS,SAASC,QAAO,iBAAiB,CAAC,CAAC;AAAA,MAC3E,UAAE;AACA,kBAAU;AAAA,MACZ;AAMA,MAAAA,QAAO,wBAAwB,QAAQ;AAIvC,MAAAA,QAAO,gBAAgB,OAAO;AAE9B,aAAO;AAAA,IACT;AAAA,IAEA,WAAW,MAAM;AACf,UAAI,YAAY;AACd,eAAO,QAAQ,QAAQ;AAAA,MACzB,OAAO;AACL,qBAAa;AACb,eAAO,OAAO,UAAUD,YAAW;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;;;ACpHA,SAAS,QAAQ,YAAAE,iBAAgB;AAGjC,SAAS,0BAAAC,yBAAwB,2BAA2B;AAG5D,IAAI;AAGJ,IAAI;AAOJ,IAAM,SAAS,IAAIA,wBAAuB;AAa1C,IAAM,cAAc;AAAA,EAClB,MAAM,YAAiC;AACrC,QAAI,eAAe;AACjB,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAGA,UAAM,iBAAiB,MAAM,mBAAmB;AAChD,oBAAgB,oBAAoB,cAAc;AAGlD,WAAO;AAAA,MACL,cAAc,cAAc;AAAA,MAC5B,cAAc,cAAc;AAAA,MAC5B,WAAW,cAAc;AAAA,MACzB,SAAS,cAAc;AAAA,MACvB,UAAU,cAAc;AAAA,MACxB,iBAAiB,cAAc;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,SAAS,UAAwD;AAC/D,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,sEAAsE;AAAA,IACxF;AAIA,WAAO,wBAAwB,QAAQ;AAGvC,kBAAc,SAAS,MAAM;AAG7B,WAAOD,UAAS,QAAQ;AAAA,EAC1B;AACF;AAWO,SAAS,kBAAkB,MAA2C;AAG3E,uBAAqB;AAGrB,SAAO,WAAW;AACpB;","names":["modelWorker","params","Transfer","BufferedRunModelParams"]}
package/dist/runner.cjs CHANGED
@@ -34,26 +34,15 @@ async function spawnAsyncModelRunner(workerSpec) {
34
34
  async function spawnAsyncModelRunnerWithWorker(worker) {
35
35
  const modelWorker = await (0, import_threads.spawn)(worker);
36
36
  const initResult = await modelWorker.initModel();
37
- let ioBuffer = initResult.ioBuffer;
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;
37
+ const modelListing = initResult.modelListing ? new import_runtime.ModelListing(initResult.modelListing) : void 0;
38
+ const params = new import_runtime.BufferedRunModelParams(modelListing);
50
39
  let running = false;
51
40
  let terminated = false;
52
41
  return {
53
42
  createOutputs: () => {
54
43
  return new import_runtime.Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq);
55
44
  },
56
- runModel: async (inputs, outputs) => {
45
+ runModel: async (inputs, outputs, options) => {
57
46
  if (terminated) {
58
47
  throw new Error("Async model runner has already been terminated");
59
48
  } else if (running) {
@@ -61,24 +50,15 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
61
50
  } else {
62
51
  running = true;
63
52
  }
64
- const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
65
- for (let i = 0; i < inputs.length; i++) {
66
- inputsArray[i] = inputs[i].get();
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
- }
53
+ params.updateFromParams(inputs, outputs, options);
54
+ let ioBuffer;
73
55
  try {
74
- ioBuffer = await modelWorker.runModel((0, import_threads.Transfer)(ioBuffer));
56
+ ioBuffer = await modelWorker.runModel((0, import_threads.Transfer)(params.getEncodedBuffer()));
75
57
  } finally {
76
58
  running = false;
77
59
  }
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);
60
+ params.updateFromEncodedBuffer(ioBuffer);
61
+ params.finalizeOutputs(outputs);
82
62
  return outputs;
83
63
  },
84
64
  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, 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":[]}
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 { BufferedRunModelParams, ModelListing, Outputs } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker\n * (a Web Worker when running in a browser environment, or a worker thread\n * when running in a Node.js environment).\n *\n * In your app project, define a JavaScript file, called `worker.js` for example,\n * that initializes the generated model in the context of a worker thread:\n *\n * ```js\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n * import loadGeneratedModel from './sde-prep/generated-model.js'\n *\n * exposeModelWorker(loadGeneratedModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the worker thread 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\n // Create a `ModelListing` instance if the listing was defined in the generated model\n const modelListing = initResult.modelListing ? new ModelListing(initResult.modelListing) : undefined\n\n // Maintain a `BufferedRunModelParams` instance that holds the I/O parameters\n const params = new BufferedRunModelParams(modelListing)\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, options) => {\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 // Update the I/O parameters\n params.updateFromParams(inputs, outputs, options)\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 let ioBuffer: ArrayBuffer\n try {\n ioBuffer = await modelWorker.runModel(Transfer(params.getEncodedBuffer()))\n } finally {\n running = false\n }\n\n // Once the buffer is transferred to the worker, the buffer in the\n // `BufferedRunModelParams` becomes \"detached\" and is no longer usable.\n // After the buffer is transferred back from the worker, we need to\n // restore the state of the object to use the new buffer.\n params.updateFromEncodedBuffer(ioBuffer)\n\n // Copy the output values and elapsed time from the buffer to the\n // `Outputs` instance\n params.finalizeOutputs(outputs)\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,qBAA8D;AAkC9D,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;AAG/C,QAAM,eAAe,WAAW,eAAe,IAAI,4BAAa,WAAW,YAAY,IAAI;AAG3F,QAAM,SAAS,IAAI,sCAAuB,YAAY;AAGtD,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,SAAS,YAAY;AAC5C,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE,WAAW,SAAS;AAClB,cAAM,IAAI,MAAM,gEAAgE;AAAA,MAClF,OAAO;AACL,kBAAU;AAAA,MACZ;AAGA,aAAO,iBAAiB,QAAQ,SAAS,OAAO;AAMhD,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,YAAY,aAAS,yBAAS,OAAO,iBAAiB,CAAC,CAAC;AAAA,MAC3E,UAAE;AACA,kBAAU;AAAA,MACZ;AAMA,aAAO,wBAAwB,QAAQ;AAIvC,aAAO,gBAAgB,OAAO;AAE9B,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.d.cts CHANGED
@@ -1,25 +1,22 @@
1
1
  import { ModelRunner } from '@sdeverywhere/runtime';
2
2
 
3
3
  /**
4
- * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.
4
+ * Initialize a `ModelRunner` that runs the model asynchronously in a worker
5
+ * (a Web Worker when running in a browser environment, or a worker thread
6
+ * when running in a Node.js environment).
5
7
  *
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
+ * In your app project, define a JavaScript file, called `worker.js` for example,
9
+ * that initializes the generated model in the context of a worker thread:
8
10
  *
9
11
  * ```js
10
- * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'
11
12
  * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'
13
+ * import loadGeneratedModel from './sde-prep/generated-model.js'
12
14
  *
13
- * async function initWasmModel() {
14
- * const wasmModules = loadWasm()
15
- * return initWasmModelAndBuffers(...)
16
- * }
17
- *
18
- * exposeModelWorker(initWasmModel)
15
+ * exposeModelWorker(loadGeneratedModel)
19
16
  * ```
20
17
  *
21
18
  * Then, in your web app, call the `spawnAsyncModelRunner` function, which
22
- * will spawn the Web Worker and initialize the `ModelRunner` that communicates
19
+ * will spawn the worker thread and initialize the `ModelRunner` that communicates
23
20
  * with the worker:
24
21
  *
25
22
  * ```js
package/dist/runner.d.ts CHANGED
@@ -1,25 +1,22 @@
1
1
  import { ModelRunner } from '@sdeverywhere/runtime';
2
2
 
3
3
  /**
4
- * Initialize a `ModelRunner` that runs the model asynchronously in a worker thread.
4
+ * Initialize a `ModelRunner` that runs the model asynchronously in a worker
5
+ * (a Web Worker when running in a browser environment, or a worker thread
6
+ * when running in a Node.js environment).
5
7
  *
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
+ * In your app project, define a JavaScript file, called `worker.js` for example,
9
+ * that initializes the generated model in the context of a worker thread:
8
10
  *
9
11
  * ```js
10
- * import { initWasmModelAndBuffers } from '@sdeverywhere/runtime'
11
12
  * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'
13
+ * import loadGeneratedModel from './sde-prep/generated-model.js'
12
14
  *
13
- * async function initWasmModel() {
14
- * const wasmModules = loadWasm()
15
- * return initWasmModelAndBuffers(...)
16
- * }
17
- *
18
- * exposeModelWorker(initWasmModel)
15
+ * exposeModelWorker(loadGeneratedModel)
19
16
  * ```
20
17
  *
21
18
  * Then, in your web app, call the `spawnAsyncModelRunner` function, which
22
- * will spawn the Web Worker and initialize the `ModelRunner` that communicates
19
+ * will spawn the worker thread and initialize the `ModelRunner` that communicates
23
20
  * with the worker:
24
21
  *
25
22
  * ```js
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, updateOutputIndices } from "@sdeverywhere/runtime";
3
+ import { BufferedRunModelParams, ModelListing, Outputs } from "@sdeverywhere/runtime";
4
4
  async function spawnAsyncModelRunner(workerSpec) {
5
5
  if (workerSpec["path"]) {
6
6
  return spawnAsyncModelRunnerWithWorker(new Worker(workerSpec["path"]));
@@ -11,26 +11,15 @@ async function spawnAsyncModelRunner(workerSpec) {
11
11
  async function spawnAsyncModelRunnerWithWorker(worker) {
12
12
  const modelWorker = await spawn(worker);
13
13
  const initResult = await modelWorker.initModel();
14
- let ioBuffer = initResult.ioBuffer;
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;
14
+ const modelListing = initResult.modelListing ? new ModelListing(initResult.modelListing) : void 0;
15
+ const params = new BufferedRunModelParams(modelListing);
27
16
  let running = false;
28
17
  let terminated = false;
29
18
  return {
30
19
  createOutputs: () => {
31
20
  return new Outputs(initResult.outputVarIds, initResult.startTime, initResult.endTime, initResult.saveFreq);
32
21
  },
33
- runModel: async (inputs, outputs) => {
22
+ runModel: async (inputs, outputs, options) => {
34
23
  if (terminated) {
35
24
  throw new Error("Async model runner has already been terminated");
36
25
  } else if (running) {
@@ -38,24 +27,15 @@ async function spawnAsyncModelRunnerWithWorker(worker) {
38
27
  } else {
39
28
  running = true;
40
29
  }
41
- const inputsArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
42
- for (let i = 0; i < inputs.length; i++) {
43
- inputsArray[i] = inputs[i].get();
44
- }
45
- if (indicesLengthInElements > 0) {
46
- const outputSpecs = outputs.varSpecs || [];
47
- const indicesArray = new Int32Array(ioBuffer, indicesOffsetInBytes, indicesLengthInElements);
48
- updateOutputIndices(indicesArray, outputSpecs);
49
- }
30
+ params.updateFromParams(inputs, outputs, options);
31
+ let ioBuffer;
50
32
  try {
51
- ioBuffer = await modelWorker.runModel(Transfer(ioBuffer));
33
+ ioBuffer = await modelWorker.runModel(Transfer(params.getEncodedBuffer()));
52
34
  } finally {
53
35
  running = false;
54
36
  }
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);
37
+ params.updateFromEncodedBuffer(ioBuffer);
38
+ params.finalizeOutputs(outputs);
59
39
  return outputs;
60
40
  },
61
41
  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, 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":[]}
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 { BufferedRunModelParams, ModelListing, Outputs } from '@sdeverywhere/runtime'\n\n/**\n * Initialize a `ModelRunner` that runs the model asynchronously in a worker\n * (a Web Worker when running in a browser environment, or a worker thread\n * when running in a Node.js environment).\n *\n * In your app project, define a JavaScript file, called `worker.js` for example,\n * that initializes the generated model in the context of a worker thread:\n *\n * ```js\n * import { exposeModelWorker } from '@sdeverywhere/runtime-async/worker'\n * import loadGeneratedModel from './sde-prep/generated-model.js'\n *\n * exposeModelWorker(loadGeneratedModel)\n * ```\n *\n * Then, in your web app, call the `spawnAsyncModelRunner` function, which\n * will spawn the worker thread 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\n // Create a `ModelListing` instance if the listing was defined in the generated model\n const modelListing = initResult.modelListing ? new ModelListing(initResult.modelListing) : undefined\n\n // Maintain a `BufferedRunModelParams` instance that holds the I/O parameters\n const params = new BufferedRunModelParams(modelListing)\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, options) => {\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 // Update the I/O parameters\n params.updateFromParams(inputs, outputs, options)\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 let ioBuffer: ArrayBuffer\n try {\n ioBuffer = await modelWorker.runModel(Transfer(params.getEncodedBuffer()))\n } finally {\n running = false\n }\n\n // Once the buffer is transferred to the worker, the buffer in the\n // `BufferedRunModelParams` becomes \"detached\" and is no longer usable.\n // After the buffer is transferred back from the worker, we need to\n // restore the state of the object to use the new buffer.\n params.updateFromEncodedBuffer(ioBuffer)\n\n // Copy the output values and elapsed time from the buffer to the\n // `Outputs` instance\n params.finalizeOutputs(outputs)\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,wBAAwB,cAAc,eAAe;AAkC9D,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;AAG/C,QAAM,eAAe,WAAW,eAAe,IAAI,aAAa,WAAW,YAAY,IAAI;AAG3F,QAAM,SAAS,IAAI,uBAAuB,YAAY;AAGtD,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,SAAS,YAAY;AAC5C,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE,WAAW,SAAS;AAClB,cAAM,IAAI,MAAM,gEAAgE;AAAA,MAClF,OAAO;AACL,kBAAU;AAAA,MACZ;AAGA,aAAO,iBAAiB,QAAQ,SAAS,OAAO;AAMhD,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,YAAY,SAAS,SAAS,OAAO,iBAAiB,CAAC,CAAC;AAAA,MAC3E,UAAE;AACA,kBAAU;AAAA,MACZ;AAMA,aAAO,wBAAwB,QAAQ;AAIvC,aAAO,gBAAgB,OAAO;AAE9B,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
@@ -24,81 +24,36 @@ __export(worker_exports, {
24
24
  module.exports = __toCommonJS(worker_exports);
25
25
  var import_worker = require("threads/worker");
26
26
  var import_runtime = require("@sdeverywhere/runtime");
27
- var initWasmModel;
28
- var wasmModel;
29
- var inputsWasmBuffer;
30
- var outputsWasmBuffer;
31
- var outputIndicesWasmBuffer;
27
+ var initGeneratedModel;
28
+ var runnableModel;
29
+ var params = new import_runtime.BufferedRunModelParams();
32
30
  var modelWorker = {
33
31
  async initModel() {
34
- if (wasmModel) {
35
- throw new Error("WasmModel was already initialized");
32
+ if (runnableModel) {
33
+ throw new Error("RunnableModel was already initialized");
36
34
  }
37
- const wasmResult = await initWasmModel();
38
- wasmModel = wasmResult.model;
39
- inputsWasmBuffer = wasmResult.inputsBuffer;
40
- outputsWasmBuffer = wasmResult.outputsBuffer;
41
- outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer;
42
- const runTimeLength = 8;
43
- const inputsLength = inputsWasmBuffer.getArrayView().length;
44
- const outputsLength = outputsWasmBuffer.getArrayView().length;
45
- const outputIndicesLength = (outputIndicesWasmBuffer == null ? void 0 : outputIndicesWasmBuffer.getArrayView().length) || 0;
46
- const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength;
47
- const ioArray = new Float64Array(totalLength);
48
- const ioBuffer = ioArray.buffer;
49
- const initResult = {
50
- outputVarIds: wasmResult.outputVarIds,
51
- startTime: wasmModel.startTime,
52
- endTime: wasmModel.endTime,
53
- saveFreq: wasmModel.saveFreq,
54
- inputsLength,
55
- outputsLength,
56
- outputIndicesLength,
57
- outputRowLength: wasmModel.numSavePoints,
58
- ioBuffer
35
+ const generatedModel = await initGeneratedModel();
36
+ runnableModel = (0, import_runtime.createRunnableModel)(generatedModel);
37
+ return {
38
+ outputVarIds: runnableModel.outputVarIds,
39
+ modelListing: runnableModel.modelListing,
40
+ startTime: runnableModel.startTime,
41
+ endTime: runnableModel.endTime,
42
+ saveFreq: runnableModel.saveFreq,
43
+ outputRowLength: runnableModel.numSavePoints
59
44
  };
60
- return (0, import_worker.Transfer)(initResult, [ioBuffer]);
61
45
  },
62
46
  runModel(ioBuffer) {
63
- if (!wasmModel) {
64
- throw new Error("WasmModel must be initialized before running the model in worker");
47
+ if (!runnableModel) {
48
+ throw new Error("RunnableModel must be initialized before running the model in worker");
65
49
  }
66
- const runTimeOffsetInBytes = 0;
67
- const runTimeLengthInElements = 1;
68
- const runTimeLengthInBytes = runTimeLengthInElements * 8;
69
- const inputsWasmArray = inputsWasmBuffer.getArrayView();
70
- const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
71
- const inputsLengthInElements = inputsWasmArray.length;
72
- const inputsLengthInBytes = inputsWasmArray.byteLength;
73
- const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
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
- }
89
- const t0 = (0, import_runtime.perfNow)();
90
- wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : void 0);
91
- const elapsed = (0, import_runtime.perfElapsed)(t0);
92
- const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
93
- runTimeBufferArray[0] = elapsed;
94
- const outputsLengthInElements = outputsWasmArray.length;
95
- const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
96
- outputsBufferArray.set(outputsWasmArray);
50
+ params.updateFromEncodedBuffer(ioBuffer);
51
+ runnableModel.runModel(params);
97
52
  return (0, import_worker.Transfer)(ioBuffer);
98
53
  }
99
54
  };
100
55
  function exposeModelWorker(init) {
101
- initWasmModel = init;
56
+ initGeneratedModel = init;
102
57
  (0, import_worker.expose)(modelWorker);
103
58
  }
104
59
  // Annotate the CommonJS export names for ESM import in node:
@@ -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<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":[]}
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 { GeneratedModel, RunnableModel } from '@sdeverywhere/runtime'\nimport { BufferedRunModelParams, createRunnableModel } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initGeneratedModel: () => Promise<GeneratedModel>\n\n/** @hidden */\nlet runnableModel: RunnableModel\n\n/**\n * Maintain a `BufferedRunModelParams` instance that wraps the transferable buffer\n * containing the I/O parameters.\n * @hidden\n */\nconst params = new BufferedRunModelParams()\n\ninterface InitResult {\n outputVarIds: string[]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n modelListing?: /*ModelListingSpecs*/ any\n startTime: number\n endTime: number\n saveFreq: number\n outputRowLength: number\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<InitResult> {\n if (runnableModel) {\n throw new Error('RunnableModel was already initialized')\n }\n\n // Initialize the runnable model\n const generatedModel = await initGeneratedModel()\n runnableModel = createRunnableModel(generatedModel)\n\n // Transfer the model metadata to the runner\n return {\n outputVarIds: runnableModel.outputVarIds,\n modelListing: runnableModel.modelListing,\n startTime: runnableModel.startTime,\n endTime: runnableModel.endTime,\n saveFreq: runnableModel.saveFreq,\n outputRowLength: runnableModel.numSavePoints\n }\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!runnableModel) {\n throw new Error('RunnableModel must be initialized before running the model in worker')\n }\n\n // Update the `BufferedRunModelParams` to use the values in the buffer that was transferred\n // from the runner to the worker\n params.updateFromEncodedBuffer(ioBuffer)\n\n // Run the model synchronously on the worker thread using those I/O parameters\n runnableModel.runModel(params)\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 model on the worker thread and\n * sending the outputs back to the main thread.\n *\n * @param init The function that initializes the generated model instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<GeneratedModel>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initGeneratedModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAAiC;AAGjC,qBAA4D;AAG5D,IAAI;AAGJ,IAAI;AAOJ,IAAM,SAAS,IAAI,sCAAuB;AAa1C,IAAM,cAAc;AAAA,EAClB,MAAM,YAAiC;AACrC,QAAI,eAAe;AACjB,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAGA,UAAM,iBAAiB,MAAM,mBAAmB;AAChD,wBAAgB,oCAAoB,cAAc;AAGlD,WAAO;AAAA,MACL,cAAc,cAAc;AAAA,MAC5B,cAAc,cAAc;AAAA,MAC5B,WAAW,cAAc;AAAA,MACzB,SAAS,cAAc;AAAA,MACvB,UAAU,cAAc;AAAA,MACxB,iBAAiB,cAAc;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,SAAS,UAAwD;AAC/D,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,sEAAsE;AAAA,IACxF;AAIA,WAAO,wBAAwB,QAAQ;AAGvC,kBAAc,SAAS,MAAM;AAG7B,eAAO,wBAAS,QAAQ;AAAA,EAC1B;AACF;AAWO,SAAS,kBAAkB,MAA2C;AAG3E,uBAAqB;AAGrB,4BAAO,WAAW;AACpB;","names":[]}
package/dist/worker.d.cts CHANGED
@@ -1,14 +1,14 @@
1
- import { WasmModelInitResult } from '@sdeverywhere/runtime';
1
+ import { GeneratedModel } from '@sdeverywhere/runtime';
2
2
 
3
3
  /**
4
4
  * Expose an object in the current worker thread that communicates with the
5
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.
6
+ * object will take care of running the model on the worker thread and
7
+ * sending the outputs back to the main thread.
8
8
  *
9
- * @param init The function that initializes the `WasmModel` instance that
9
+ * @param init The function that initializes the generated model instance that
10
10
  * is used in the worker thread.
11
11
  */
12
- declare function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void;
12
+ declare function exposeModelWorker(init: () => Promise<GeneratedModel>): void;
13
13
 
14
14
  export { exposeModelWorker };
package/dist/worker.d.ts CHANGED
@@ -1,14 +1,14 @@
1
- import { WasmModelInitResult } from '@sdeverywhere/runtime';
1
+ import { GeneratedModel } from '@sdeverywhere/runtime';
2
2
 
3
3
  /**
4
4
  * Expose an object in the current worker thread that communicates with the
5
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.
6
+ * object will take care of running the model on the worker thread and
7
+ * sending the outputs back to the main thread.
8
8
  *
9
- * @param init The function that initializes the `WasmModel` instance that
9
+ * @param init The function that initializes the generated model instance that
10
10
  * is used in the worker thread.
11
11
  */
12
- declare function exposeModelWorker(init: () => Promise<WasmModelInitResult>): void;
12
+ declare function exposeModelWorker(init: () => Promise<GeneratedModel>): void;
13
13
 
14
14
  export { exposeModelWorker };
package/dist/worker.js CHANGED
@@ -1,81 +1,36 @@
1
1
  // src/worker.ts
2
2
  import { expose, Transfer } from "threads/worker";
3
- import { perfElapsed, perfNow } from "@sdeverywhere/runtime";
4
- var initWasmModel;
5
- var wasmModel;
6
- var inputsWasmBuffer;
7
- var outputsWasmBuffer;
8
- var outputIndicesWasmBuffer;
3
+ import { BufferedRunModelParams, createRunnableModel } from "@sdeverywhere/runtime";
4
+ var initGeneratedModel;
5
+ var runnableModel;
6
+ var params = new BufferedRunModelParams();
9
7
  var modelWorker = {
10
8
  async initModel() {
11
- if (wasmModel) {
12
- throw new Error("WasmModel was already initialized");
9
+ if (runnableModel) {
10
+ throw new Error("RunnableModel was already initialized");
13
11
  }
14
- const wasmResult = await initWasmModel();
15
- wasmModel = wasmResult.model;
16
- inputsWasmBuffer = wasmResult.inputsBuffer;
17
- outputsWasmBuffer = wasmResult.outputsBuffer;
18
- outputIndicesWasmBuffer = wasmResult.outputIndicesBuffer;
19
- const runTimeLength = 8;
20
- const inputsLength = inputsWasmBuffer.getArrayView().length;
21
- const outputsLength = outputsWasmBuffer.getArrayView().length;
22
- const outputIndicesLength = (outputIndicesWasmBuffer == null ? void 0 : outputIndicesWasmBuffer.getArrayView().length) || 0;
23
- const totalLength = runTimeLength + inputsLength + outputsLength + outputIndicesLength;
24
- const ioArray = new Float64Array(totalLength);
25
- const ioBuffer = ioArray.buffer;
26
- const initResult = {
27
- outputVarIds: wasmResult.outputVarIds,
28
- startTime: wasmModel.startTime,
29
- endTime: wasmModel.endTime,
30
- saveFreq: wasmModel.saveFreq,
31
- inputsLength,
32
- outputsLength,
33
- outputIndicesLength,
34
- outputRowLength: wasmModel.numSavePoints,
35
- ioBuffer
12
+ const generatedModel = await initGeneratedModel();
13
+ runnableModel = createRunnableModel(generatedModel);
14
+ return {
15
+ outputVarIds: runnableModel.outputVarIds,
16
+ modelListing: runnableModel.modelListing,
17
+ startTime: runnableModel.startTime,
18
+ endTime: runnableModel.endTime,
19
+ saveFreq: runnableModel.saveFreq,
20
+ outputRowLength: runnableModel.numSavePoints
36
21
  };
37
- return Transfer(initResult, [ioBuffer]);
38
22
  },
39
23
  runModel(ioBuffer) {
40
- if (!wasmModel) {
41
- throw new Error("WasmModel must be initialized before running the model in worker");
24
+ if (!runnableModel) {
25
+ throw new Error("RunnableModel must be initialized before running the model in worker");
42
26
  }
43
- const runTimeOffsetInBytes = 0;
44
- const runTimeLengthInElements = 1;
45
- const runTimeLengthInBytes = runTimeLengthInElements * 8;
46
- const inputsWasmArray = inputsWasmBuffer.getArrayView();
47
- const inputsOffsetInBytes = runTimeOffsetInBytes + runTimeLengthInBytes;
48
- const inputsLengthInElements = inputsWasmArray.length;
49
- const inputsLengthInBytes = inputsWasmArray.byteLength;
50
- const inputsBufferArray = new Float64Array(ioBuffer, inputsOffsetInBytes, inputsLengthInElements);
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
- }
66
- const t0 = perfNow();
67
- wasmModel.runModel(inputsWasmBuffer, outputsWasmBuffer, useIndices ? outputIndicesWasmBuffer : void 0);
68
- const elapsed = perfElapsed(t0);
69
- const runTimeBufferArray = new Float64Array(ioBuffer, runTimeOffsetInBytes, runTimeLengthInElements);
70
- runTimeBufferArray[0] = elapsed;
71
- const outputsLengthInElements = outputsWasmArray.length;
72
- const outputsBufferArray = new Float64Array(ioBuffer, outputsOffsetInBytes, outputsLengthInElements);
73
- outputsBufferArray.set(outputsWasmArray);
27
+ params.updateFromEncodedBuffer(ioBuffer);
28
+ runnableModel.runModel(params);
74
29
  return Transfer(ioBuffer);
75
30
  }
76
31
  };
77
32
  function exposeModelWorker(init) {
78
- initWasmModel = init;
33
+ initGeneratedModel = init;
79
34
  expose(modelWorker);
80
35
  }
81
36
  export {
@@ -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<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":[]}
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 { GeneratedModel, RunnableModel } from '@sdeverywhere/runtime'\nimport { BufferedRunModelParams, createRunnableModel } from '@sdeverywhere/runtime'\n\n/** @hidden */\nlet initGeneratedModel: () => Promise<GeneratedModel>\n\n/** @hidden */\nlet runnableModel: RunnableModel\n\n/**\n * Maintain a `BufferedRunModelParams` instance that wraps the transferable buffer\n * containing the I/O parameters.\n * @hidden\n */\nconst params = new BufferedRunModelParams()\n\ninterface InitResult {\n outputVarIds: string[]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n modelListing?: /*ModelListingSpecs*/ any\n startTime: number\n endTime: number\n saveFreq: number\n outputRowLength: number\n}\n\n/** @hidden */\nconst modelWorker = {\n async initModel(): Promise<InitResult> {\n if (runnableModel) {\n throw new Error('RunnableModel was already initialized')\n }\n\n // Initialize the runnable model\n const generatedModel = await initGeneratedModel()\n runnableModel = createRunnableModel(generatedModel)\n\n // Transfer the model metadata to the runner\n return {\n outputVarIds: runnableModel.outputVarIds,\n modelListing: runnableModel.modelListing,\n startTime: runnableModel.startTime,\n endTime: runnableModel.endTime,\n saveFreq: runnableModel.saveFreq,\n outputRowLength: runnableModel.numSavePoints\n }\n },\n\n runModel(ioBuffer: ArrayBuffer): TransferDescriptor<ArrayBuffer> {\n if (!runnableModel) {\n throw new Error('RunnableModel must be initialized before running the model in worker')\n }\n\n // Update the `BufferedRunModelParams` to use the values in the buffer that was transferred\n // from the runner to the worker\n params.updateFromEncodedBuffer(ioBuffer)\n\n // Run the model synchronously on the worker thread using those I/O parameters\n runnableModel.runModel(params)\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 model on the worker thread and\n * sending the outputs back to the main thread.\n *\n * @param init The function that initializes the generated model instance that\n * is used in the worker thread.\n */\nexport function exposeModelWorker(init: () => Promise<GeneratedModel>): void {\n // Save the initializer, which will be used when the runner calls `initModel`\n // on the worker\n initGeneratedModel = init\n\n // Expose the worker implementation to `threads.js`\n expose(modelWorker)\n}\n"],"mappings":";AAGA,SAAS,QAAQ,gBAAgB;AAGjC,SAAS,wBAAwB,2BAA2B;AAG5D,IAAI;AAGJ,IAAI;AAOJ,IAAM,SAAS,IAAI,uBAAuB;AAa1C,IAAM,cAAc;AAAA,EAClB,MAAM,YAAiC;AACrC,QAAI,eAAe;AACjB,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAGA,UAAM,iBAAiB,MAAM,mBAAmB;AAChD,oBAAgB,oBAAoB,cAAc;AAGlD,WAAO;AAAA,MACL,cAAc,cAAc;AAAA,MAC5B,cAAc,cAAc;AAAA,MAC5B,WAAW,cAAc;AAAA,MACzB,SAAS,cAAc;AAAA,MACvB,UAAU,cAAc;AAAA,MACxB,iBAAiB,cAAc;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,SAAS,UAAwD;AAC/D,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,sEAAsE;AAAA,IACxF;AAIA,WAAO,wBAAwB,QAAQ;AAGvC,kBAAc,SAAS,MAAM;AAG7B,WAAO,SAAS,QAAQ;AAAA,EAC1B;AACF;AAWO,SAAS,kBAAkB,MAA2C;AAG3E,uBAAqB;AAGrB,SAAO,WAAW;AACpB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/runtime-async",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "files": [
5
5
  "dist/**"
6
6
  ],
@@ -26,7 +26,7 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "@sdeverywhere/runtime": "^0.2.2",
29
+ "@sdeverywhere/runtime": "^0.2.3",
30
30
  "threads": "1.7.0"
31
31
  },
32
32
  "author": "Climate Interactive",
@@ -49,7 +49,7 @@
49
49
  "test": "vitest run",
50
50
  "test:watch": "vitest",
51
51
  "test:ci": "vitest run",
52
- "type-check": "tsc --noEmit -p tsconfig-build.json",
52
+ "type-check": "tsc --noEmit -p tsconfig-test.json",
53
53
  "build": "tsup",
54
54
  "docs": "../../scripts/gen-docs.js",
55
55
  "ci:build": "run-s clean lint prettier:check type-check build test:ci docs"