Skip to content

Latest commit

 

History

History

plugin-worker

README.md

@sdeverywhere/plugin-worker

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.

Quick Start

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.

Install

# npm
npm install --save-dev @sdeverywhere/plugin-worker

# pnpm
pnpm add -D @sdeverywhere/plugin-worker

# yarn
yarn add -D @sdeverywhere/plugin-worker

Usage

Note: 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.

Why use this plugin?

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.

Steps

  1. Add @sdeverywhere/plugin-worker as a project "dev" dependency:
cd your-model-project
npm install --save-dev @sdeverywhere/plugin-worker
  1. Update your sde.config.js file to use workerPlugin. Make sure that workerPlugin runs 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')]
      })
    ]
  }
}
  1. Run sde bundle (or sde dev); the plugin writes a worker.js file 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.

Using the generated worker

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.

Writing to multiple locations

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')]
})

Documentation

API documentation (for plugin configuration options) is available in the docs directory.

License

SDEverywhere is distributed under the MIT license. See LICENSE for more details.