This package provides a plugin that generates a worker.js file that can run a System Dynamics model (as generated by SDEverywhere) asynchronously in a Web Worker or Node.js worker thread.
The best way to get started with SDEverywhere is to follow the Quick Start instructions.
If you follow those instructions, the @sdeverywhere/plugin-worker package will be added to your project automatically, in which case you can skip the next section and jump straight to the "Usage" section below.
# npm
npm install --save-dev @sdeverywhere/plugin-worker
# pnpm
pnpm add -D @sdeverywhere/plugin-worker
# yarn
yarn add -D @sdeverywhere/plugin-workerNote: If you followed the "Quick Start" instructions above and/or are using one of the standard project templates provided by SDEverywhere, the sde.config.js file should already be set up to use plugin-worker.
Reading these instructions can still be helpful if you are setting up a project manually or want to understand how plugin-worker can be integrated into your project.
Running an SDEverywhere-generated model can take long enough to make an application feel unresponsive if the model runs on the main (UI) thread.
This plugin bundles your generated model together with the @sdeverywhere/runtime-async glue code into a single, self-contained worker.js file.
When your application spawns that worker, model runs happen on a separate thread, which keeps sliders and other controls responsive.
The generated worker works in both web browser and Node.js environments without any changes on your part: it uses a Web Worker in a browser, and a worker thread in Node.js.
- Add
@sdeverywhere/plugin-workeras a project "dev" dependency:
cd your-model-project
npm install --save-dev @sdeverywhere/plugin-worker- Update your
sde.config.jsfile to useworkerPlugin. Make sure thatworkerPluginruns after any plugin that produces the generated model (for example,plugin-wasm):
import { dirname, join as joinPath } from 'path'
import { fileURLToPath } from 'url'
import { workerPlugin } from '@sdeverywhere/plugin-worker'
const __dirname = dirname(fileURLToPath(import.meta.url))
const corePath = (...parts) => joinPath(__dirname, 'packages', 'core', ...parts)
export async function config() {
return {
modelFiles: ['model/example.mdl'],
// ...
plugins: [
// Generate a `worker.js` file that runs the model asynchronously on a
// worker thread for improved responsiveness
workerPlugin({
// If `outputPaths` is undefined, `worker.js` is written to the `sde-prep`
// directory. More commonly, you will write it into your app or library
// source tree so that it can be imported directly.
outputPaths: [corePath('src', 'model', 'generated', 'worker.js')]
})
]
}
}- Run
sde bundle(orsde dev); the plugin writes aworker.jsfile to each configured output path.
Note: If you set genFormat to 'c' in your sde.config.js file, add wasmPlugin() before workerPlugin() so that the C code is compiled to a WebAssembly module first.
If genFormat is 'js' (the default), workerPlugin can pick up the generated JavaScript model directly and no additional plugin is needed.
The generated worker.js is a self-contained bundle, so the recommended approach is to import its source and pass it to spawnAsyncModelRunner from @sdeverywhere/runtime-async.
This avoids having to serve or resolve a separate worker file at runtime.
In a Vite-based project, you can use the ?raw suffix to import the file as a string:
import { spawnAsyncModelRunner } from '@sdeverywhere/runtime-async'
import workerJs from './generated/worker.js?raw'
export async function createModelRunner() {
return spawnAsyncModelRunner({ source: workerJs })
}If you are not using Vite (or if you are building a library with a bundler that doesn't support ?raw), use the outputSourceModulePaths option instead.
This generates a module that exports the worker source as a string, so it can be imported like any other module:
workerPlugin({
outputSourceModulePaths: [corePath('src', 'model', 'generated', 'worker-source.ts')]
})import workerSource from './generated/worker-source'
export async function createModelRunner() {
return spawnAsyncModelRunner({ source: workerSource })
}Alternatively, if you serve worker.js as a static asset, you can spawn it by path instead:
const runner = await spawnAsyncModelRunner({ path: './worker.js' })The resulting ModelRunner can be used directly, or can be passed to a higher-level scheduler such as ModelScheduler.
See the @sdeverywhere/runtime and @sdeverywhere/runtime-async packages for more details.
The outputPaths option accepts more than one path, which is useful if the same worker is needed by more than one package in a monorepo:
workerPlugin({
outputPaths: [corePath('src', 'model', 'generated', 'worker.js'), appPath('src', 'model', 'generated', 'worker.js')]
})API documentation (for plugin configuration options) is available in the docs directory.
SDEverywhere is distributed under the MIT license. See LICENSE for more details.