UNPKG

starboard-python

Version:
162 lines (148 loc) 5.05 kB
/** * One-way memory, can block a web worker until data from the main thread arrives. * * Web Worker Usage: * 1. Lock "web worker" * 2. Set "shared memory signal" * 3. Notify main thread (Main thread does stuff) * 4. Wait for "shared memory signal" * 5. Read size buffer * 6. Read shared memory * 7. If the size buffer was bigger than the read memory size * 7.1. Set "shared memory signal" * 7.2. Notify main thread (Main thread writes remaining data to shared memory) * 7.3. Wait for "shared memory signal" * 7.4. Read shared memory * 7.5. Go back to step 7. (loop) * 8. Unlock "web worker" * * Main Thread Usage: * 1. Get notification * 2. Do operations * 3. Serialize data * 4. Write size into the size buffer * 5. Write partial data into shared memory * 6. Unlock "shared memory signal" (Worker does stuff) * 7. If not everything has been written to the shared memory yet * 7.1. Get notification * 7.2. Write partial data into shared memory * 7.3. Unlock "shared memory signal" (Worker does stuff) * 7.4. Go back to step 7. (loop) */ export class AsyncMemory { // Reference: https://v8.dev/features/atomics static LOCK_WORKER_INDEX = 0; static LOCK_SIZE_INDEX = 2; static SIZE_INDEX = 4; static UNLOCKED = 0; static LOCKED = 1; readonly sharedLock: SharedArrayBuffer; readonly lockAndSize: Int32Array; readonly sharedMemory: SharedArrayBuffer; readonly memory: Uint8Array; constructor(sharedLock?: SharedArrayBuffer, sharedMemory?: SharedArrayBuffer) { this.sharedLock = sharedLock ?? new SharedArrayBuffer(8 * Int32Array.BYTES_PER_ELEMENT); this.lockAndSize = new Int32Array(this.sharedLock); if (this.lockAndSize.length < 8) { throw new Error("Expected an sharedLock with at least 8x32 bytes"); } this.sharedMemory = sharedMemory ?? new SharedArrayBuffer(1024); this.memory = new Uint8Array(this.sharedMemory); if (this.sharedMemory.byteLength < 1024) { throw new Error("Expected an sharedMemory with at least 1024 bytes"); } } /** * Should be called from the worker thread */ lockWorker() { const oldValue = Atomics.compareExchange( this.lockAndSize, AsyncMemory.LOCK_WORKER_INDEX, AsyncMemory.UNLOCKED, // old value AsyncMemory.LOCKED // new value ); if (oldValue !== AsyncMemory.UNLOCKED) { throw new Error(`Cannot lock worker, the worker has to be unlocked ${AsyncMemory.UNLOCKED} !== ${oldValue}`); } } /** * Should be called from the worker thread */ lockSize() { const oldValue = Atomics.compareExchange( this.lockAndSize, AsyncMemory.LOCK_SIZE_INDEX, AsyncMemory.UNLOCKED, // old value AsyncMemory.LOCKED // new value ); if (oldValue !== AsyncMemory.UNLOCKED) { throw new Error(`Cannot set size flag, the size has to be unlocked ${AsyncMemory.UNLOCKED} !== ${oldValue}`); } } /** * Only legal if the worker is locked */ waitForSize() { Atomics.wait(this.lockAndSize, AsyncMemory.LOCK_SIZE_INDEX, AsyncMemory.LOCKED); } /** * Should be called from the main thread! * Only legal if the worker is locked and the size is locked */ writeSize(value: number) { return Atomics.store(this.lockAndSize, AsyncMemory.SIZE_INDEX, value); } /** * Only legal if the worker is locked but the size is not */ readSize(): number { return Atomics.load(this.lockAndSize, AsyncMemory.SIZE_INDEX); } /** * Should be called from the main thread! */ unlockSize() { const oldValue = Atomics.compareExchange( this.lockAndSize, AsyncMemory.LOCK_SIZE_INDEX, AsyncMemory.LOCKED, // old value AsyncMemory.UNLOCKED // new value ); if (oldValue != AsyncMemory.LOCKED) { throw new Error("Tried to unlock, but was already unlocked"); } Atomics.notify(this.lockAndSize, AsyncMemory.LOCK_SIZE_INDEX); } /** * Ensures that the size gets unlocked */ forceUnlockSize() { const oldValue = Atomics.compareExchange( this.lockAndSize, AsyncMemory.LOCK_SIZE_INDEX, AsyncMemory.LOCKED, // old value AsyncMemory.UNLOCKED // new value ); if (oldValue != AsyncMemory.LOCKED) { // And force unlock it Atomics.store(this.lockAndSize, AsyncMemory.LOCK_SIZE_INDEX, AsyncMemory.UNLOCKED); } Atomics.notify(this.lockAndSize, AsyncMemory.LOCK_SIZE_INDEX); } /** * Should be called from the worker thread! */ unlockWorker() { const oldValue = Atomics.compareExchange( this.lockAndSize, AsyncMemory.LOCK_WORKER_INDEX, AsyncMemory.LOCKED, // old value AsyncMemory.UNLOCKED // new value ); if (oldValue != AsyncMemory.LOCKED) { throw new Error("Tried to unlock, but was already unlocked"); } Atomics.notify(this.lockAndSize, AsyncMemory.LOCK_WORKER_INDEX); } }