A javascript library to split your code with web workers
Don't forget to
await
anyasync
result. Otherwise it will be a Promise.
How to:
Engine:
Special:
Class to create units manager.
Manager(units?: Object); // see add
To register units:
add(units: Object like {
// units:
// 1) instantiated
name: new Unit(),
// 2) will be instantiated on demand in the main thread
name: () => new Unit(),
// 3) will be imported on demand
name: () => import('pathto/unit.js'),
// 4) will be run on demand as web worker
name: () => new Worker(url: string, options: any),
// 5) will be run on demand as shared worker
name: () => new SharedWorker(url: string, options: any),
// 6) as registered service worker
name: () => window.navigator.serviceWorker
})
By default units are lazy if they are not instantiated. The manager loads them on demand automatically.
But you may do that yourself with:
async start(name: string)
To terminate unit:
async terminate(name?: string) // all by default
Factory to create unit.
Unit(value: class);
Please follow the syntax to have the unit as universal.
Inplace export
export default Unit(
class {
// your ES6+ class
method(...args: any) {
// do things
return something;
}
// may be asynchronous
async method(...args: any) {
// do things
return await something(...args);
}
}
);
or
class MyUnit {
// your ES6+ class
}
export default Unit(MyUnit);
You may also use these methods:
// to initialize unit
async onstart() {
await loading...
}
// to finalize unit
async onterminate() {
await unloading...
}
Use units property to access other units.
Also you have to read about Objects, Transferables and Adapters.
If you'd like to load the unit into the main thread you have to import
it as ES6+ module and instantiate with new
.
Each class has special property to communicate with other units.
units: Object;
To call "method" of "other" unit:
await this.units.other.method(...args: any);
To catch "event" from all units:
this.units.on(event: string, callback: (sender: string, ...args: any) => {
// do things
});
To catch "event" from "other" unit:
this.units.other.on(event: string, callback: (...args: any) => {
// do things
});
To post events:
// "this" will be "sender"
// to all other units
this.units.post(event: string, ...args: any);
// to "other" unit
this.units.other.post(event: string, ...args: any);
To create the main unit with built in manager use the following syntax:
class Main extends Unit(Manager) {
// your main unit code
constructor() {
super(name?: string) // "main" by default
}
}
Being a Unit it extends Manager functionality to orchestrate all other units and helps to write less code.
Due to structured clone algorithm you are restricted to transfer complex types between workers, such as objects with methods or functions.
But with the Uman you can.
If you'd like to export any
object which can not be cloned, just do it. The unit becomes an objects's factory, while the object exports own methods. Technically, the object will live in the unit's thread but you may call it's methods from other threads.
if you'd like to fire events from the object, extend it's class with Emitter class:
class MyObject extends Emitter {
// your code
}
// fo fire event from the object:
fire(event: string, ...args: any);
// to subscribe on event:
// returns unsubscribe function
on(event: string, callback: (...args: any) => {
// do things
})
Usage:
./units/one.js
class MyObject extends Emitter {
method(...args) {
// do things
// fire event
this.fire("event", ...args);
return something;
}
// can be async
async method(...args) {
return await something(...args);
}
}
// as a factory
export default Unit(
class {
MyObject(...args) {
return new MyObject(...args);
}
}
);
And somewhere in your unit:
const { one, two } = this.units;
// call "one" to create objects
const object1 = one.MyObject(...args);
const object2 = one.MyObject(...args);
// subscribe to catch event
const off = object2.on("event", (...args) => {
// do things
});
// do things
const result1 = object1.method(...args);
const result2 = object2.method(...args);
// you my pass them as arguments as well
const object3 = two.method(object1, ...args);
// or
const result3 = object3.method({ object2, ...args });
// unsubscribe
off();
To send transferable objects from one unit to another just pass them as arguments of methods. You may return such objects as well.
The following classes like ArrayBuffer, ImageBitmap and OffscreenCanvas are supported.
Note, object becomes unusable in the thread it was sent from and becomes available to the thread it was sent to.
Example:
one.js
const abuf1 = new ArrayBuffer(size1);
const abuf2 = new ArrayBuffer(size2);
// send buffers
const result = await this.units.two.method(abuf1, abuf2);
// do things with result
two.js
async method(buffer1, buffer2) {
// do things with buffers, reply
return result;
}
Adaptrers are required to control the workers from the main thread.
Note, they are created automatically with the manager.
To extend unit's functionality from the main thread side you may use web worker's methods, like postMessage
and onmessage
to catch events to exchange raw data with the worker thread.
But with the Uman you don't need to.
Usage:
index.js
class Adapter {
progress(persent) {
// do something
}
}
main.add({
unit: {
// see Manager.add
loader: () => new SharedWorker("unit.js"),
adapter: Adapter
}
});
main.units.unit.dothings();
unit.js
export default Unit(
class {
// your ES6+ class
dothings() {
this.progress(0);
// do things...
this.progress(100);
}
}
);
Copyright © 2019-2020 G. Schurovski
Licensed under the Apache-2.0 license.