├── .babelrc ├── .browserslistrc ├── .eslintrc ├── .github ├── FUNDING.yml └── workflows │ └── commit.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg ├── pre-commit └── prepare-commit-msg ├── LICENSE ├── README.md ├── commitlint.config.js ├── demo ├── copyBuild.sh ├── demo.js ├── index.css └── index.html ├── docs ├── .nojekyll ├── CNAME ├── assets │ ├── highlight.css │ ├── main.js │ ├── search.js │ └── style.css ├── classes │ └── RinzlerEngine.html ├── index.html ├── modules.html └── types │ ├── WorkerFunction.html │ ├── WorkerFunctionTransferArgs.html │ └── WorkerInitFunction.html ├── media ├── infographic.png ├── rinzler_cover.png ├── rinzler_logo.png ├── rinzler_logo.svg └── rinzler_logo_text.svg ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── event-emitter.ts ├── index.ts ├── median.ts ├── scheduler.ts ├── tsconfig.json └── worker-wrapper.ts ├── tsconfig.json ├── typedoc.json └── worker-src ├── index.d.ts ├── index.ts └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env", { 4 | "targets": { "esmodules": true } 5 | }], 6 | "@babel/typescript" 7 | ], 8 | "plugins": [ 9 | "@babel/plugin-proposal-class-properties" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 0.5% and supports webworkers and supports promises and not dead 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "jsx": true, 8 | "impliedStrict": true 9 | } 10 | }, 11 | "plugins": [ 12 | "@typescript-eslint" 13 | ], 14 | "extends": [ 15 | "eslint:recommended", 16 | "plugin:@typescript-eslint/recommended" 17 | ], 18 | "env": { 19 | "node": true, 20 | "es2020": true 21 | }, 22 | "rules": { 23 | "indent": [ 24 | 2, 25 | "tab", 26 | { 27 | "SwitchCase": 1 28 | } 29 | ], 30 | "semi": [ 31 | 2, 32 | "never" 33 | ], 34 | "curly": [ 35 | 2, 36 | "multi-line" 37 | ], 38 | "linebreak-style": [ 39 | 2, 40 | "unix" 41 | ], 42 | "quotes": [ 43 | 2, 44 | "single", 45 | { 46 | "avoidEscape": true, 47 | "allowTemplateLiterals": true 48 | } 49 | ], 50 | "no-warning-comments": 1, 51 | "object-curly-spacing": [ 52 | 1, 53 | "always" 54 | ], 55 | "array-bracket-spacing": [ 56 | 1, 57 | "never" 58 | ], 59 | "no-await-in-loop": 0, 60 | "@typescript-eslint/member-delimiter-style": [ 61 | 2, 62 | { 63 | "multiline": { 64 | "delimiter": "none" 65 | }, 66 | "singleline": { 67 | "delimiter": "semi", 68 | "requireLast": false 69 | } 70 | } 71 | ], 72 | "@typescript-eslint/naming-convention": [ 73 | 1, 74 | { 75 | "selector": "default", 76 | "format": [ 77 | "camelCase" 78 | ], 79 | "leadingUnderscore": "allow", 80 | "trailingUnderscore": "allow" 81 | }, 82 | { 83 | "selector": "variable", 84 | "format": [ 85 | "camelCase", 86 | "UPPER_CASE" 87 | ], 88 | "leadingUnderscore": "allow", 89 | "trailingUnderscore": "allow" 90 | }, 91 | { 92 | "selector": "property", 93 | "format": [ 94 | "camelCase", 95 | "UPPER_CASE" 96 | ], 97 | "leadingUnderscore": "allow", 98 | "trailingUnderscore": "allow" 99 | }, 100 | { 101 | "selector": "memberLike", 102 | "modifiers": [ 103 | "private" 104 | ], 105 | "format": [ 106 | "camelCase" 107 | ], 108 | "leadingUnderscore": "require" 109 | }, 110 | { 111 | "selector": "typeLike", 112 | "format": [ 113 | "PascalCase" 114 | ] 115 | } 116 | ] 117 | }, 118 | "overrides": [ 119 | { 120 | "files": [ 121 | "*.js" 122 | ], 123 | "rules": { 124 | "@typescript-eslint/no-var-requires": 0 125 | } 126 | } 127 | ] 128 | } 129 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: GitSquared 4 | custom: ['https://gaby.dev/donate'] 5 | -------------------------------------------------------------------------------- /.github/workflows/commit.yml: -------------------------------------------------------------------------------- 1 | name: Commit Checks 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: 12.x 14 | - name: Cache dependencies 15 | uses: actions/cache@v2 16 | with: 17 | path: | 18 | **/node_modules 19 | key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} 20 | - name: Install dependencies 21 | run: npm ci 22 | - name: Run lint script 23 | run: npm run lint 24 | build: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions/setup-node@v1 30 | with: 31 | node-version: 12.x 32 | - name: Cache dependencies 33 | uses: actions/cache@v2 34 | with: 35 | path: | 36 | **/node_modules 37 | key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} 38 | - name: Install dependencies 39 | run: npm ci 40 | - name: Run build 41 | run: npm run build 42 | test: 43 | runs-on: ubuntu-latest 44 | 45 | steps: 46 | - uses: actions/checkout@v2 47 | - uses: actions/setup-node@v1 48 | with: 49 | node-version: 12.x 50 | - name: Cache dependencies 51 | uses: actions/cache@v2 52 | with: 53 | path: | 54 | **/node_modules 55 | key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} 56 | - name: Install dependencies 57 | run: npm ci 58 | - name: Run build 59 | run: npm run build 60 | - name: Run tests 61 | run: npm run test 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | demo/dist 2 | .vercel 3 | 4 | .DS_Store 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | .parcel-cache 83 | 84 | # Next.js build output 85 | .next 86 | out 87 | 88 | # Nuxt.js build / generate output 89 | .nuxt 90 | dist 91 | 92 | # Gatsby files 93 | .cache/ 94 | # Comment in the public line in if your project uses Gatsby and not Next.js 95 | # https://nextjs.org/blog/next-9-1#public-directory-support 96 | # public 97 | 98 | # vuepress build output 99 | .vuepress/dist 100 | 101 | # Serverless directories 102 | .serverless/ 103 | 104 | # FuseBox cache 105 | .fusebox/ 106 | 107 | # DynamoDB Local files 108 | .dynamodb/ 109 | 110 | # TernJS port file 111 | .tern-port 112 | 113 | # Stores VSCode versions used for testing VSCode extensions 114 | .vscode-test 115 | 116 | # yarn v2 117 | .yarn/cache 118 | .yarn/unplugged 119 | .yarn/build-state.yml 120 | .yarn/install-state.gz 121 | .pnp.* 122 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install komit $1 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2020-2021 Gabriel Saillard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
9 | 10 | [Rinzler](https://github.com/GitSquared/rinzler) is a ~~turboramjet~~ **parallel processing engine** for the browser. 11 | 12 | It speeds up your web application by allowing recurring functions to execute in parallel taking full advantage of the host system's available processing power. 13 | 14 | Check out the [full docs](https://gitsquared.github.io/rinzler/classes/rinzlerengine.html), try the [interactive demo](https://rinzler-demo.vercel.app) or read on for a high-level overview and a quick start guide. 15 | 16 | ## Concept 17 | Most devices have a processor unit (CPU) with multiple *cores*, meaning that they are capable of working on multiple tasks at the same time. 18 | Modern operating systems with multi-tasking functionality (e.g the ability to run & manage multiple programs/windows) have a special component called a *thread scheduler*. 19 | 20 | Each program you run can have multiple *threads*, and the scheduler's job is to distribute threads to the CPU's cores. 21 | 22 | A web page's JavaScript normally executes on a single thread, meaning it will never use more than one CPU core. In most cases this is fine, and also helps ensures other tabs in the user's browser, or other programs, can also keep running smoothly. 23 | 24 | However, some web applications might need to process a lot of data, or do a lot of expensive computing, and therefore can benefit from spreading work across all the available cores of the host machine. 25 | 26 | Rinzler is a tool to do just that, in the simplest way possible - just define functions to run in parallel, and use native ES6 [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) to run & manage parallelized jobs. 27 | 28 |-no data-129 |
Start the engine.
77 |Internally, configures the job processing functions, starts the load balancer and launches a first Web Worker.
78 | 79 |A Promise for the current engine instance. Can be used for chaining multiple instance method calls.
80 |The function which will process all jobs sent to this engine instance.
87 |Optional
initFunction: WorkerInitFunction<unknown>A function for setting up a Web Worker environment before it starts processing jobs. May be sync or async.
91 |Optional
initArgs: WorkerFunctionTransferArgsDynamic data to send to the initFunction to set up the environment of new Web Worker instances.
95 |Schedule, execute and get the results of a job.
105 | 106 |A Promise that will be fulfilled with T or an empty resolve when the job has been processed.
107 |If the job threw an error when processing, the Promise will reject with an [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Instance_properties) object containing the job's error message.
108 |
109 | The return type of a successful job - meaning what the WorkerFunction
will return, with Transferable
objects inlined.
Rest
...args: WorkerFunctionTransferArgsStart as much WebWorkers as currently recommended by the browser, or allowed by afterburner.
144 |You should use this only if you know you will have to process many jobs and want to ensure that the engine is fully revved up. 145 | In most cases the automatic "temperature" adjustement will provide a good balance between performance and memory footprint.
146 |You should immediatly follow this call with your actual runJob calls, otherwise the engine might start to cool itself down automatically.
147 | 148 |A Promise for the current engine instance. Can be used for chaining multiple instance method calls.
149 |WARNING: Possibly unstable.
171 |Overrides the maximum number of WebWorkers Rinzler will attempt to spawn.
172 |Normally this is retrieved from navigator.hardwareConcurrency
, which is a browser-provided indicator of how many Workers a browsing context should use, with respect to the browser's own limitations and the number of available CPU cores on the host hardware.
By going beyond that number you may crash the browser tab, get unexpected errors, or a considerable boost in parallelized performance. 174 | Aim for the moon, right?
175 | 176 |A Promise for the current engine instance. Can be used for chaining multiple instance method calls.
177 |The new maximum number of WebWorkers this instance will manage. If set to lower than the current max, extra Workers will be gracefully shut down to respect the new limit.
184 |Generated using TypeDoc
Rinzler is a turboramjet parallel processing engine for the browser.
It speeds up your web application by allowing recurring functions to execute in parallel taking full advantage of the host system's available processing power.
26 |Check out the full docs, try the interactive demo or read on for a high-level overview and a quick start guide.
27 | 28 | 29 |Most devices have a processor unit (CPU) with multiple cores, meaning that they are capable of working on multiple tasks at the same time. 32 | Modern operating systems with multi-tasking functionality (e.g the ability to run & manage multiple programs/windows) have a special component called a thread scheduler.
33 |Each program you run can have multiple threads, and the scheduler's job is to distribute threads to the CPU's cores.
34 |A web page's JavaScript normally executes on a single thread, meaning it will never use more than one CPU core. In most cases this is fine, and also helps ensures other tabs in the user's browser, or other programs, can also keep running smoothly.
35 |However, some web applications might need to process a lot of data, or do a lot of expensive computing, and therefore can benefit from spreading work across all the available cores of the host machine.
36 |Rinzler is a tool to do just that, in the simplest way possible - just define functions to run in parallel, and use native ES6 Promises to run & manage parallelized jobs.
37 |Internally, it leverages Web Workers, which is a standard Web API for executing code in separate threads. It also includes a custom scheduler that handles spawning new Workers when necessary, sharing work between them, and shutting them down when they're not used.
42 | 43 | 44 |npm i rinzler-engine
47 |
48 | Both ES & UMD modules are bundled, as well as TypeScript types, so you should be all set.
49 |Rinzler targets browsers with WebWorkers and Promises support (check the browserslistrc). Most modern evergreen browsers, including Edge, should be compatible.
50 | 51 | 52 |In the following example, we will set up a Rinzler-accelerated app that decodes ArrayBuffer
s of text.
In most real-life use cases, the job processing you will offload to Rinzler will depend on some dynamic variable in the context of your app: in this example, the original encoding of the text we want to decode.
60 |The processing functions you will pass to Rinzler cannot contain references to external variables, because their source code will be extracted and printed in the Worker instances' source.
61 |To work around this limitation, Rinzler allows you to setup an "initialization" function and pass it a payload. This function & payload will be run on each new Web Worker instance before it starts processing your jobs.
62 |const initOptions = [{
encoding: 'utf-8' // We're just going to print this here, but in real life you would probably get this option from user input.
}]
function init(options) {
// This will run once in new Web Worker contexts. We can use the `self` global to store data for later.
self.params = {
encoding: options.encoding
}
}
63 |
64 |
65 |
66 | We need to setup a function that will actually do the job we need to parallelize, in this case, decoding text buffers.
69 |function processJob(message) {
// We expect to receive an object with an `encodedText` prop that is an ArrayBuffer.
const buffer = message.encodedText
// Get the encoding parameter we stored earlier, or default to ASCII.
const encoding = self.params?.encoding || 'ascii'
const text = new TextDecoder(encoding).decode(buffer)
return [text]
}
70 |
71 |
72 |
73 | Next we will import Rinzler and start the engine by passing the function(s) we defined above.
76 |The following code is written for asynchronous contexts, but you can translate it to synchronous by using .then()
with a callback instead of await
.
import RinzlerEngine from 'rinzler-engine'
const engine = await new RinzlerEngine().configureAndStart(processJob, init, initOptions)
78 |
79 |
80 |
81 | Now we can actually run jobs! We'll use the runJob()
method, which returns a Promise
that will resolve when the job is completed.
Since we need to pass an ArrayBuffer
, we'll use the second argument as a Transferable[]
- much like in the native worker.postMessage()
API.
// Encode some text to try our decoder with
const encodedText = new TextEncoder('utf-8').encode('hello Rinzler!')
// Pass the encoded text to our decoding engine
const decodedResult = await engine.runJob({ encodedText }, [encodedText])
console.log(decodedResult) // "hello Rinzler!"
86 |
87 | You can start as many jobs as you want, and take full advantage of ES6's asynchronous syntax (for example, Promise.all()
).
If you use TypeScript, you can pass return types with the runJob<T>(): Promise<T>
signature.
Under the hood, Rinzler will take care of launching Web Workers, balancing their load, and gracefully shutting them down when needed to reduce your app's memory footprint.
90 | 91 | 92 |Web Worker instances will be destroyed by the browser when the page exits, but you can schedule a graceful shutdown yourself using engine.shutdown()
, which returns a Promise
that will resolve once all currently active jobs have completed and all workers have been stopped.
Rinzler is licensed under the MIT License. You may integrate it in commercial applications.
100 |Generated using TypeDoc
Generated using TypeDoc
Anything passed in runJob, with Transferable
objects inlined.
The function which will process all jobs sent to this engine instance. May be sync or async. 34 | Worker functions cannot use variables defined outside of their block, because the function code itself is inlined 35 | and written to the Web Worker source code.
36 |You can safely throw errors in this function as they will be catched and bubbled up from the engine.
37 |Optional
message: TGenerated using TypeDoc
Interface for passing messages and Transferable
data to Web Worker instances.
See DedicatedWorkerGlobalScope.postMessage()
for more details.
Generated using TypeDoc
Anything passed as initArgs
in configureAndStart, with Transferable
objects inlined.
A function for setting up a Web Worker environment before it starts processing jobs. May be sync or async. 34 | Worker functions cannot use variables defined outside of their block, because the function code itself is inlined 35 | and written to the Web Worker source code.
36 |You should pass any dynamic data that won't change between jobs using initArgs
in configureAndStart,
37 | and parse/use them in the init function.
If you need to store some global state to be used later when processing jobs, you can write a property to self
, which will be a DedicatedWorkerGlobalScope
.
39 | But be careful not to overwrite any browser-initialized properties in there!
Optional
message: TGenerated using TypeDoc
Welcome to Rinzler's full documentation.
22 | 23 |If you're just getting started, check out the Quick Start guide.
24 |This page describes the interface of an instanted RinzlerEngine class, which you can access like so:
25 | 27 |