UNPKG

@webreflection/coincident

Version:

An Atomics based Proxy to simplify, and synchronize, Worker related tasks

258 lines (244 loc) 7.43 kB
import { minByteLength } from '../utils.js'; import littleEndian from './endian.js'; import types from './types.js'; import { toConstructorName, toSymbolName, toType } from '../utils/shared.js'; const { isArray } = Array; const { isView } = ArrayBuffer; const { isInteger, isFinite } = Number; const { entries } = Object; // numbers const toInt = (data, value) => { if (-128 <= value && value < 128) { resize(data, i + 2); data.setUint8(i++, types.i8); data.setInt8(i++, value); } else if (-32768 <= value && value < 32768) { resize(data, i + 3); data.setUint8(i++, types.i16); data.setInt16(i, value, littleEndian); i += 2; } else if (-2147483648 <= value && value < 2147483648) { resize(data, i + 5); data.setUint8(i++, types.i32); data.setInt32(i, value, littleEndian); i += 4; } else toFloat(data, value); }; const toUint = (data, value) => { if (value < 256) { resize(data, i + 2); data.setUint8(i++, types.u8); data.setUint8(i++, value); } else if (value < 65536) { resize(data, i + 3); data.setUint8(i++, types.u16); data.setUint16(i, value, littleEndian); i += 2; } else if (value < 4294967296) { resize(data, i + 5); data.setUint8(i++, types.u32); data.setUint32(i, value, littleEndian); i += 4; } else toFloat(data, value); }; const toFloat = (data, value) => { resize(data, i + 9); data.setUint8(i++, types.f64); data.setFloat64(i, value, littleEndian); i += 8; }; // cache const toCache = (data, cache, value) => { const index = cache.get(value); if (typeof index === 'number') { resize(data, i + 1); data.setUint8(i++, types.ref); toUint(data, index); return true; } cache.set(value, i); return false; }; // common const toLength = (data, type, length, factor) => { resize(data, i + 10 + length * factor); data.setUint8(i++, type); toUint(data, length); }; // utils const ignore = data => { resize(data, i + 1); data.setUint8(i++, types.ignore); }; const one = (data, type) => { resize(data, i + 1); data.setUint8(i++, type); }; const resize = (data, length) => { if (byteLength < length) { byteLength += length + minByteLength; data.buffer.grow(byteLength); } }; let i = 0, byteLength = i; const flatten = (data, cache, value, type = toType(value)) => { switch (type) { case 'null': { one(data, types.null); break; } case 'object': { if (toCache(data, cache, value)) break; switch (true) { case isArray(value): { const length = value.length; toLength(data, types.array, length, 1); for (let j = 0; j < length; j++) flatten(data, cache, value[j]); break; } case isView(value): { const { BYTES_PER_ELEMENT, buffer, byteOffset, length } = value; one(data, types.view); flatten(data, cache, toConstructorName(value.constructor), 'string'); toUint(data, byteOffset); toUint(data, length !== ((buffer.byteLength - byteOffset) / BYTES_PER_ELEMENT) ? length : 0); if (toCache(data, cache, buffer)) break; value = buffer; } case value instanceof ArrayBuffer: { const byteLength = value.byteLength; const maxByteLength = value.resizable ? value.maxByteLength : 0; toLength(data, types.buffer, byteLength, 1); toUint(data, maxByteLength); if (byteLength) { resize(data, i += byteLength); const ui8a = new Uint8Array(value, 0, byteLength); new Uint8Array(data.buffer).set(ui8a, i - byteLength); } break; } case value instanceof Date: { one(data, types.date); toUint(data, +value); break; } case value instanceof Map: { toLength(data, types.map, value.size, 1); for (const [k, v] of value) { const tk = toType(k); const tv = toType(v); if (tk && tv) { flatten(data, cache, k, tk); flatten(data, cache, v, tv); } else ignore(data); } break; } case value instanceof Set: { toLength(data, types.set, value.size, 1); for (const v of value) { const t = toType(v); if (t) flatten(data, cache, v, t); else ignore(data); } break; } case value instanceof RegExp: { one(data, types.regexp); flatten(data, cache, value.source, 'string'); flatten(data, cache, value.flags, 'string'); break; } case value instanceof Error: { one(data, types.error); flatten(data, cache, value.name, 'string'); flatten(data, cache, value.message, 'string'); flatten(data, cache, value.stack, 'string'); break; } default: { if ('toJSON' in value) { const other = value.toJSON(); if (other === value) { one(data, types.object); toUint(data, 0); break; } value = other; if (toCache(data, cache, value)) break; } const items = entries(value); const length = items.length; toLength(data, types.object, length, 1); for (let j = 0; j < length; j++) { const pair = items[j]; const t = toType(pair[1]); if (t) { flatten(data, cache, pair[0], 'string'); flatten(data, cache, pair[1], t); } else ignore(data); } break; } } break; } case 'boolean': { one(data, value ? types.true : types.false); break; } case 'number': { if (isInteger(value)) toInt(data, value); else if (isFinite(value)) toFloat(data, value); else one(data, types.null); break; } case 'string': { if (toCache(data, cache, value)) break; const length = value.length; toLength(data, types.string, length, 2); for (let j = 0; j < length; j++) { data.setUint16(i, value.charCodeAt(j), littleEndian); i += 2; } break; } case 'bigint': { resize(data, i + 9); data.setUint8(i++, types.b64); data.setBigInt64(i, value, littleEndian); i += 8; break; } case 'symbol': { const name = toSymbolName(value); if (name) { one(data, types.symbol); flatten(data, cache, name, 'string'); break; } } case '': { one(data, types.undefined); break; } /* c8 ignore next */ default: throw new Error(`Unsupported type: ${type}`); } }; export const encode = (value, buffer, { byteOffset = 0 } = {}) => { const current = i = byteOffset; byteLength = buffer.byteLength; flatten(new DataView(buffer), new Map, value); return i - current; }; export const encoder = options => (value, buffer) => encode(value, buffer, options);