sabcom
Version:
A TypeScript/Node.js library for inter-thread communication using SharedArrayBuffer with atomic operations for raw buffer data transfer
148 lines (146 loc) • 5.47 kB
JavaScript
export const SEMAPHORE = 0;
export var Semaphore = /*#__PURE__*/ function(Semaphore) {
Semaphore[Semaphore["READY"] = 0] = "READY";
Semaphore[Semaphore["HANDSHAKE"] = 1] = "HANDSHAKE";
Semaphore[Semaphore["PAYLOAD"] = 2] = "PAYLOAD";
return Semaphore;
}({});
export var Handshake = /*#__PURE__*/ function(Handshake) {
Handshake[Handshake["TOTAL_SIZE"] = 1] = "TOTAL_SIZE";
Handshake[Handshake["TOTAL_CHUNKS"] = 2] = "TOTAL_CHUNKS";
return Handshake;
}({});
export var Header = /*#__PURE__*/ function(Header) {
Header[Header["CHUNK_INDEX"] = 1] = "CHUNK_INDEX";
Header[Header["CHUNK_OFFSET"] = 2] = "CHUNK_OFFSET";
Header[Header["CHUNK_SIZE"] = 3] = "CHUNK_SIZE";
return Header;
}({});
export const HEADER_VALUES = 1 + Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;
export const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;
export function* writeGenerator(data, buffer, { timeout = 5000 } = {}) {
const chunkSize = buffer.byteLength - HEADER_SIZE;
const totalSize = data.length;
const totalChunks = Math.ceil(totalSize / chunkSize);
const header = new Int32Array(buffer);
header[1] = totalSize;
header[2] = totalChunks;
Atomics.store(header, SEMAPHORE, 1);
Atomics.notify(header, SEMAPHORE);
try {
const handshakeResult = yield {
target: header,
index: SEMAPHORE,
value: 1,
timeout
};
if (handshakeResult === 'timed-out') {
throw new Error('Reader handshake timeout');
}
const payload = new Uint8Array(buffer, HEADER_SIZE);
for(let i = 0; i < totalChunks; i++){
const start = i * chunkSize;
const end = Math.min(start + chunkSize, totalSize);
const size = end - start;
payload.set(data.subarray(start, end), 0);
header[1] = i;
header[2] = start;
header[3] = size;
Atomics.store(header, SEMAPHORE, 2);
Atomics.notify(header, SEMAPHORE);
const chunkResult = yield {
target: header,
index: SEMAPHORE,
value: 2,
timeout
};
if (chunkResult === 'timed-out') {
throw new Error(`Reader timeout on chunk ${i}/${totalChunks - 1}`);
}
}
} finally{
Atomics.store(header, SEMAPHORE, 0);
}
}
export function* readGenerator(buffer, { timeout = 5000 } = {}) {
const header = new Int32Array(buffer);
const handshakeResult = yield {
target: header,
index: SEMAPHORE,
value: 0,
timeout
};
if (handshakeResult === 'timed-out') {
throw new Error('Handshake timeout');
}
if (header[SEMAPHORE] !== 1) {
throw new Error('Invalid handshake state');
}
const totalSize = header[1];
const totalChunks = header[2];
const data = new Uint8Array(totalSize);
Atomics.store(header, SEMAPHORE, 0);
Atomics.notify(header, SEMAPHORE);
const payload = new Uint8Array(buffer, HEADER_SIZE);
for(let i = 0; i < totalChunks; i++){
const chunkResult = yield {
target: header,
index: SEMAPHORE,
value: 0,
timeout
};
if (chunkResult === 'timed-out') {
throw new Error(`Writer timeout waiting for chunk ${i}`);
}
if (header[SEMAPHORE] !== 2) {
throw new Error(`Expected payload header, received ${Semaphore[header[SEMAPHORE]]}`);
}
const chunkIndex = header[1];
if (i !== chunkIndex) {
throw new Error(`Reader integrity failure for chunk ${chunkIndex} expected ${i}`);
}
const offset = header[2];
const size = header[3];
data.set(payload.subarray(0, size), offset);
Atomics.store(header, SEMAPHORE, 0);
Atomics.notify(header, SEMAPHORE);
}
return data;
}
export const writeSync = (data, buffer, options)=>{
const gen = writeGenerator(data, buffer, options);
let result = gen.next();
while(!result.done){
const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);
result = gen.next(waitResult);
}
};
export const write = async (data, buffer, options)=>{
const gen = writeGenerator(data, buffer, options);
let result = gen.next();
while(!result.done){
const request = result.value;
const waitResult = await Atomics.waitAsync(request.target, request.index, request.value, request.timeout).value;
result = gen.next(waitResult);
}
};
export const readSync = (buffer, options)=>{
const gen = readGenerator(buffer, options);
let result = gen.next();
while(!result.done){
const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);
result = gen.next(waitResult);
}
return result.value;
};
export const read = async (buffer, options)=>{
const gen = readGenerator(buffer, options);
let result = gen.next();
while(!result.done){
const request = result.value;
const waitResult = await Atomics.waitAsync(request.target, request.index, request.value, request.timeout).value;
result = gen.next(waitResult);
}
return result.value;
};
//# sourceMappingURL=index.js.map