Master-slave solution for sharing objects using SharedArrayBuffer fast and safely for OffscreenCanvas.
npm install shared-objects
npm install shared-objects
`
#### General algorithm
MASTER
1) On your tick make your calculations and add dirty objects to MASTER
2) master.flushToMemory()
- lock memory
- update shared array buffers
- unlock
3) back to beginning
SLAVE
1) const changes = slave.sync() for example called in requestAnimationFrame handler.
- lock memory
- update local objects
- unlock
2) update OffscreenCanvas based on changes.
3) back to beginning
#### Anything else?
Complexity of change detection is O(no_of_objects_changed).
Its fast enough for ~50 fps with 100k objects that change every tick/frame.
(Depends on the CPU and million other things of course, but to just give you ball park figure.)
Example of use case
`
// IN Main thread
const main = new ExampleMasterObjectArray(5);
main.initPeriodicFlush(60); // 60 is target FPS, uses setInterval
worker.postMessage({buffers: main.export()});
const obj1 = {x: 1, y: 2};
const obj2 = {x: 3, y: 4};
main.dirtyObject(obj1);
main.dirtyObject(obj2);
main.deleteObject(obj1);
main.replaceObjectAt(0, obj1);
// IN Worker thread
const slave = new ExampleSlaveObjectArray( buffersFromMain ).init();
const f = () => {
const changes = slave.sync(); // Creates local dataset from shared memory. (while doing so memory is locked).
console.log(changes.deleted);
console.log(changes.updated);
requestAnimationFrame(f);
}
f();
`
Example of definition
`
export class ExampleMasterObjectArray extends StateBufferForMaster {
public constructor(maxObjects: number) {
super(maxObjects, 12);
}
protected populateMemory(index: number, obj: MasterObject) {
const p8 = index * this.size8;
const p32 = index * this.size32;
this.view8[p8] = obj.metaId;
this.view8[p8 + 1] = obj.x;
this.view8[p8 + 2] = obj.y;
this.view32[p32 + 1] = obj.sx;
this.view32[p32 + 2] = obj.sy;
}
}
export interface MasterObject {
metaId: number;
x: number;
y: number;
sx: number;
sy: number;
}
export class ExampleSlaveObjectArray extends StateBufferForSlave {
protected exists(index: number) {
return this.view8[index * this.size8] !== 0;
}
protected isSame(index: number, obj: SlaveObject) {
return obj.metaId === this.view8[index * this.size8];
}
protected onNew(index: number): SlaveObject {
const p8 = index * this.size8;
const p32 = index * this.size32;
return {
metaId: this.view8[p8],
x: this.view8[p8 + 1],
y: this.view8[p8 + 2],
sx: this.view32[p32 + 1],
sy: this.view32[p32 + 2]
}
}
protected onUpdate(index: number, obj: SlaveObject): void {
const p8 = index * this.size8;
const p32 = index * this.size32;
obj.x = this.view8[p8 + 1];
obj.y = this.view8[p8 + 2];
obj.sx = this.view32[p32 + 1];
obj.sy = this.view32[p32 + 2];
}
protected onDelete(index: number, obj: SlaveObject): void {
}
}
export interface SlaveObject {
metaId: number;
x: number;
y: number;
sx: number;
sy: number;
}
``