@bnaya/objectbuffer
Advanced tools
Comparing version 0.1.4 to 0.2.0
@@ -1,4 +0,4 @@ | ||
export declare function createObjectBuffer<T = any>(textDecoder: any, textEncoder: any, size: number, initialValue: T): T; | ||
export declare function getUnderlyingArrayBuffer(objectBuffer: any): ArrayBuffer; | ||
export declare function createObjectBufferFromArrayBuffer<T = any>(textDecoder: any, textEncoder: any, arrayBuffer: ArrayBuffer, shouldInitializeArrayBuffer?: boolean): T; | ||
export * from "./internal/api"; | ||
import * as locks from "./internal/locks"; | ||
export { locks }; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -1,20 +0,3 @@ | ||
import { initializeArrayBuffer } from "./internal/store"; | ||
import { objectSaver } from "./internal/objectSaver"; | ||
import { createObjectWrapper, GET_UNDERLYING_ARRAY_BUFFER_SYMBOL } from "./internal/objectWrapper"; | ||
export function createObjectBuffer(textDecoder, textEncoder, size, initialValue) { | ||
const arrayBuffer = new ArrayBuffer(size); | ||
const dataView = initializeArrayBuffer(arrayBuffer); | ||
const { | ||
start | ||
} = objectSaver(textEncoder, dataView, initialValue); | ||
dataView.setUint32(8, start); | ||
return createObjectWrapper(dataView, start, textDecoder, textEncoder, true); | ||
} | ||
export function getUnderlyingArrayBuffer(objectBuffer) { | ||
return objectBuffer[GET_UNDERLYING_ARRAY_BUFFER_SYMBOL]; | ||
} | ||
export function createObjectBufferFromArrayBuffer(textDecoder, textEncoder, arrayBuffer, // set to true if the give array buffer is not one from `getUnderlyingArrayBuffer` | ||
shouldInitializeArrayBuffer = false) { | ||
const dataView = shouldInitializeArrayBuffer ? initializeArrayBuffer(arrayBuffer) : new DataView(arrayBuffer); | ||
return createObjectWrapper(dataView, dataView.getUint32(8), textDecoder, textEncoder); | ||
} | ||
export * from "./internal/api"; | ||
import * as locks from "./internal/locks"; | ||
export { locks }; |
export declare enum ENTRY_TYPE { | ||
NULL = 0, | ||
UNDEFINED = 1, | ||
UNDEFINED = 0, | ||
NULL = 1, | ||
NUMBER = 2, | ||
@@ -18,3 +18,3 @@ BIGINT = 3, | ||
export declare const PRIMITIVE_TYPES: readonly [ENTRY_TYPE.NULL, ENTRY_TYPE.UNDEFINED, ENTRY_TYPE.NUMBER, ENTRY_TYPE.BIGINT, ENTRY_TYPE.UBIGINT, ENTRY_TYPE.BOOLEAN, ENTRY_TYPE.STRING]; | ||
export declare const isPrimitiveEntryType: (v: unknown) => v is ENTRY_TYPE.NULL | ENTRY_TYPE.UNDEFINED | ENTRY_TYPE.NUMBER | ENTRY_TYPE.BIGINT | ENTRY_TYPE.UBIGINT | ENTRY_TYPE.STRING | ENTRY_TYPE.BOOLEAN; | ||
export declare const isPrimitiveEntryType: (v: unknown) => v is ENTRY_TYPE.UNDEFINED | ENTRY_TYPE.NULL | ENTRY_TYPE.NUMBER | ENTRY_TYPE.BIGINT | ENTRY_TYPE.UBIGINT | ENTRY_TYPE.STRING | ENTRY_TYPE.BOOLEAN; | ||
//# sourceMappingURL=entry-types.d.ts.map |
@@ -5,4 +5,4 @@ import { createKnownTypeGuard } from "./utils"; | ||
(function (ENTRY_TYPE) { | ||
ENTRY_TYPE[ENTRY_TYPE["NULL"] = 0] = "NULL"; | ||
ENTRY_TYPE[ENTRY_TYPE["UNDEFINED"] = 1] = "UNDEFINED"; | ||
ENTRY_TYPE[ENTRY_TYPE["UNDEFINED"] = 0] = "UNDEFINED"; | ||
ENTRY_TYPE[ENTRY_TYPE["NULL"] = 1] = "NULL"; | ||
ENTRY_TYPE[ENTRY_TYPE["NUMBER"] = 2] = "NUMBER"; | ||
@@ -9,0 +9,0 @@ ENTRY_TYPE[ENTRY_TYPE["BIGINT"] = 3] = "BIGINT"; |
import { ENTRY_TYPE } from "./entry-types"; | ||
export declare type primitive = string | number | bigint | boolean | undefined | null; | ||
export declare type Entry = NullEntry | NullUndefined | BooleanEntry | StringEntry | NumberEntry | BigIntEntry | UBigIntEntry | ObjectEntry | ObjectPropEntry | ArrayEntry | ArrayItemEntry; | ||
export declare type Entry = NullEntry | NullUndefined | BooleanEntry | StringEntry | NumberEntry | BigIntEntry | UBigIntEntry | ObjectEntry | ObjectPropEntry | ArrayEntry; | ||
export interface NullEntry { | ||
@@ -57,10 +57,5 @@ type: ENTRY_TYPE.NULL; | ||
value: number; | ||
length: number; | ||
allocatedLength: number; | ||
} | ||
export interface ArrayItemEntry { | ||
type: ENTRY_TYPE.ARRAY_ITEM; | ||
value: { | ||
value: number; | ||
next: number; | ||
}; | ||
} | ||
//# sourceMappingURL=interfaces.d.ts.map |
@@ -1,2 +0,2 @@ | ||
export declare function objectSaver(textEncoder: any, dataView: DataView, objectToSave: any): { | ||
export declare function objectSaver(textEncoder: any, dataView: DataView, arrayAdditionalAllocation: number, objectToSave: any): { | ||
start: number; | ||
@@ -3,0 +3,0 @@ length: number; |
@@ -6,3 +6,3 @@ // import { isPrimitive, primitiveValueToEntry } from "./utils"; | ||
export function objectSaver(textEncoder, dataView, objectToSave) { | ||
export function objectSaver(textEncoder, dataView, arrayAdditionalAllocation, objectToSave) { | ||
let totalWrittenBytes = 0; // const writtenLength = 0; | ||
@@ -14,3 +14,3 @@ | ||
for (const [key, value] of objectEntries) { | ||
const rOfValue = saveValue(textEncoder, dataView, value); | ||
const rOfValue = saveValue(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
const objectPropEntry = { | ||
@@ -17,0 +17,0 @@ type: ENTRY_TYPE.OBJECT_PROP, |
@@ -8,3 +8,4 @@ export declare const GET_UNDERLYING_ARRAY_BUFFER_SYMBOL: unique symbol; | ||
private isTopLevel; | ||
constructor(dataView: DataView, entryPointer: number, textDecoder: any, textEncoder: any, isTopLevel: boolean); | ||
private arrayAdditionalAllocation; | ||
constructor(dataView: DataView, entryPointer: number, textDecoder: any, textEncoder: any, isTopLevel: boolean, arrayAdditionalAllocation: number); | ||
getUnderlyingArrayBuffer(): ArrayBuffer; | ||
@@ -24,5 +25,5 @@ get(target: {}, p: PropertyKey): any; | ||
setPrototypeOf(): boolean; | ||
private readonly entry; | ||
private get entry(); | ||
} | ||
export declare function createObjectWrapper<T = any>(dataView: DataView, entryPointer: number, textDecoder: any, textEncoder: any, isTopLevel?: boolean): T; | ||
export declare function createObjectWrapper<T = any>(dataView: DataView, entryPointer: number, textDecoder: any, textEncoder: any, isTopLevel?: boolean, arrayAdditionalAllocation?: number): T; | ||
//# sourceMappingURL=objectWrapper.d.ts.map |
import { readEntry, writeEntry, appendEntry } from "./store"; | ||
import { ENTRY_TYPE, isPrimitiveEntryType } from "./entry-types"; | ||
import { ENTRY_TYPE } from "./entry-types"; | ||
import { findObjectPropertyEntry, getObjectPropertiesEntries, deleteObjectPropertyEntryByKey, findLastObjectPropertyEntry } from "./objectWrapperHelpers"; | ||
import { saveValue } from "./saveValue"; | ||
import { entryToFinalJavaScriptValue } from "./entryToFinalJavaScriptValue"; | ||
export const GET_UNDERLYING_ARRAY_BUFFER_SYMBOL = Symbol("GET_UNDERLYING_ARRAY_BUFFER_SYMBOL"); | ||
export class ObjectWrapper { | ||
constructor(dataView, entryPointer, textDecoder, textEncoder, isTopLevel) { | ||
constructor(dataView, entryPointer, textDecoder, textEncoder, isTopLevel, arrayAdditionalAllocation) { | ||
this.dataView = dataView; | ||
@@ -13,2 +14,3 @@ this.entryPointer = entryPointer; | ||
this.isTopLevel = isTopLevel; | ||
this.arrayAdditionalAllocation = arrayAdditionalAllocation; | ||
} | ||
@@ -44,20 +46,3 @@ | ||
const [valueEntry] = readEntry(this.dataView, foundEntry[1].value.value, this.textDecoder); | ||
if (valueEntry.type === ENTRY_TYPE.NULL) { | ||
return null; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.UNDEFINED) { | ||
return undefined; | ||
} | ||
if (isPrimitiveEntryType(valueEntry.type)) { | ||
return valueEntry.value; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.OBJECT) { | ||
return createObjectWrapper(this.dataView, foundEntry[1].value.value, this.textDecoder, this.textEncoder); | ||
} | ||
throw new Error("unsupported yet"); | ||
return entryToFinalJavaScriptValue(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, valueEntry, foundEntry[1].value.value); | ||
} | ||
@@ -102,3 +87,3 @@ | ||
start: newValueEntryPointer | ||
} = saveValue(this.textEncoder, this.dataView, value); | ||
} = saveValue(this.textEncoder, this.dataView, this.arrayAdditionalAllocation, value); | ||
const foundPropEntry = findObjectPropertyEntry(this.dataView, this.entryPointer, p, this.textDecoder); // new prop | ||
@@ -180,6 +165,6 @@ | ||
} | ||
export function createObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel = false) { | ||
export function createObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel = false, arrayAdditionalAllocation = 50) { | ||
return new Proxy({ | ||
bla: 1 | ||
}, new ObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel)); | ||
objectBufferWrapper: "objectBufferWrapper" | ||
}, new ObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel, arrayAdditionalAllocation)); | ||
} |
@@ -1,2 +0,2 @@ | ||
export declare function saveValue(textEncoder: any, dataView: DataView, value: any): { | ||
export declare function saveValue(textEncoder: any, dataView: DataView, arrayAdditionalAllocation: number, value: any): { | ||
start: number; | ||
@@ -3,0 +3,0 @@ length: number; |
import { primitiveValueToEntry, isPrimitive } from "./utils"; | ||
import { appendEntry } from "./store"; | ||
import { objectSaver } from "./objectSaver"; | ||
export function saveValue(textEncoder, dataView, value) { | ||
import { arraySaver } from "./arraySaver"; | ||
export function saveValue(textEncoder, dataView, arrayAdditionalAllocation, value) { | ||
let totalWrittenBytes = 0; | ||
@@ -16,2 +17,9 @@ let valuePointer = 0; | ||
totalWrittenBytes += length; | ||
} else if (Array.isArray(value)) { | ||
const { | ||
start, | ||
length | ||
} = arraySaver(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
valuePointer = start; | ||
totalWrittenBytes += length; | ||
} else if (typeof value === "object") { | ||
@@ -21,3 +29,3 @@ const { | ||
length | ||
} = objectSaver(textEncoder, dataView, value); | ||
} = objectSaver(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
valuePointer = start; | ||
@@ -24,0 +32,0 @@ totalWrittenBytes += length; |
@@ -26,2 +26,3 @@ import { Entry } from "./interfaces"; | ||
textDecoder: any): [Entry, number]; | ||
export declare function reserveMemory(dataView: DataView, length: number): number; | ||
//# sourceMappingURL=store.d.ts.map |
import { ENTRY_TYPE } from "./entry-types"; | ||
import { arrayBufferCopyTo } from "./utils"; | ||
const DEFAULT_ARRAY_BUFFER_SIZE = 10 ^ 6; | ||
@@ -11,7 +12,9 @@ export class Store { | ||
export function initializeArrayBuffer(arrayBuffer) { | ||
const dataView = new DataView(arrayBuffer); // End of data pointer | ||
const dataView = new DataView(arrayBuffer); // global lock | ||
dataView.setUint32(0, 16); // first entry pointer | ||
dataView.setInt32(0, 0); // End of data pointer / first free byte | ||
dataView.setUint32(8, 16); | ||
dataView.setUint32(8, 24); // first entry pointer | ||
dataView.setUint32(16, 24); | ||
return dataView; | ||
@@ -93,3 +96,14 @@ } | ||
case ENTRY_TYPE.ARRAY: | ||
dataView.setUint32(cursor, entry.value); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
dataView.setUint32(cursor, entry.length); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
dataView.setUint32(cursor, entry.allocatedLength); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
break; | ||
default: | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
throw new Error(ENTRY_TYPE[entry.type] + " Not implemented yet"); | ||
@@ -106,5 +120,5 @@ } | ||
// End of data pointer | ||
const firstFreeByte = dataView.getUint32(0); | ||
const firstFreeByte = dataView.getUint32(8); | ||
const written = writeEntry(dataView, firstFreeByte, entry, textEncoder); | ||
dataView.setUint32(0, firstFreeByte + written); | ||
dataView.setUint32(8, firstFreeByte + written); | ||
return { | ||
@@ -148,6 +162,9 @@ start: firstFreeByte, | ||
const stringLength = dataView.getUint16(cursor); | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; | ||
entry.value = textDecoder.decode( // this wrapping is needed until: | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
new Uint8Array(dataView.buffer.slice(cursor, cursor + stringLength))); | ||
// eslint-disable-next-line no-case-declarations | ||
const tempAB = new ArrayBuffer(stringLength); | ||
arrayBufferCopyTo(dataView.buffer, cursor, stringLength, tempAB, 0); | ||
entry.value = textDecoder.decode(tempAB); | ||
cursor += stringLength; | ||
@@ -174,8 +191,11 @@ break; | ||
const keyStringLength = dataView.getUint16(cursor); | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // eslint-disable-next-line no-case-declarations | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
// eslint-disable-next-line no-case-declarations | ||
const tempAB2 = new ArrayBuffer(keyStringLength); | ||
arrayBufferCopyTo(dataView.buffer, cursor, keyStringLength, tempAB2, 0); // eslint-disable-next-line no-case-declarations | ||
const objectPropsValue = { | ||
key: textDecoder.decode( // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
new Uint8Array(dataView.buffer.slice(cursor, cursor + keyStringLength))), | ||
key: textDecoder.decode(tempAB2), | ||
value: dataView.getUint32(cursor + keyStringLength), | ||
@@ -188,2 +208,11 @@ next: dataView.getUint32(cursor + keyStringLength + Uint32Array.BYTES_PER_ELEMENT) | ||
case ENTRY_TYPE.ARRAY: | ||
entry.value = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
entry.length = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
entry.allocatedLength = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
break; | ||
default: | ||
@@ -194,2 +223,7 @@ throw new Error(ENTRY_TYPE[entryType] + " Not implemented yet"); | ||
return [entry, cursor - startingCursor]; | ||
} | ||
export function reserveMemory(dataView, length) { | ||
const firstFreeByte = dataView.getUint32(8); | ||
dataView.setUint32(8, firstFreeByte + length); | ||
return firstFreeByte; | ||
} |
@@ -6,2 +6,3 @@ import { primitive, Entry } from "./interfaces"; | ||
export declare function invariant(condition: boolean, message: string): void; | ||
export declare function arrayBufferCopyTo(origin: ArrayBuffer, startByte: number, length: number, target: ArrayBuffer, toTargetByte: number): void; | ||
//# sourceMappingURL=utils.d.ts.map |
@@ -66,2 +66,10 @@ import { ENTRY_TYPE } from "./entry-types"; | ||
} | ||
} | ||
export function arrayBufferCopyTo(origin, startByte, length, target, toTargetByte) { | ||
const copyFrom = new Uint8Array(origin); | ||
const copyTo = new Uint8Array(target); | ||
for (let i = 0; i < length; i += 1) { | ||
copyTo[toTargetByte + i] = copyFrom[startByte + i]; | ||
} | ||
} |
@@ -1,4 +0,4 @@ | ||
export declare function createObjectBuffer<T = any>(textDecoder: any, textEncoder: any, size: number, initialValue: T): T; | ||
export declare function getUnderlyingArrayBuffer(objectBuffer: any): ArrayBuffer; | ||
export declare function createObjectBufferFromArrayBuffer<T = any>(textDecoder: any, textEncoder: any, arrayBuffer: ArrayBuffer, shouldInitializeArrayBuffer?: boolean): T; | ||
export * from "./internal/api"; | ||
import * as locks from "./internal/locks"; | ||
export { locks }; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -70,8 +70,16 @@ 'use strict'; | ||
} | ||
function arrayBufferCopyTo(origin, startByte, length, target, toTargetByte) { | ||
const copyFrom = new Uint8Array(origin); | ||
const copyTo = new Uint8Array(target); | ||
for (let i = 0; i < length; i += 1) { | ||
copyTo[toTargetByte + i] = copyFrom[startByte + i]; | ||
} | ||
} | ||
let ENTRY_TYPE; | ||
(function (ENTRY_TYPE) { | ||
ENTRY_TYPE[ENTRY_TYPE["NULL"] = 0] = "NULL"; | ||
ENTRY_TYPE[ENTRY_TYPE["UNDEFINED"] = 1] = "UNDEFINED"; | ||
ENTRY_TYPE[ENTRY_TYPE["UNDEFINED"] = 0] = "UNDEFINED"; | ||
ENTRY_TYPE[ENTRY_TYPE["NULL"] = 1] = "NULL"; | ||
ENTRY_TYPE[ENTRY_TYPE["NUMBER"] = 2] = "NUMBER"; | ||
@@ -95,7 +103,9 @@ ENTRY_TYPE[ENTRY_TYPE["BIGINT"] = 3] = "BIGINT"; | ||
function initializeArrayBuffer(arrayBuffer) { | ||
const dataView = new DataView(arrayBuffer); // End of data pointer | ||
const dataView = new DataView(arrayBuffer); // global lock | ||
dataView.setUint32(0, 16); // first entry pointer | ||
dataView.setInt32(0, 0); // End of data pointer / first free byte | ||
dataView.setUint32(8, 16); | ||
dataView.setUint32(8, 24); // first entry pointer | ||
dataView.setUint32(16, 24); | ||
return dataView; | ||
@@ -177,3 +187,14 @@ } | ||
case ENTRY_TYPE.ARRAY: | ||
dataView.setUint32(cursor, entry.value); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
dataView.setUint32(cursor, entry.length); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
dataView.setUint32(cursor, entry.allocatedLength); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
break; | ||
default: | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
throw new Error(ENTRY_TYPE[entry.type] + " Not implemented yet"); | ||
@@ -190,5 +211,5 @@ } | ||
// End of data pointer | ||
const firstFreeByte = dataView.getUint32(0); | ||
const firstFreeByte = dataView.getUint32(8); | ||
const written = writeEntry(dataView, firstFreeByte, entry, textEncoder); | ||
dataView.setUint32(0, firstFreeByte + written); | ||
dataView.setUint32(8, firstFreeByte + written); | ||
return { | ||
@@ -232,6 +253,9 @@ start: firstFreeByte, | ||
const stringLength = dataView.getUint16(cursor); | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; | ||
entry.value = textDecoder.decode( // this wrapping is needed until: | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
new Uint8Array(dataView.buffer.slice(cursor, cursor + stringLength))); | ||
// eslint-disable-next-line no-case-declarations | ||
const tempAB = new ArrayBuffer(stringLength); | ||
arrayBufferCopyTo(dataView.buffer, cursor, stringLength, tempAB, 0); | ||
entry.value = textDecoder.decode(tempAB); | ||
cursor += stringLength; | ||
@@ -258,8 +282,11 @@ break; | ||
const keyStringLength = dataView.getUint16(cursor); | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // eslint-disable-next-line no-case-declarations | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
// eslint-disable-next-line no-case-declarations | ||
const tempAB2 = new ArrayBuffer(keyStringLength); | ||
arrayBufferCopyTo(dataView.buffer, cursor, keyStringLength, tempAB2, 0); // eslint-disable-next-line no-case-declarations | ||
const objectPropsValue = { | ||
key: textDecoder.decode( // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
new Uint8Array(dataView.buffer.slice(cursor, cursor + keyStringLength))), | ||
key: textDecoder.decode(tempAB2), | ||
value: dataView.getUint32(cursor + keyStringLength), | ||
@@ -272,2 +299,11 @@ next: dataView.getUint32(cursor + keyStringLength + Uint32Array.BYTES_PER_ELEMENT) | ||
case ENTRY_TYPE.ARRAY: | ||
entry.value = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
entry.length = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
entry.allocatedLength = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
break; | ||
default: | ||
@@ -279,5 +315,36 @@ throw new Error(ENTRY_TYPE[entryType] + " Not implemented yet"); | ||
} | ||
function reserveMemory(dataView, length) { | ||
const firstFreeByte = dataView.getUint32(8); | ||
dataView.setUint32(8, firstFreeByte + length); | ||
return firstFreeByte; | ||
} | ||
function saveValue(textEncoder, dataView, value) { | ||
function arraySaver(textEncoder, dataView, arrayAdditionalAllocation, arrayToSave) { | ||
let totalWrittenBytes = 0; | ||
let memoryForPointersCursor = reserveMemory(dataView, (arrayToSave.length + arrayAdditionalAllocation) * Uint32Array.BYTES_PER_ELEMENT); | ||
totalWrittenBytes += (arrayToSave.length + arrayAdditionalAllocation) * Uint32Array.BYTES_PER_ELEMENT; | ||
const arrayStartEntry = { | ||
type: ENTRY_TYPE.ARRAY, | ||
value: memoryForPointersCursor, | ||
allocatedLength: arrayToSave.length + arrayAdditionalAllocation, | ||
length: arrayToSave.length | ||
}; | ||
for (const item of arrayToSave) { | ||
const rOfValue = saveValue(textEncoder, dataView, arrayAdditionalAllocation, item); | ||
dataView.setUint32(memoryForPointersCursor, rOfValue.start); | ||
memoryForPointersCursor += Uint32Array.BYTES_PER_ELEMENT; | ||
totalWrittenBytes += rOfValue.length; | ||
} | ||
const arrayEntryAppendResult = appendEntry(dataView, arrayStartEntry, textEncoder); | ||
totalWrittenBytes += arrayEntryAppendResult.length; | ||
return { | ||
start: arrayEntryAppendResult.start, | ||
length: totalWrittenBytes | ||
}; | ||
} | ||
function saveValue(textEncoder, dataView, arrayAdditionalAllocation, value) { | ||
let totalWrittenBytes = 0; | ||
let valuePointer = 0; | ||
@@ -293,2 +360,9 @@ | ||
totalWrittenBytes += length; | ||
} else if (Array.isArray(value)) { | ||
const { | ||
start, | ||
length | ||
} = arraySaver(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
valuePointer = start; | ||
totalWrittenBytes += length; | ||
} else if (typeof value === "object") { | ||
@@ -298,3 +372,3 @@ const { | ||
length | ||
} = objectSaver(textEncoder, dataView, value); | ||
} = objectSaver(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
valuePointer = start; | ||
@@ -314,3 +388,3 @@ totalWrittenBytes += length; | ||
function objectSaver(textEncoder, dataView, objectToSave) { | ||
function objectSaver(textEncoder, dataView, arrayAdditionalAllocation, objectToSave) { | ||
let totalWrittenBytes = 0; // const writtenLength = 0; | ||
@@ -322,3 +396,3 @@ | ||
for (const [key, value] of objectEntries) { | ||
const rOfValue = saveValue(textEncoder, dataView, value); | ||
const rOfValue = saveValue(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
const objectPropEntry = { | ||
@@ -466,5 +540,274 @@ type: ENTRY_TYPE.OBJECT_PROP, | ||
// isolated due to parser changes ts 3.7 | ||
// https://github.com/microsoft/TypeScript/pull/32695 | ||
// eslint-disable-next-line prettier/prettier | ||
// export function assertNonNull<T>(v: T): asserts v is NonNullable<T> { | ||
// if (v === undefined || v === null) { | ||
// throw new Error("assertNonNull"); | ||
// } | ||
// } | ||
/** | ||
* @template T | ||
* @param {T} v | ||
* @return {asserts v is NonNullable<T>} | ||
*/ | ||
function assertNonNull(v) { | ||
if (v === undefined || v === null) { | ||
throw new Error("assertNonNull"); | ||
} | ||
} | ||
function arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry) { | ||
const [arrayEntry] = readEntry(dataView, pointerToArrayEntry, textDecoder); | ||
return arrayEntry; | ||
} | ||
function arrayGetPointersToValue(dataView, textDecoder, pointerToArrayEntry, indexToGet) { | ||
const metadata = arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry); // out of bound | ||
if (indexToGet >= metadata.length) { | ||
return undefined; | ||
} | ||
const pointerToThePointer = metadata.value + indexToGet * Uint32Array.BYTES_PER_ELEMENT; | ||
const pointer = dataView.getUint32(pointerToThePointer); | ||
return { | ||
pointer, | ||
pointerToThePointer | ||
}; | ||
} | ||
function getFinalValueAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToGet) { | ||
const pointers = arrayGetPointersToValue(dataView, textDecoder, pointerToArrayEntry, indexToGet); | ||
if (pointers === undefined) { | ||
return undefined; | ||
} | ||
const entry = readEntry(dataView, pointers.pointer, textDecoder); | ||
return entryToFinalJavaScriptValue(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, entry[0], pointers.pointer); | ||
} | ||
function setValuePointerAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToSet, pointerToEntry) { | ||
const metadata = arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry); | ||
if (indexToSet >= metadata.length) { | ||
// we need to re-allocate the array in a new and bigger place | ||
if (indexToSet >= metadata.allocatedLength) { | ||
reallocateArray(dataView, textDecoder, textEncoder, pointerToArrayEntry, indexToSet + 1 + arrayAdditionalAllocation, indexToSet + 1); | ||
} else { | ||
// no need to re-allocated, just push the length forward | ||
writeEntry(dataView, pointerToArrayEntry, { | ||
type: ENTRY_TYPE.ARRAY, | ||
value: metadata.value, | ||
allocatedLength: metadata.allocatedLength, | ||
length: indexToSet + 1 | ||
}, textEncoder); | ||
} | ||
} | ||
const pointers = arrayGetPointersToValue(dataView, textDecoder, pointerToArrayEntry, indexToSet); | ||
assertNonNull(pointers); | ||
dataView.setUint32(pointers.pointerToThePointer, pointerToEntry); | ||
} | ||
function setValueAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToSet, value) { | ||
const saveValueResult = saveValue(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
setValuePointerAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToSet, saveValueResult.start); | ||
} | ||
function reallocateArray(dataView, textDecoder, textEncoder, pointerToArrayEntry, newAllocatedLength, newLength) { | ||
const metadata = arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry); | ||
const newArrayValueLocation = reserveMemory(dataView, newAllocatedLength * Uint32Array.BYTES_PER_ELEMENT); | ||
for (let memoryToCopyIndex = 0; memoryToCopyIndex < metadata.length; memoryToCopyIndex += 1) { | ||
dataView.setUint32(newArrayValueLocation + memoryToCopyIndex * Uint32Array.BYTES_PER_ELEMENT, dataView.getUint32(metadata.value + memoryToCopyIndex * Uint32Array.BYTES_PER_ELEMENT)); | ||
} | ||
writeEntry(dataView, pointerToArrayEntry, { | ||
type: ENTRY_TYPE.ARRAY, | ||
value: newArrayValueLocation, | ||
allocatedLength: newAllocatedLength, | ||
length: newLength | ||
}, textEncoder); | ||
} | ||
class ArrayWrapper { | ||
constructor(dataView, arrayAdditionalAllocation, entryPointer, textDecoder, textEncoder) { | ||
this.dataView = dataView; | ||
this.arrayAdditionalAllocation = arrayAdditionalAllocation; | ||
this.entryPointer = entryPointer; | ||
this.textDecoder = textDecoder; | ||
this.textEncoder = textEncoder; | ||
} | ||
get(target, p) { | ||
if (p in this && p !== "constructor") { | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
return this[p]; | ||
} | ||
if (p === "length") { | ||
return arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} | ||
if (typeof p === "string" || typeof p === "number") { | ||
const asInt = typeof p === "string" ? Number.parseInt(p, 10) : p; | ||
if (Number.isSafeInteger(asInt)) { | ||
return getFinalValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, asInt); | ||
} | ||
} // eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
return target[p]; | ||
} | ||
deleteProperty(target, p) { | ||
throw new Error("unsupported"); | ||
} | ||
enumerate() { | ||
throw new Error("unsupported"); | ||
} | ||
ownKeys() { | ||
const length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
return [...new Array(length).keys()]; | ||
} | ||
getOwnPropertyDescriptor() { | ||
return { | ||
configurable: true, | ||
enumerable: true | ||
}; | ||
} | ||
has(target, p) { | ||
throw new Error("unsupported"); | ||
} | ||
set(target, p, value) { | ||
setValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, Number.parseInt(p, 10), value); | ||
return true; | ||
} | ||
isExtensible() { | ||
return true; | ||
} | ||
preventExtensions() { | ||
throw new Error("unsupported"); | ||
} | ||
setPrototypeOf() { | ||
throw new Error("unsupported"); | ||
} | ||
*entries() { | ||
let index = 0; | ||
let length = 0; | ||
do { | ||
yield [index, getFinalValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, index)]; | ||
index += 1; | ||
length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} while (index < length); | ||
} | ||
*keys() { | ||
let index = 0; | ||
let length = 0; | ||
do { | ||
yield index; | ||
index += 1; | ||
length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} while (index < length); | ||
} | ||
*values() { | ||
let index = 0; | ||
let length = 0; | ||
do { | ||
yield getFinalValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, index); | ||
index += 1; | ||
length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} while (index < length); | ||
} | ||
get [Symbol.iterator]() { | ||
return this.values; | ||
} // in-place methods | ||
shift() { | ||
throw new Error("unsupported"); | ||
} | ||
unshift() { | ||
throw new Error("unsupported"); | ||
} | ||
pop() { | ||
throw new Error("unsupported"); | ||
} | ||
push() { | ||
throw new Error("unsupported"); | ||
} | ||
sort() { | ||
throw new Error("unsupported"); | ||
} | ||
splice() { | ||
throw new Error("unsupported"); | ||
} // // copy methods | ||
// private concat() { | ||
// throw new Error("unsupported"); | ||
// } | ||
// private slice() { | ||
// throw new Error("unsupported"); | ||
// } | ||
// private map() { | ||
// throw new Error("unsupported"); | ||
// } | ||
// private reduce() { | ||
// throw new Error("unsupported"); | ||
// } | ||
} | ||
function createArrayWrapper(dataView, arrayAdditionalAllocation, entryPointer, textDecoder, textEncoder) { | ||
return new Proxy([], new ArrayWrapper(dataView, arrayAdditionalAllocation, entryPointer, textDecoder, textEncoder)); | ||
} | ||
function entryToFinalJavaScriptValue(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, valueEntry, pointerToEntry) { | ||
if (valueEntry.type === ENTRY_TYPE.NULL) { | ||
return null; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.UNDEFINED) { | ||
return undefined; | ||
} | ||
if (isPrimitiveEntryType(valueEntry.type)) { | ||
return valueEntry.value; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.OBJECT) { | ||
return createObjectWrapper(dataView, pointerToEntry, textDecoder, textEncoder, false, arrayAdditionalAllocation); | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.ARRAY) { | ||
return createArrayWrapper(dataView, arrayAdditionalAllocation, pointerToEntry, textDecoder, textEncoder); | ||
} | ||
throw new Error("unsupported yet"); | ||
} | ||
const GET_UNDERLYING_ARRAY_BUFFER_SYMBOL = Symbol("GET_UNDERLYING_ARRAY_BUFFER_SYMBOL"); | ||
class ObjectWrapper { | ||
constructor(dataView, entryPointer, textDecoder, textEncoder, isTopLevel) { | ||
constructor(dataView, entryPointer, textDecoder, textEncoder, isTopLevel, arrayAdditionalAllocation) { | ||
this.dataView = dataView; | ||
@@ -475,2 +818,3 @@ this.entryPointer = entryPointer; | ||
this.isTopLevel = isTopLevel; | ||
this.arrayAdditionalAllocation = arrayAdditionalAllocation; | ||
} | ||
@@ -506,20 +850,3 @@ | ||
const [valueEntry] = readEntry(this.dataView, foundEntry[1].value.value, this.textDecoder); | ||
if (valueEntry.type === ENTRY_TYPE.NULL) { | ||
return null; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.UNDEFINED) { | ||
return undefined; | ||
} | ||
if (isPrimitiveEntryType(valueEntry.type)) { | ||
return valueEntry.value; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.OBJECT) { | ||
return createObjectWrapper(this.dataView, foundEntry[1].value.value, this.textDecoder, this.textEncoder); | ||
} | ||
throw new Error("unsupported yet"); | ||
return entryToFinalJavaScriptValue(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, valueEntry, foundEntry[1].value.value); | ||
} | ||
@@ -564,3 +891,3 @@ | ||
start: newValueEntryPointer | ||
} = saveValue(this.textEncoder, this.dataView, value); | ||
} = saveValue(this.textEncoder, this.dataView, this.arrayAdditionalAllocation, value); | ||
const foundPropEntry = findObjectPropertyEntry(this.dataView, this.entryPointer, p, this.textDecoder); // new prop | ||
@@ -642,15 +969,21 @@ | ||
} | ||
function createObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel = false) { | ||
function createObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel = false, arrayAdditionalAllocation = 50) { | ||
return new Proxy({ | ||
bla: 1 | ||
}, new ObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel)); | ||
objectBufferWrapper: "objectBufferWrapper" | ||
}, new ObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel, arrayAdditionalAllocation)); | ||
} | ||
function createObjectBuffer(textDecoder, textEncoder, size, initialValue) { | ||
const arrayBuffer = new ArrayBuffer(size); | ||
function createObjectBuffer(textDecoder, textEncoder, size, initialValue, { | ||
arrayAdditionalAllocation, | ||
useSharedArrayBuffer | ||
} = { | ||
arrayAdditionalAllocation: 0, | ||
useSharedArrayBuffer: false | ||
}) { | ||
const arrayBuffer = new (useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer)(size); | ||
const dataView = initializeArrayBuffer(arrayBuffer); | ||
const { | ||
start | ||
} = objectSaver(textEncoder, dataView, initialValue); | ||
dataView.setUint32(8, start); | ||
} = objectSaver(textEncoder, dataView, arrayAdditionalAllocation || 0, initialValue); | ||
dataView.setUint32(16, start); | ||
return createObjectWrapper(dataView, start, textDecoder, textEncoder, true); | ||
@@ -662,9 +995,63 @@ } | ||
function createObjectBufferFromArrayBuffer(textDecoder, textEncoder, arrayBuffer, // set to true if the give array buffer is not one from `getUnderlyingArrayBuffer` | ||
shouldInitializeArrayBuffer = false) { | ||
shouldInitializeArrayBuffer = false, { | ||
arrayAdditionalAllocation | ||
} = { | ||
arrayAdditionalAllocation: 0 | ||
}) { | ||
const dataView = shouldInitializeArrayBuffer ? initializeArrayBuffer(arrayBuffer) : new DataView(arrayBuffer); | ||
return createObjectWrapper(dataView, dataView.getUint32(8), textDecoder, textEncoder); | ||
return createObjectWrapper(dataView, dataView.getUint32(16), textDecoder, textEncoder, true, arrayAdditionalAllocation); | ||
} | ||
/* global Atomics */ | ||
// I have no idea if its really works as i think it should | ||
function getLock(playerId, sab) { | ||
invariant(playerId > 0, "playerId must be more than 0"); | ||
const int32 = new Int32Array(sab); | ||
const oldValue = Atomics.compareExchange(int32, 0, 0, playerId); | ||
return oldValue === 0; | ||
} | ||
function releaseLock(playerId, sab) { | ||
const int32 = new Int32Array(sab); | ||
const oldValue = Atomics.compareExchange(int32, 0, playerId, 0); // we've released a lock. lets tell them about it | ||
if (oldValue === playerId) { | ||
Atomics.notify(int32, 0, +Infinity); | ||
return true; | ||
} | ||
return false; | ||
} | ||
function waitForLock(playerId, sab, timeout) { | ||
const int32 = new Int32Array(sab); | ||
const oldValue = Atomics.compareExchange(int32, 0, 0, playerId); | ||
if (oldValue === 0) { | ||
return "have-lock"; | ||
} | ||
const r = Atomics.wait(int32, 0, oldValue, timeout); | ||
if (r === "not-equal") { | ||
if (Atomics.compareExchange(int32, 0, 0, playerId) === 0) { | ||
return "have-lock"; | ||
} else { | ||
return "miss-lock"; | ||
} | ||
} else if (r === "timed-out") { | ||
return "timed-out"; | ||
} else { | ||
return "no-lock"; | ||
} | ||
} | ||
var locks = /*#__PURE__*/Object.freeze({ | ||
getLock: getLock, | ||
releaseLock: releaseLock, | ||
waitForLock: waitForLock | ||
}); | ||
exports.createObjectBuffer = createObjectBuffer; | ||
exports.createObjectBufferFromArrayBuffer = createObjectBufferFromArrayBuffer; | ||
exports.getUnderlyingArrayBuffer = getUnderlyingArrayBuffer; | ||
exports.locks = locks; |
@@ -66,8 +66,16 @@ const primitives = ["string", "number", "bigint", "boolean", "undefined"]; | ||
} | ||
function arrayBufferCopyTo(origin, startByte, length, target, toTargetByte) { | ||
const copyFrom = new Uint8Array(origin); | ||
const copyTo = new Uint8Array(target); | ||
for (let i = 0; i < length; i += 1) { | ||
copyTo[toTargetByte + i] = copyFrom[startByte + i]; | ||
} | ||
} | ||
let ENTRY_TYPE; | ||
(function (ENTRY_TYPE) { | ||
ENTRY_TYPE[ENTRY_TYPE["NULL"] = 0] = "NULL"; | ||
ENTRY_TYPE[ENTRY_TYPE["UNDEFINED"] = 1] = "UNDEFINED"; | ||
ENTRY_TYPE[ENTRY_TYPE["UNDEFINED"] = 0] = "UNDEFINED"; | ||
ENTRY_TYPE[ENTRY_TYPE["NULL"] = 1] = "NULL"; | ||
ENTRY_TYPE[ENTRY_TYPE["NUMBER"] = 2] = "NUMBER"; | ||
@@ -91,7 +99,9 @@ ENTRY_TYPE[ENTRY_TYPE["BIGINT"] = 3] = "BIGINT"; | ||
function initializeArrayBuffer(arrayBuffer) { | ||
const dataView = new DataView(arrayBuffer); // End of data pointer | ||
const dataView = new DataView(arrayBuffer); // global lock | ||
dataView.setUint32(0, 16); // first entry pointer | ||
dataView.setInt32(0, 0); // End of data pointer / first free byte | ||
dataView.setUint32(8, 16); | ||
dataView.setUint32(8, 24); // first entry pointer | ||
dataView.setUint32(16, 24); | ||
return dataView; | ||
@@ -173,3 +183,14 @@ } | ||
case ENTRY_TYPE.ARRAY: | ||
dataView.setUint32(cursor, entry.value); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
dataView.setUint32(cursor, entry.length); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
dataView.setUint32(cursor, entry.allocatedLength); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
break; | ||
default: | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
throw new Error(ENTRY_TYPE[entry.type] + " Not implemented yet"); | ||
@@ -186,5 +207,5 @@ } | ||
// End of data pointer | ||
const firstFreeByte = dataView.getUint32(0); | ||
const firstFreeByte = dataView.getUint32(8); | ||
const written = writeEntry(dataView, firstFreeByte, entry, textEncoder); | ||
dataView.setUint32(0, firstFreeByte + written); | ||
dataView.setUint32(8, firstFreeByte + written); | ||
return { | ||
@@ -228,6 +249,9 @@ start: firstFreeByte, | ||
const stringLength = dataView.getUint16(cursor); | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; | ||
entry.value = textDecoder.decode( // this wrapping is needed until: | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
new Uint8Array(dataView.buffer.slice(cursor, cursor + stringLength))); | ||
// eslint-disable-next-line no-case-declarations | ||
const tempAB = new ArrayBuffer(stringLength); | ||
arrayBufferCopyTo(dataView.buffer, cursor, stringLength, tempAB, 0); | ||
entry.value = textDecoder.decode(tempAB); | ||
cursor += stringLength; | ||
@@ -254,8 +278,11 @@ break; | ||
const keyStringLength = dataView.getUint16(cursor); | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // eslint-disable-next-line no-case-declarations | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
// eslint-disable-next-line no-case-declarations | ||
const tempAB2 = new ArrayBuffer(keyStringLength); | ||
arrayBufferCopyTo(dataView.buffer, cursor, keyStringLength, tempAB2, 0); // eslint-disable-next-line no-case-declarations | ||
const objectPropsValue = { | ||
key: textDecoder.decode( // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
new Uint8Array(dataView.buffer.slice(cursor, cursor + keyStringLength))), | ||
key: textDecoder.decode(tempAB2), | ||
value: dataView.getUint32(cursor + keyStringLength), | ||
@@ -268,2 +295,11 @@ next: dataView.getUint32(cursor + keyStringLength + Uint32Array.BYTES_PER_ELEMENT) | ||
case ENTRY_TYPE.ARRAY: | ||
entry.value = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
entry.length = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
entry.allocatedLength = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
break; | ||
default: | ||
@@ -275,5 +311,36 @@ throw new Error(ENTRY_TYPE[entryType] + " Not implemented yet"); | ||
} | ||
function reserveMemory(dataView, length) { | ||
const firstFreeByte = dataView.getUint32(8); | ||
dataView.setUint32(8, firstFreeByte + length); | ||
return firstFreeByte; | ||
} | ||
function saveValue(textEncoder, dataView, value) { | ||
function arraySaver(textEncoder, dataView, arrayAdditionalAllocation, arrayToSave) { | ||
let totalWrittenBytes = 0; | ||
let memoryForPointersCursor = reserveMemory(dataView, (arrayToSave.length + arrayAdditionalAllocation) * Uint32Array.BYTES_PER_ELEMENT); | ||
totalWrittenBytes += (arrayToSave.length + arrayAdditionalAllocation) * Uint32Array.BYTES_PER_ELEMENT; | ||
const arrayStartEntry = { | ||
type: ENTRY_TYPE.ARRAY, | ||
value: memoryForPointersCursor, | ||
allocatedLength: arrayToSave.length + arrayAdditionalAllocation, | ||
length: arrayToSave.length | ||
}; | ||
for (const item of arrayToSave) { | ||
const rOfValue = saveValue(textEncoder, dataView, arrayAdditionalAllocation, item); | ||
dataView.setUint32(memoryForPointersCursor, rOfValue.start); | ||
memoryForPointersCursor += Uint32Array.BYTES_PER_ELEMENT; | ||
totalWrittenBytes += rOfValue.length; | ||
} | ||
const arrayEntryAppendResult = appendEntry(dataView, arrayStartEntry, textEncoder); | ||
totalWrittenBytes += arrayEntryAppendResult.length; | ||
return { | ||
start: arrayEntryAppendResult.start, | ||
length: totalWrittenBytes | ||
}; | ||
} | ||
function saveValue(textEncoder, dataView, arrayAdditionalAllocation, value) { | ||
let totalWrittenBytes = 0; | ||
let valuePointer = 0; | ||
@@ -289,2 +356,9 @@ | ||
totalWrittenBytes += length; | ||
} else if (Array.isArray(value)) { | ||
const { | ||
start, | ||
length | ||
} = arraySaver(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
valuePointer = start; | ||
totalWrittenBytes += length; | ||
} else if (typeof value === "object") { | ||
@@ -294,3 +368,3 @@ const { | ||
length | ||
} = objectSaver(textEncoder, dataView, value); | ||
} = objectSaver(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
valuePointer = start; | ||
@@ -310,3 +384,3 @@ totalWrittenBytes += length; | ||
function objectSaver(textEncoder, dataView, objectToSave) { | ||
function objectSaver(textEncoder, dataView, arrayAdditionalAllocation, objectToSave) { | ||
let totalWrittenBytes = 0; // const writtenLength = 0; | ||
@@ -318,3 +392,3 @@ | ||
for (const [key, value] of objectEntries) { | ||
const rOfValue = saveValue(textEncoder, dataView, value); | ||
const rOfValue = saveValue(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
const objectPropEntry = { | ||
@@ -462,5 +536,274 @@ type: ENTRY_TYPE.OBJECT_PROP, | ||
// isolated due to parser changes ts 3.7 | ||
// https://github.com/microsoft/TypeScript/pull/32695 | ||
// eslint-disable-next-line prettier/prettier | ||
// export function assertNonNull<T>(v: T): asserts v is NonNullable<T> { | ||
// if (v === undefined || v === null) { | ||
// throw new Error("assertNonNull"); | ||
// } | ||
// } | ||
/** | ||
* @template T | ||
* @param {T} v | ||
* @return {asserts v is NonNullable<T>} | ||
*/ | ||
function assertNonNull(v) { | ||
if (v === undefined || v === null) { | ||
throw new Error("assertNonNull"); | ||
} | ||
} | ||
function arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry) { | ||
const [arrayEntry] = readEntry(dataView, pointerToArrayEntry, textDecoder); | ||
return arrayEntry; | ||
} | ||
function arrayGetPointersToValue(dataView, textDecoder, pointerToArrayEntry, indexToGet) { | ||
const metadata = arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry); // out of bound | ||
if (indexToGet >= metadata.length) { | ||
return undefined; | ||
} | ||
const pointerToThePointer = metadata.value + indexToGet * Uint32Array.BYTES_PER_ELEMENT; | ||
const pointer = dataView.getUint32(pointerToThePointer); | ||
return { | ||
pointer, | ||
pointerToThePointer | ||
}; | ||
} | ||
function getFinalValueAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToGet) { | ||
const pointers = arrayGetPointersToValue(dataView, textDecoder, pointerToArrayEntry, indexToGet); | ||
if (pointers === undefined) { | ||
return undefined; | ||
} | ||
const entry = readEntry(dataView, pointers.pointer, textDecoder); | ||
return entryToFinalJavaScriptValue(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, entry[0], pointers.pointer); | ||
} | ||
function setValuePointerAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToSet, pointerToEntry) { | ||
const metadata = arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry); | ||
if (indexToSet >= metadata.length) { | ||
// we need to re-allocate the array in a new and bigger place | ||
if (indexToSet >= metadata.allocatedLength) { | ||
reallocateArray(dataView, textDecoder, textEncoder, pointerToArrayEntry, indexToSet + 1 + arrayAdditionalAllocation, indexToSet + 1); | ||
} else { | ||
// no need to re-allocated, just push the length forward | ||
writeEntry(dataView, pointerToArrayEntry, { | ||
type: ENTRY_TYPE.ARRAY, | ||
value: metadata.value, | ||
allocatedLength: metadata.allocatedLength, | ||
length: indexToSet + 1 | ||
}, textEncoder); | ||
} | ||
} | ||
const pointers = arrayGetPointersToValue(dataView, textDecoder, pointerToArrayEntry, indexToSet); | ||
assertNonNull(pointers); | ||
dataView.setUint32(pointers.pointerToThePointer, pointerToEntry); | ||
} | ||
function setValueAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToSet, value) { | ||
const saveValueResult = saveValue(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
setValuePointerAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToSet, saveValueResult.start); | ||
} | ||
function reallocateArray(dataView, textDecoder, textEncoder, pointerToArrayEntry, newAllocatedLength, newLength) { | ||
const metadata = arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry); | ||
const newArrayValueLocation = reserveMemory(dataView, newAllocatedLength * Uint32Array.BYTES_PER_ELEMENT); | ||
for (let memoryToCopyIndex = 0; memoryToCopyIndex < metadata.length; memoryToCopyIndex += 1) { | ||
dataView.setUint32(newArrayValueLocation + memoryToCopyIndex * Uint32Array.BYTES_PER_ELEMENT, dataView.getUint32(metadata.value + memoryToCopyIndex * Uint32Array.BYTES_PER_ELEMENT)); | ||
} | ||
writeEntry(dataView, pointerToArrayEntry, { | ||
type: ENTRY_TYPE.ARRAY, | ||
value: newArrayValueLocation, | ||
allocatedLength: newAllocatedLength, | ||
length: newLength | ||
}, textEncoder); | ||
} | ||
class ArrayWrapper { | ||
constructor(dataView, arrayAdditionalAllocation, entryPointer, textDecoder, textEncoder) { | ||
this.dataView = dataView; | ||
this.arrayAdditionalAllocation = arrayAdditionalAllocation; | ||
this.entryPointer = entryPointer; | ||
this.textDecoder = textDecoder; | ||
this.textEncoder = textEncoder; | ||
} | ||
get(target, p) { | ||
if (p in this && p !== "constructor") { | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
return this[p]; | ||
} | ||
if (p === "length") { | ||
return arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} | ||
if (typeof p === "string" || typeof p === "number") { | ||
const asInt = typeof p === "string" ? Number.parseInt(p, 10) : p; | ||
if (Number.isSafeInteger(asInt)) { | ||
return getFinalValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, asInt); | ||
} | ||
} // eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
return target[p]; | ||
} | ||
deleteProperty(target, p) { | ||
throw new Error("unsupported"); | ||
} | ||
enumerate() { | ||
throw new Error("unsupported"); | ||
} | ||
ownKeys() { | ||
const length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
return [...new Array(length).keys()]; | ||
} | ||
getOwnPropertyDescriptor() { | ||
return { | ||
configurable: true, | ||
enumerable: true | ||
}; | ||
} | ||
has(target, p) { | ||
throw new Error("unsupported"); | ||
} | ||
set(target, p, value) { | ||
setValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, Number.parseInt(p, 10), value); | ||
return true; | ||
} | ||
isExtensible() { | ||
return true; | ||
} | ||
preventExtensions() { | ||
throw new Error("unsupported"); | ||
} | ||
setPrototypeOf() { | ||
throw new Error("unsupported"); | ||
} | ||
*entries() { | ||
let index = 0; | ||
let length = 0; | ||
do { | ||
yield [index, getFinalValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, index)]; | ||
index += 1; | ||
length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} while (index < length); | ||
} | ||
*keys() { | ||
let index = 0; | ||
let length = 0; | ||
do { | ||
yield index; | ||
index += 1; | ||
length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} while (index < length); | ||
} | ||
*values() { | ||
let index = 0; | ||
let length = 0; | ||
do { | ||
yield getFinalValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, index); | ||
index += 1; | ||
length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} while (index < length); | ||
} | ||
get [Symbol.iterator]() { | ||
return this.values; | ||
} // in-place methods | ||
shift() { | ||
throw new Error("unsupported"); | ||
} | ||
unshift() { | ||
throw new Error("unsupported"); | ||
} | ||
pop() { | ||
throw new Error("unsupported"); | ||
} | ||
push() { | ||
throw new Error("unsupported"); | ||
} | ||
sort() { | ||
throw new Error("unsupported"); | ||
} | ||
splice() { | ||
throw new Error("unsupported"); | ||
} // // copy methods | ||
// private concat() { | ||
// throw new Error("unsupported"); | ||
// } | ||
// private slice() { | ||
// throw new Error("unsupported"); | ||
// } | ||
// private map() { | ||
// throw new Error("unsupported"); | ||
// } | ||
// private reduce() { | ||
// throw new Error("unsupported"); | ||
// } | ||
} | ||
function createArrayWrapper(dataView, arrayAdditionalAllocation, entryPointer, textDecoder, textEncoder) { | ||
return new Proxy([], new ArrayWrapper(dataView, arrayAdditionalAllocation, entryPointer, textDecoder, textEncoder)); | ||
} | ||
function entryToFinalJavaScriptValue(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, valueEntry, pointerToEntry) { | ||
if (valueEntry.type === ENTRY_TYPE.NULL) { | ||
return null; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.UNDEFINED) { | ||
return undefined; | ||
} | ||
if (isPrimitiveEntryType(valueEntry.type)) { | ||
return valueEntry.value; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.OBJECT) { | ||
return createObjectWrapper(dataView, pointerToEntry, textDecoder, textEncoder, false, arrayAdditionalAllocation); | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.ARRAY) { | ||
return createArrayWrapper(dataView, arrayAdditionalAllocation, pointerToEntry, textDecoder, textEncoder); | ||
} | ||
throw new Error("unsupported yet"); | ||
} | ||
const GET_UNDERLYING_ARRAY_BUFFER_SYMBOL = Symbol("GET_UNDERLYING_ARRAY_BUFFER_SYMBOL"); | ||
class ObjectWrapper { | ||
constructor(dataView, entryPointer, textDecoder, textEncoder, isTopLevel) { | ||
constructor(dataView, entryPointer, textDecoder, textEncoder, isTopLevel, arrayAdditionalAllocation) { | ||
this.dataView = dataView; | ||
@@ -471,2 +814,3 @@ this.entryPointer = entryPointer; | ||
this.isTopLevel = isTopLevel; | ||
this.arrayAdditionalAllocation = arrayAdditionalAllocation; | ||
} | ||
@@ -502,20 +846,3 @@ | ||
const [valueEntry] = readEntry(this.dataView, foundEntry[1].value.value, this.textDecoder); | ||
if (valueEntry.type === ENTRY_TYPE.NULL) { | ||
return null; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.UNDEFINED) { | ||
return undefined; | ||
} | ||
if (isPrimitiveEntryType(valueEntry.type)) { | ||
return valueEntry.value; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.OBJECT) { | ||
return createObjectWrapper(this.dataView, foundEntry[1].value.value, this.textDecoder, this.textEncoder); | ||
} | ||
throw new Error("unsupported yet"); | ||
return entryToFinalJavaScriptValue(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, valueEntry, foundEntry[1].value.value); | ||
} | ||
@@ -560,3 +887,3 @@ | ||
start: newValueEntryPointer | ||
} = saveValue(this.textEncoder, this.dataView, value); | ||
} = saveValue(this.textEncoder, this.dataView, this.arrayAdditionalAllocation, value); | ||
const foundPropEntry = findObjectPropertyEntry(this.dataView, this.entryPointer, p, this.textDecoder); // new prop | ||
@@ -638,15 +965,21 @@ | ||
} | ||
function createObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel = false) { | ||
function createObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel = false, arrayAdditionalAllocation = 50) { | ||
return new Proxy({ | ||
bla: 1 | ||
}, new ObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel)); | ||
objectBufferWrapper: "objectBufferWrapper" | ||
}, new ObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel, arrayAdditionalAllocation)); | ||
} | ||
function createObjectBuffer(textDecoder, textEncoder, size, initialValue) { | ||
const arrayBuffer = new ArrayBuffer(size); | ||
function createObjectBuffer(textDecoder, textEncoder, size, initialValue, { | ||
arrayAdditionalAllocation, | ||
useSharedArrayBuffer | ||
} = { | ||
arrayAdditionalAllocation: 0, | ||
useSharedArrayBuffer: false | ||
}) { | ||
const arrayBuffer = new (useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer)(size); | ||
const dataView = initializeArrayBuffer(arrayBuffer); | ||
const { | ||
start | ||
} = objectSaver(textEncoder, dataView, initialValue); | ||
dataView.setUint32(8, start); | ||
} = objectSaver(textEncoder, dataView, arrayAdditionalAllocation || 0, initialValue); | ||
dataView.setUint32(16, start); | ||
return createObjectWrapper(dataView, start, textDecoder, textEncoder, true); | ||
@@ -658,7 +991,60 @@ } | ||
function createObjectBufferFromArrayBuffer(textDecoder, textEncoder, arrayBuffer, // set to true if the give array buffer is not one from `getUnderlyingArrayBuffer` | ||
shouldInitializeArrayBuffer = false) { | ||
shouldInitializeArrayBuffer = false, { | ||
arrayAdditionalAllocation | ||
} = { | ||
arrayAdditionalAllocation: 0 | ||
}) { | ||
const dataView = shouldInitializeArrayBuffer ? initializeArrayBuffer(arrayBuffer) : new DataView(arrayBuffer); | ||
return createObjectWrapper(dataView, dataView.getUint32(8), textDecoder, textEncoder); | ||
return createObjectWrapper(dataView, dataView.getUint32(16), textDecoder, textEncoder, true, arrayAdditionalAllocation); | ||
} | ||
export { createObjectBuffer, createObjectBufferFromArrayBuffer, getUnderlyingArrayBuffer }; | ||
/* global Atomics */ | ||
// I have no idea if its really works as i think it should | ||
function getLock(playerId, sab) { | ||
invariant(playerId > 0, "playerId must be more than 0"); | ||
const int32 = new Int32Array(sab); | ||
const oldValue = Atomics.compareExchange(int32, 0, 0, playerId); | ||
return oldValue === 0; | ||
} | ||
function releaseLock(playerId, sab) { | ||
const int32 = new Int32Array(sab); | ||
const oldValue = Atomics.compareExchange(int32, 0, playerId, 0); // we've released a lock. lets tell them about it | ||
if (oldValue === playerId) { | ||
Atomics.notify(int32, 0, +Infinity); | ||
return true; | ||
} | ||
return false; | ||
} | ||
function waitForLock(playerId, sab, timeout) { | ||
const int32 = new Int32Array(sab); | ||
const oldValue = Atomics.compareExchange(int32, 0, 0, playerId); | ||
if (oldValue === 0) { | ||
return "have-lock"; | ||
} | ||
const r = Atomics.wait(int32, 0, oldValue, timeout); | ||
if (r === "not-equal") { | ||
if (Atomics.compareExchange(int32, 0, 0, playerId) === 0) { | ||
return "have-lock"; | ||
} else { | ||
return "miss-lock"; | ||
} | ||
} else if (r === "timed-out") { | ||
return "timed-out"; | ||
} else { | ||
return "no-lock"; | ||
} | ||
} | ||
var locks = /*#__PURE__*/Object.freeze({ | ||
getLock: getLock, | ||
releaseLock: releaseLock, | ||
waitForLock: waitForLock | ||
}); | ||
export { createObjectBuffer, createObjectBufferFromArrayBuffer, getUnderlyingArrayBuffer, locks }; |
@@ -72,8 +72,16 @@ (function (global, factory) { | ||
} | ||
function arrayBufferCopyTo(origin, startByte, length, target, toTargetByte) { | ||
const copyFrom = new Uint8Array(origin); | ||
const copyTo = new Uint8Array(target); | ||
for (let i = 0; i < length; i += 1) { | ||
copyTo[toTargetByte + i] = copyFrom[startByte + i]; | ||
} | ||
} | ||
let ENTRY_TYPE; | ||
(function (ENTRY_TYPE) { | ||
ENTRY_TYPE[ENTRY_TYPE["NULL"] = 0] = "NULL"; | ||
ENTRY_TYPE[ENTRY_TYPE["UNDEFINED"] = 1] = "UNDEFINED"; | ||
ENTRY_TYPE[ENTRY_TYPE["UNDEFINED"] = 0] = "UNDEFINED"; | ||
ENTRY_TYPE[ENTRY_TYPE["NULL"] = 1] = "NULL"; | ||
ENTRY_TYPE[ENTRY_TYPE["NUMBER"] = 2] = "NUMBER"; | ||
@@ -97,7 +105,9 @@ ENTRY_TYPE[ENTRY_TYPE["BIGINT"] = 3] = "BIGINT"; | ||
function initializeArrayBuffer(arrayBuffer) { | ||
const dataView = new DataView(arrayBuffer); // End of data pointer | ||
const dataView = new DataView(arrayBuffer); // global lock | ||
dataView.setUint32(0, 16); // first entry pointer | ||
dataView.setInt32(0, 0); // End of data pointer / first free byte | ||
dataView.setUint32(8, 16); | ||
dataView.setUint32(8, 24); // first entry pointer | ||
dataView.setUint32(16, 24); | ||
return dataView; | ||
@@ -179,3 +189,14 @@ } | ||
case ENTRY_TYPE.ARRAY: | ||
dataView.setUint32(cursor, entry.value); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
dataView.setUint32(cursor, entry.length); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
dataView.setUint32(cursor, entry.allocatedLength); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
break; | ||
default: | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
throw new Error(ENTRY_TYPE[entry.type] + " Not implemented yet"); | ||
@@ -192,5 +213,5 @@ } | ||
// End of data pointer | ||
const firstFreeByte = dataView.getUint32(0); | ||
const firstFreeByte = dataView.getUint32(8); | ||
const written = writeEntry(dataView, firstFreeByte, entry, textEncoder); | ||
dataView.setUint32(0, firstFreeByte + written); | ||
dataView.setUint32(8, firstFreeByte + written); | ||
return { | ||
@@ -234,6 +255,9 @@ start: firstFreeByte, | ||
const stringLength = dataView.getUint16(cursor); | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; | ||
entry.value = textDecoder.decode( // this wrapping is needed until: | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
new Uint8Array(dataView.buffer.slice(cursor, cursor + stringLength))); | ||
// eslint-disable-next-line no-case-declarations | ||
const tempAB = new ArrayBuffer(stringLength); | ||
arrayBufferCopyTo(dataView.buffer, cursor, stringLength, tempAB, 0); | ||
entry.value = textDecoder.decode(tempAB); | ||
cursor += stringLength; | ||
@@ -260,8 +284,11 @@ break; | ||
const keyStringLength = dataView.getUint16(cursor); | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // eslint-disable-next-line no-case-declarations | ||
cursor += Uint16Array.BYTES_PER_ELEMENT; // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
// eslint-disable-next-line no-case-declarations | ||
const tempAB2 = new ArrayBuffer(keyStringLength); | ||
arrayBufferCopyTo(dataView.buffer, cursor, keyStringLength, tempAB2, 0); // eslint-disable-next-line no-case-declarations | ||
const objectPropsValue = { | ||
key: textDecoder.decode( // this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
new Uint8Array(dataView.buffer.slice(cursor, cursor + keyStringLength))), | ||
key: textDecoder.decode(tempAB2), | ||
value: dataView.getUint32(cursor + keyStringLength), | ||
@@ -274,2 +301,11 @@ next: dataView.getUint32(cursor + keyStringLength + Uint32Array.BYTES_PER_ELEMENT) | ||
case ENTRY_TYPE.ARRAY: | ||
entry.value = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
entry.length = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
entry.allocatedLength = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
break; | ||
default: | ||
@@ -281,5 +317,36 @@ throw new Error(ENTRY_TYPE[entryType] + " Not implemented yet"); | ||
} | ||
function reserveMemory(dataView, length) { | ||
const firstFreeByte = dataView.getUint32(8); | ||
dataView.setUint32(8, firstFreeByte + length); | ||
return firstFreeByte; | ||
} | ||
function saveValue(textEncoder, dataView, value) { | ||
function arraySaver(textEncoder, dataView, arrayAdditionalAllocation, arrayToSave) { | ||
let totalWrittenBytes = 0; | ||
let memoryForPointersCursor = reserveMemory(dataView, (arrayToSave.length + arrayAdditionalAllocation) * Uint32Array.BYTES_PER_ELEMENT); | ||
totalWrittenBytes += (arrayToSave.length + arrayAdditionalAllocation) * Uint32Array.BYTES_PER_ELEMENT; | ||
const arrayStartEntry = { | ||
type: ENTRY_TYPE.ARRAY, | ||
value: memoryForPointersCursor, | ||
allocatedLength: arrayToSave.length + arrayAdditionalAllocation, | ||
length: arrayToSave.length | ||
}; | ||
for (const item of arrayToSave) { | ||
const rOfValue = saveValue(textEncoder, dataView, arrayAdditionalAllocation, item); | ||
dataView.setUint32(memoryForPointersCursor, rOfValue.start); | ||
memoryForPointersCursor += Uint32Array.BYTES_PER_ELEMENT; | ||
totalWrittenBytes += rOfValue.length; | ||
} | ||
const arrayEntryAppendResult = appendEntry(dataView, arrayStartEntry, textEncoder); | ||
totalWrittenBytes += arrayEntryAppendResult.length; | ||
return { | ||
start: arrayEntryAppendResult.start, | ||
length: totalWrittenBytes | ||
}; | ||
} | ||
function saveValue(textEncoder, dataView, arrayAdditionalAllocation, value) { | ||
let totalWrittenBytes = 0; | ||
let valuePointer = 0; | ||
@@ -295,2 +362,9 @@ | ||
totalWrittenBytes += length; | ||
} else if (Array.isArray(value)) { | ||
const { | ||
start, | ||
length | ||
} = arraySaver(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
valuePointer = start; | ||
totalWrittenBytes += length; | ||
} else if (typeof value === "object") { | ||
@@ -300,3 +374,3 @@ const { | ||
length | ||
} = objectSaver(textEncoder, dataView, value); | ||
} = objectSaver(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
valuePointer = start; | ||
@@ -316,3 +390,3 @@ totalWrittenBytes += length; | ||
function objectSaver(textEncoder, dataView, objectToSave) { | ||
function objectSaver(textEncoder, dataView, arrayAdditionalAllocation, objectToSave) { | ||
let totalWrittenBytes = 0; // const writtenLength = 0; | ||
@@ -324,3 +398,3 @@ | ||
for (const [key, value] of objectEntries) { | ||
const rOfValue = saveValue(textEncoder, dataView, value); | ||
const rOfValue = saveValue(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
const objectPropEntry = { | ||
@@ -468,5 +542,274 @@ type: ENTRY_TYPE.OBJECT_PROP, | ||
// isolated due to parser changes ts 3.7 | ||
// https://github.com/microsoft/TypeScript/pull/32695 | ||
// eslint-disable-next-line prettier/prettier | ||
// export function assertNonNull<T>(v: T): asserts v is NonNullable<T> { | ||
// if (v === undefined || v === null) { | ||
// throw new Error("assertNonNull"); | ||
// } | ||
// } | ||
/** | ||
* @template T | ||
* @param {T} v | ||
* @return {asserts v is NonNullable<T>} | ||
*/ | ||
function assertNonNull(v) { | ||
if (v === undefined || v === null) { | ||
throw new Error("assertNonNull"); | ||
} | ||
} | ||
function arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry) { | ||
const [arrayEntry] = readEntry(dataView, pointerToArrayEntry, textDecoder); | ||
return arrayEntry; | ||
} | ||
function arrayGetPointersToValue(dataView, textDecoder, pointerToArrayEntry, indexToGet) { | ||
const metadata = arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry); // out of bound | ||
if (indexToGet >= metadata.length) { | ||
return undefined; | ||
} | ||
const pointerToThePointer = metadata.value + indexToGet * Uint32Array.BYTES_PER_ELEMENT; | ||
const pointer = dataView.getUint32(pointerToThePointer); | ||
return { | ||
pointer, | ||
pointerToThePointer | ||
}; | ||
} | ||
function getFinalValueAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToGet) { | ||
const pointers = arrayGetPointersToValue(dataView, textDecoder, pointerToArrayEntry, indexToGet); | ||
if (pointers === undefined) { | ||
return undefined; | ||
} | ||
const entry = readEntry(dataView, pointers.pointer, textDecoder); | ||
return entryToFinalJavaScriptValue(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, entry[0], pointers.pointer); | ||
} | ||
function setValuePointerAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToSet, pointerToEntry) { | ||
const metadata = arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry); | ||
if (indexToSet >= metadata.length) { | ||
// we need to re-allocate the array in a new and bigger place | ||
if (indexToSet >= metadata.allocatedLength) { | ||
reallocateArray(dataView, textDecoder, textEncoder, pointerToArrayEntry, indexToSet + 1 + arrayAdditionalAllocation, indexToSet + 1); | ||
} else { | ||
// no need to re-allocated, just push the length forward | ||
writeEntry(dataView, pointerToArrayEntry, { | ||
type: ENTRY_TYPE.ARRAY, | ||
value: metadata.value, | ||
allocatedLength: metadata.allocatedLength, | ||
length: indexToSet + 1 | ||
}, textEncoder); | ||
} | ||
} | ||
const pointers = arrayGetPointersToValue(dataView, textDecoder, pointerToArrayEntry, indexToSet); | ||
assertNonNull(pointers); | ||
dataView.setUint32(pointers.pointerToThePointer, pointerToEntry); | ||
} | ||
function setValueAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToSet, value) { | ||
const saveValueResult = saveValue(textEncoder, dataView, arrayAdditionalAllocation, value); | ||
setValuePointerAtArrayIndex(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, pointerToArrayEntry, indexToSet, saveValueResult.start); | ||
} | ||
function reallocateArray(dataView, textDecoder, textEncoder, pointerToArrayEntry, newAllocatedLength, newLength) { | ||
const metadata = arrayGetMetadata(dataView, textDecoder, pointerToArrayEntry); | ||
const newArrayValueLocation = reserveMemory(dataView, newAllocatedLength * Uint32Array.BYTES_PER_ELEMENT); | ||
for (let memoryToCopyIndex = 0; memoryToCopyIndex < metadata.length; memoryToCopyIndex += 1) { | ||
dataView.setUint32(newArrayValueLocation + memoryToCopyIndex * Uint32Array.BYTES_PER_ELEMENT, dataView.getUint32(metadata.value + memoryToCopyIndex * Uint32Array.BYTES_PER_ELEMENT)); | ||
} | ||
writeEntry(dataView, pointerToArrayEntry, { | ||
type: ENTRY_TYPE.ARRAY, | ||
value: newArrayValueLocation, | ||
allocatedLength: newAllocatedLength, | ||
length: newLength | ||
}, textEncoder); | ||
} | ||
class ArrayWrapper { | ||
constructor(dataView, arrayAdditionalAllocation, entryPointer, textDecoder, textEncoder) { | ||
this.dataView = dataView; | ||
this.arrayAdditionalAllocation = arrayAdditionalAllocation; | ||
this.entryPointer = entryPointer; | ||
this.textDecoder = textDecoder; | ||
this.textEncoder = textEncoder; | ||
} | ||
get(target, p) { | ||
if (p in this && p !== "constructor") { | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
return this[p]; | ||
} | ||
if (p === "length") { | ||
return arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} | ||
if (typeof p === "string" || typeof p === "number") { | ||
const asInt = typeof p === "string" ? Number.parseInt(p, 10) : p; | ||
if (Number.isSafeInteger(asInt)) { | ||
return getFinalValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, asInt); | ||
} | ||
} // eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
return target[p]; | ||
} | ||
deleteProperty(target, p) { | ||
throw new Error("unsupported"); | ||
} | ||
enumerate() { | ||
throw new Error("unsupported"); | ||
} | ||
ownKeys() { | ||
const length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
return [...new Array(length).keys()]; | ||
} | ||
getOwnPropertyDescriptor() { | ||
return { | ||
configurable: true, | ||
enumerable: true | ||
}; | ||
} | ||
has(target, p) { | ||
throw new Error("unsupported"); | ||
} | ||
set(target, p, value) { | ||
setValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, Number.parseInt(p, 10), value); | ||
return true; | ||
} | ||
isExtensible() { | ||
return true; | ||
} | ||
preventExtensions() { | ||
throw new Error("unsupported"); | ||
} | ||
setPrototypeOf() { | ||
throw new Error("unsupported"); | ||
} | ||
*entries() { | ||
let index = 0; | ||
let length = 0; | ||
do { | ||
yield [index, getFinalValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, index)]; | ||
index += 1; | ||
length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} while (index < length); | ||
} | ||
*keys() { | ||
let index = 0; | ||
let length = 0; | ||
do { | ||
yield index; | ||
index += 1; | ||
length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} while (index < length); | ||
} | ||
*values() { | ||
let index = 0; | ||
let length = 0; | ||
do { | ||
yield getFinalValueAtArrayIndex(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, this.entryPointer, index); | ||
index += 1; | ||
length = arrayGetMetadata(this.dataView, this.textDecoder, this.entryPointer).length; | ||
} while (index < length); | ||
} | ||
get [Symbol.iterator]() { | ||
return this.values; | ||
} // in-place methods | ||
shift() { | ||
throw new Error("unsupported"); | ||
} | ||
unshift() { | ||
throw new Error("unsupported"); | ||
} | ||
pop() { | ||
throw new Error("unsupported"); | ||
} | ||
push() { | ||
throw new Error("unsupported"); | ||
} | ||
sort() { | ||
throw new Error("unsupported"); | ||
} | ||
splice() { | ||
throw new Error("unsupported"); | ||
} // // copy methods | ||
// private concat() { | ||
// throw new Error("unsupported"); | ||
// } | ||
// private slice() { | ||
// throw new Error("unsupported"); | ||
// } | ||
// private map() { | ||
// throw new Error("unsupported"); | ||
// } | ||
// private reduce() { | ||
// throw new Error("unsupported"); | ||
// } | ||
} | ||
function createArrayWrapper(dataView, arrayAdditionalAllocation, entryPointer, textDecoder, textEncoder) { | ||
return new Proxy([], new ArrayWrapper(dataView, arrayAdditionalAllocation, entryPointer, textDecoder, textEncoder)); | ||
} | ||
function entryToFinalJavaScriptValue(dataView, textDecoder, textEncoder, arrayAdditionalAllocation, valueEntry, pointerToEntry) { | ||
if (valueEntry.type === ENTRY_TYPE.NULL) { | ||
return null; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.UNDEFINED) { | ||
return undefined; | ||
} | ||
if (isPrimitiveEntryType(valueEntry.type)) { | ||
return valueEntry.value; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.OBJECT) { | ||
return createObjectWrapper(dataView, pointerToEntry, textDecoder, textEncoder, false, arrayAdditionalAllocation); | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.ARRAY) { | ||
return createArrayWrapper(dataView, arrayAdditionalAllocation, pointerToEntry, textDecoder, textEncoder); | ||
} | ||
throw new Error("unsupported yet"); | ||
} | ||
const GET_UNDERLYING_ARRAY_BUFFER_SYMBOL = Symbol("GET_UNDERLYING_ARRAY_BUFFER_SYMBOL"); | ||
class ObjectWrapper { | ||
constructor(dataView, entryPointer, textDecoder, textEncoder, isTopLevel) { | ||
constructor(dataView, entryPointer, textDecoder, textEncoder, isTopLevel, arrayAdditionalAllocation) { | ||
this.dataView = dataView; | ||
@@ -477,2 +820,3 @@ this.entryPointer = entryPointer; | ||
this.isTopLevel = isTopLevel; | ||
this.arrayAdditionalAllocation = arrayAdditionalAllocation; | ||
} | ||
@@ -508,20 +852,3 @@ | ||
const [valueEntry] = readEntry(this.dataView, foundEntry[1].value.value, this.textDecoder); | ||
if (valueEntry.type === ENTRY_TYPE.NULL) { | ||
return null; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.UNDEFINED) { | ||
return undefined; | ||
} | ||
if (isPrimitiveEntryType(valueEntry.type)) { | ||
return valueEntry.value; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.OBJECT) { | ||
return createObjectWrapper(this.dataView, foundEntry[1].value.value, this.textDecoder, this.textEncoder); | ||
} | ||
throw new Error("unsupported yet"); | ||
return entryToFinalJavaScriptValue(this.dataView, this.textDecoder, this.textEncoder, this.arrayAdditionalAllocation, valueEntry, foundEntry[1].value.value); | ||
} | ||
@@ -566,3 +893,3 @@ | ||
start: newValueEntryPointer | ||
} = saveValue(this.textEncoder, this.dataView, value); | ||
} = saveValue(this.textEncoder, this.dataView, this.arrayAdditionalAllocation, value); | ||
const foundPropEntry = findObjectPropertyEntry(this.dataView, this.entryPointer, p, this.textDecoder); // new prop | ||
@@ -644,15 +971,21 @@ | ||
} | ||
function createObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel = false) { | ||
function createObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel = false, arrayAdditionalAllocation = 50) { | ||
return new Proxy({ | ||
bla: 1 | ||
}, new ObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel)); | ||
objectBufferWrapper: "objectBufferWrapper" | ||
}, new ObjectWrapper(dataView, entryPointer, textDecoder, textEncoder, isTopLevel, arrayAdditionalAllocation)); | ||
} | ||
function createObjectBuffer(textDecoder, textEncoder, size, initialValue) { | ||
const arrayBuffer = new ArrayBuffer(size); | ||
function createObjectBuffer(textDecoder, textEncoder, size, initialValue, { | ||
arrayAdditionalAllocation, | ||
useSharedArrayBuffer | ||
} = { | ||
arrayAdditionalAllocation: 0, | ||
useSharedArrayBuffer: false | ||
}) { | ||
const arrayBuffer = new (useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer)(size); | ||
const dataView = initializeArrayBuffer(arrayBuffer); | ||
const { | ||
start | ||
} = objectSaver(textEncoder, dataView, initialValue); | ||
dataView.setUint32(8, start); | ||
} = objectSaver(textEncoder, dataView, arrayAdditionalAllocation || 0, initialValue); | ||
dataView.setUint32(16, start); | ||
return createObjectWrapper(dataView, start, textDecoder, textEncoder, true); | ||
@@ -664,10 +997,64 @@ } | ||
function createObjectBufferFromArrayBuffer(textDecoder, textEncoder, arrayBuffer, // set to true if the give array buffer is not one from `getUnderlyingArrayBuffer` | ||
shouldInitializeArrayBuffer = false) { | ||
shouldInitializeArrayBuffer = false, { | ||
arrayAdditionalAllocation | ||
} = { | ||
arrayAdditionalAllocation: 0 | ||
}) { | ||
const dataView = shouldInitializeArrayBuffer ? initializeArrayBuffer(arrayBuffer) : new DataView(arrayBuffer); | ||
return createObjectWrapper(dataView, dataView.getUint32(8), textDecoder, textEncoder); | ||
return createObjectWrapper(dataView, dataView.getUint32(16), textDecoder, textEncoder, true, arrayAdditionalAllocation); | ||
} | ||
/* global Atomics */ | ||
// I have no idea if its really works as i think it should | ||
function getLock(playerId, sab) { | ||
invariant(playerId > 0, "playerId must be more than 0"); | ||
const int32 = new Int32Array(sab); | ||
const oldValue = Atomics.compareExchange(int32, 0, 0, playerId); | ||
return oldValue === 0; | ||
} | ||
function releaseLock(playerId, sab) { | ||
const int32 = new Int32Array(sab); | ||
const oldValue = Atomics.compareExchange(int32, 0, playerId, 0); // we've released a lock. lets tell them about it | ||
if (oldValue === playerId) { | ||
Atomics.notify(int32, 0, +Infinity); | ||
return true; | ||
} | ||
return false; | ||
} | ||
function waitForLock(playerId, sab, timeout) { | ||
const int32 = new Int32Array(sab); | ||
const oldValue = Atomics.compareExchange(int32, 0, 0, playerId); | ||
if (oldValue === 0) { | ||
return "have-lock"; | ||
} | ||
const r = Atomics.wait(int32, 0, oldValue, timeout); | ||
if (r === "not-equal") { | ||
if (Atomics.compareExchange(int32, 0, 0, playerId) === 0) { | ||
return "have-lock"; | ||
} else { | ||
return "miss-lock"; | ||
} | ||
} else if (r === "timed-out") { | ||
return "timed-out"; | ||
} else { | ||
return "no-lock"; | ||
} | ||
} | ||
var locks = /*#__PURE__*/Object.freeze({ | ||
getLock: getLock, | ||
releaseLock: releaseLock, | ||
waitForLock: waitForLock | ||
}); | ||
exports.createObjectBuffer = createObjectBuffer; | ||
exports.createObjectBufferFromArrayBuffer = createObjectBufferFromArrayBuffer; | ||
exports.getUnderlyingArrayBuffer = getUnderlyingArrayBuffer; | ||
exports.locks = locks; | ||
@@ -674,0 +1061,0 @@ Object.defineProperty(exports, '__esModule', { value: true }); |
{ | ||
"name": "@bnaya/objectbuffer", | ||
"description": "Object like api, backed by an array buffer", | ||
"version": "0.1.4", | ||
"version": "0.2.0", | ||
"main": "dist/objectbuffer.cjs.js", | ||
@@ -17,3 +17,4 @@ "module": "dist/index.js", | ||
"build": "yarn cleanup; yarn babel-build; yarn build-declarations; rollup -c; cp dist/index.d.ts dist/objectbuffer.cjs.d.ts ;echo `git rev-parse HEAD` > COMMIT", | ||
"prepack": "yarn build" | ||
"prepack": "yarn build", | ||
"browser-playground": "webpack-dev-server --mode=development --config playground/webpack.config.js playground/main.js --open" | ||
}, | ||
@@ -29,5 +30,7 @@ "devDependencies": { | ||
"@typescript-eslint/parser": "^2.0.0", | ||
"babel-loader": "^8.0.6", | ||
"eslint": "^6.3.0", | ||
"eslint-config-prettier": "^6.1.0", | ||
"eslint-plugin-prettier": "^3.1.0", | ||
"html-webpack-plugin": "^3.2.0", | ||
"husky": "^3.0.4", | ||
@@ -41,3 +44,7 @@ "jest": "^24.9.0", | ||
"rollup-plugin-node-resolve": "^5.2.0", | ||
"typescript": "^3.6.2", | ||
"typescript": "^3.7.0-dev.20190927", | ||
"webpack": "^4.39.3", | ||
"webpack-cli": "^3.3.8", | ||
"webpack-dev-server": "^3.8.0", | ||
"worker-loader": "^2.0.0", | ||
"yarn-deduplicate": "^1.1.1" | ||
@@ -44,0 +51,0 @@ }, |
@@ -1,17 +0,40 @@ | ||
# WIP - object like api backed by a single array buffer. | ||
# WIP: object-like api, backed by a [shared]arraybuffer. | ||
For Modern browsers and node. Zero direct dependencies. | ||
"design" doc: | ||
https://docs.google.com/document/d/1-UlUyH3HgOrN58avyScZlfjQtfJxgVwK_yE35mQHpYw/edit?usp=sharing | ||
The library offers you an api that have the look & feel of a regular javascript object, but the data is saved to an `ArrayBuffer` that can be shared or transferred to a `WebWorker`. | ||
The library is still not complete, and will never offer full compatibility with plain js objects due, to the nature of the language and the problems space. | ||
## Play with it: | ||
[![Edit objectbuffer demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/objectbuffer-demo-uyov5?fontsize=14&module=%2Fsrc%2Findex.test.ts) | ||
See [main.js](playground/main.js) for shared memory example. | ||
to run it: clone the repo, `yarn install` and `yarn browser-playground` | ||
## Why? | ||
Personal interest, Maybe will be useful as shared memory primitive, and communicating with wasm. | ||
Exchanging data with `WebWorkers` (other than ArrayBuffer) is done by serializing and copying the data to the other side. | ||
for some use-cases, it's slow and memory expensive. | ||
`ArrayBuffer` can be `transferred` without copy, and `SharedArrayBuffer` can be directly shared, but out of the box, it's hard to use `ArrayBuffer` as more than a [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays). | ||
At the current state, it's working! but very optimized (based only on linked lists), only append data (no logic to reuse unreachable memory) | ||
`SharedArrayBuffer` and `ArrayBuffer` | ||
## Motivation | ||
Personal interest, Maybe will be useful as shared memory primitive, and communicating with WASM. Maybe state management with shared memory across workers? | ||
## What do we have in hand? | ||
It's working! but very unoptimized (eg objects are simple linked lists), only append data (no logic to reuse unreachable memory) | ||
and its not extending the backing `arraybuffer` size by itself. | ||
if you pass the limit, an exception will be thrown. | ||
We still don't have [ArrayBuffer.prototype.transfer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/transfer) So we can't do that efficiently. | ||
if you exceed the sb size, an exception will be thrown. | ||
[ArrayBuffer.prototype.transfer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/transfer) is still not supported anywhere, so we can't do that efficiently anyhow | ||
### What's working: | ||
* Kinda what ever that can go into `JSON.stringify` | ||
* objects | ||
* arrays (without methods yet) | ||
* global lock using [Atomics](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics) (i hope its really working) | ||
### Missing parts | ||
* `array` (Not sure yet if to make just make them as objects, or something smarter) | ||
* `array` methods | ||
* `Date` | ||
@@ -21,7 +44,2 @@ * `Map` On primitive key/value | ||
### What's working: | ||
* Kinda what ever that can do into `JSON.stringify` | ||
* objects! | ||
* other | ||
### What's not working yet, but can be: | ||
@@ -35,5 +53,9 @@ * `bigint` bigger than 64 bit | ||
### "design" doc: | ||
https://docs.google.com/document/d/1-UlUyH3HgOrN58avyScZlfjQtfJxgVwK_yE35mQHpYw/edit?usp=sharing | ||
## Contribution / Collaboration | ||
There's a huge place for optimizations, code hygiene, and features! | ||
Feel free to open issues and maybe implementing missing parts | ||
@@ -1,45 +0,3 @@ | ||
import { initializeArrayBuffer } from "./internal/store"; | ||
import { objectSaver } from "./internal/objectSaver"; | ||
import { | ||
createObjectWrapper, | ||
GET_UNDERLYING_ARRAY_BUFFER_SYMBOL | ||
} from "./internal/objectWrapper"; | ||
export function createObjectBuffer<T = any>( | ||
textDecoder: any, | ||
textEncoder: any, | ||
size: number, | ||
initialValue: T | ||
): T { | ||
const arrayBuffer = new ArrayBuffer(size); | ||
const dataView = initializeArrayBuffer(arrayBuffer); | ||
const { start } = objectSaver(textEncoder, dataView, initialValue); | ||
dataView.setUint32(8, start); | ||
return createObjectWrapper(dataView, start, textDecoder, textEncoder, true); | ||
} | ||
export function getUnderlyingArrayBuffer(objectBuffer: any): ArrayBuffer { | ||
return objectBuffer[GET_UNDERLYING_ARRAY_BUFFER_SYMBOL]; | ||
} | ||
export function createObjectBufferFromArrayBuffer<T = any>( | ||
textDecoder: any, | ||
textEncoder: any, | ||
arrayBuffer: ArrayBuffer, | ||
// set to true if the give array buffer is not one from `getUnderlyingArrayBuffer` | ||
shouldInitializeArrayBuffer = false | ||
): T { | ||
const dataView = shouldInitializeArrayBuffer | ||
? initializeArrayBuffer(arrayBuffer) | ||
: new DataView(arrayBuffer); | ||
return createObjectWrapper( | ||
dataView, | ||
dataView.getUint32(8), | ||
textDecoder, | ||
textEncoder | ||
); | ||
} | ||
export * from "./internal/api"; | ||
import * as locks from "./internal/locks"; | ||
export { locks }; |
@@ -9,4 +9,4 @@ /* eslint-env jest */ | ||
Object { | ||
"0": "NULL", | ||
"1": "UNDEFINED", | ||
"0": "UNDEFINED", | ||
"1": "NULL", | ||
"10": "ARRAY_ITEM", | ||
@@ -30,3 +30,3 @@ "11": "MAP", | ||
"MAP": 11, | ||
"NULL": 0, | ||
"NULL": 1, | ||
"NUMBER": 2, | ||
@@ -38,3 +38,3 @@ "OBJECT": 7, | ||
"UBIGINT": 4, | ||
"UNDEFINED": 1, | ||
"UNDEFINED": 0, | ||
} | ||
@@ -41,0 +41,0 @@ `); |
import { createKnownTypeGuard } from "./utils"; | ||
export enum ENTRY_TYPE { | ||
UNDEFINED, | ||
NULL, | ||
UNDEFINED, | ||
NUMBER, | ||
@@ -7,0 +7,0 @@ BIGINT, |
@@ -15,4 +15,3 @@ import { ENTRY_TYPE } from "./entry-types"; | ||
| ObjectPropEntry | ||
| ArrayEntry | ||
| ArrayItemEntry; | ||
| ArrayEntry; | ||
@@ -82,12 +81,4 @@ export interface NullEntry { | ||
value: number; | ||
length: number; | ||
allocatedLength: number; | ||
} | ||
export interface ArrayItemEntry { | ||
type: ENTRY_TYPE.ARRAY_ITEM; | ||
value: { | ||
// pointer to value entry | ||
value: number; | ||
// pointer to next prop | ||
next: number; | ||
}; | ||
} |
@@ -28,3 +28,3 @@ /* eslint-env jest */ | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -34,3 +34,3 @@ expect(saverOutput).toMatchInlineSnapshot(` | ||
"length": 159, | ||
"start": 170, | ||
"start": 178, | ||
} | ||
@@ -37,0 +37,0 @@ `); |
@@ -11,2 +11,3 @@ // import { isPrimitive, primitiveValueToEntry } from "./utils"; | ||
dataView: DataView, | ||
arrayAdditionalAllocation: number, | ||
objectToSave: any | ||
@@ -22,3 +23,8 @@ ) { | ||
for (const [key, value] of objectEntries) { | ||
const rOfValue = saveValue(textEncoder, dataView, value); | ||
const rOfValue = saveValue( | ||
textEncoder, | ||
dataView, | ||
arrayAdditionalAllocation, | ||
value | ||
); | ||
@@ -25,0 +31,0 @@ const objectPropEntry: ObjectPropEntry = { |
@@ -27,3 +27,3 @@ /* eslint-env jest */ | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -71,3 +71,3 @@ const objectWrapper: any = createObjectWrapper( | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -102,3 +102,3 @@ const objectWrapper: any = createObjectWrapper( | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -144,3 +144,3 @@ const objectWrapper = createObjectWrapper( | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -187,3 +187,3 @@ const objectWrapper = createObjectWrapper( | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -190,0 +190,0 @@ const objectWrapper = createObjectWrapper( |
import { ObjectEntry } from "./interfaces"; | ||
import { readEntry, writeEntry, appendEntry } from "./store"; | ||
import { ENTRY_TYPE, isPrimitiveEntryType } from "./entry-types"; | ||
import { ENTRY_TYPE } from "./entry-types"; | ||
import { | ||
@@ -12,2 +12,4 @@ findObjectPropertyEntry, | ||
import { entryToFinalJavaScriptValue } from "./entryToFinalJavaScriptValue"; | ||
export const GET_UNDERLYING_ARRAY_BUFFER_SYMBOL = Symbol( | ||
@@ -23,3 +25,4 @@ "GET_UNDERLYING_ARRAY_BUFFER_SYMBOL" | ||
private textEncoder: any, | ||
private isTopLevel: boolean | ||
private isTopLevel: boolean, | ||
private arrayAdditionalAllocation: number | ||
) {} | ||
@@ -65,24 +68,10 @@ | ||
if (valueEntry.type === ENTRY_TYPE.NULL) { | ||
return null; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.UNDEFINED) { | ||
return undefined; | ||
} | ||
if (isPrimitiveEntryType(valueEntry.type)) { | ||
return valueEntry.value; | ||
} | ||
if (valueEntry.type === ENTRY_TYPE.OBJECT) { | ||
return createObjectWrapper( | ||
this.dataView, | ||
foundEntry[1].value.value, | ||
this.textDecoder, | ||
this.textEncoder | ||
); | ||
} | ||
throw new Error("unsupported yet"); | ||
return entryToFinalJavaScriptValue( | ||
this.dataView, | ||
this.textDecoder, | ||
this.textEncoder, | ||
this.arrayAdditionalAllocation, | ||
valueEntry, | ||
foundEntry[1].value.value | ||
); | ||
} | ||
@@ -147,2 +136,3 @@ | ||
this.dataView, | ||
this.arrayAdditionalAllocation, | ||
value | ||
@@ -265,6 +255,7 @@ ); | ||
textEncoder: any, | ||
isTopLevel = false | ||
isTopLevel = false, | ||
arrayAdditionalAllocation = 50 | ||
): T { | ||
return new Proxy( | ||
{ bla: 1 }, | ||
{ objectBufferWrapper: "objectBufferWrapper" }, | ||
new ObjectWrapper( | ||
@@ -275,5 +266,6 @@ dataView, | ||
textEncoder, | ||
isTopLevel | ||
isTopLevel, | ||
arrayAdditionalAllocation | ||
) | ||
) as any; | ||
} |
@@ -29,3 +29,3 @@ /* eslint-env jest */ | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -41,3 +41,3 @@ let foundEntry = findObjectPropertyEntry( | ||
Array [ | ||
71, | ||
79, | ||
Object { | ||
@@ -47,4 +47,4 @@ "type": 8, | ||
"key": "a", | ||
"next": 42, | ||
"value": 62, | ||
"next": 50, | ||
"value": 70, | ||
}, | ||
@@ -82,3 +82,3 @@ }, | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -97,4 +97,4 @@ const gotEntries = getObjectPropertiesEntries( | ||
"key": "5", | ||
"next": 129, | ||
"value": 141, | ||
"next": 137, | ||
"value": 149, | ||
}, | ||
@@ -106,4 +106,4 @@ }, | ||
"key": "a", | ||
"next": 100, | ||
"value": 120, | ||
"next": 108, | ||
"value": 128, | ||
}, | ||
@@ -115,4 +115,4 @@ }, | ||
"key": "kawabanga", | ||
"next": 87, | ||
"value": 99, | ||
"next": 95, | ||
"value": 107, | ||
}, | ||
@@ -124,4 +124,4 @@ }, | ||
"key": "b", | ||
"next": 51, | ||
"value": 74, | ||
"next": 59, | ||
"value": 82, | ||
}, | ||
@@ -134,3 +134,3 @@ }, | ||
"next": 0, | ||
"value": 46, | ||
"value": 54, | ||
}, | ||
@@ -155,3 +155,3 @@ }, | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -178,4 +178,4 @@ deleteObjectPropertyEntryByKey( | ||
"key": "b", | ||
"next": 30, | ||
"value": 42, | ||
"next": 38, | ||
"value": 50, | ||
}, | ||
@@ -187,4 +187,4 @@ }, | ||
"key": "c", | ||
"next": 17, | ||
"value": 29, | ||
"next": 25, | ||
"value": 37, | ||
}, | ||
@@ -197,3 +197,3 @@ }, | ||
"next": 0, | ||
"value": 16, | ||
"value": 24, | ||
}, | ||
@@ -219,3 +219,3 @@ }, | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -238,4 +238,4 @@ deleteObjectPropertyEntryByKey( | ||
"key": "a", | ||
"next": 55, | ||
"value": 67, | ||
"next": 63, | ||
"value": 75, | ||
}, | ||
@@ -247,4 +247,4 @@ }, | ||
"key": "b", | ||
"next": 30, | ||
"value": 42, | ||
"next": 38, | ||
"value": 50, | ||
}, | ||
@@ -257,3 +257,3 @@ }, | ||
"next": 0, | ||
"value": 29, | ||
"value": 37, | ||
}, | ||
@@ -280,3 +280,3 @@ }, | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -299,4 +299,4 @@ deleteObjectPropertyEntryByKey( | ||
"key": "a", | ||
"next": 76, | ||
"value": 88, | ||
"next": 84, | ||
"value": 96, | ||
}, | ||
@@ -308,4 +308,4 @@ }, | ||
"key": "b", | ||
"next": 38, | ||
"value": 63, | ||
"next": 46, | ||
"value": 71, | ||
}, | ||
@@ -317,4 +317,4 @@ }, | ||
"key": "d", | ||
"next": 25, | ||
"value": 37, | ||
"next": 33, | ||
"value": 45, | ||
}, | ||
@@ -327,3 +327,3 @@ }, | ||
"next": 0, | ||
"value": 16, | ||
"value": 24, | ||
}, | ||
@@ -348,3 +348,3 @@ }, | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -359,3 +359,3 @@ const found = findLastObjectPropertyEntry( | ||
Array [ | ||
17, | ||
25, | ||
Object { | ||
@@ -366,3 +366,3 @@ "type": 8, | ||
"next": 0, | ||
"value": 16, | ||
"value": 24, | ||
}, | ||
@@ -382,4 +382,4 @@ }, | ||
"key": "a", | ||
"next": 42, | ||
"value": 54, | ||
"next": 50, | ||
"value": 62, | ||
}, | ||
@@ -391,4 +391,4 @@ }, | ||
"key": "b", | ||
"next": 17, | ||
"value": 29, | ||
"next": 25, | ||
"value": 37, | ||
}, | ||
@@ -401,3 +401,3 @@ }, | ||
"next": 0, | ||
"value": 16, | ||
"value": 24, | ||
}, | ||
@@ -418,3 +418,3 @@ }, | ||
const saverOutput = objectSaver(textEncoder, dataView, objectToSave); | ||
const saverOutput = objectSaver(textEncoder, dataView, 0, objectToSave); | ||
@@ -429,3 +429,3 @@ const found = findLastObjectPropertyEntry( | ||
Array [ | ||
16, | ||
24, | ||
Object { | ||
@@ -432,0 +432,0 @@ "type": 7, |
import { primitiveValueToEntry, isPrimitive } from "./utils"; | ||
import { appendEntry } from "./store"; | ||
import { objectSaver } from "./objectSaver"; | ||
import { arraySaver } from "./arraySaver"; | ||
export function saveValue(textEncoder: any, dataView: DataView, value: any) { | ||
export function saveValue( | ||
textEncoder: any, | ||
dataView: DataView, | ||
arrayAdditionalAllocation: number, | ||
value: any | ||
) { | ||
let totalWrittenBytes = 0; | ||
@@ -15,4 +21,18 @@ let valuePointer = 0; | ||
totalWrittenBytes += length; | ||
} else if (Array.isArray(value)) { | ||
const { start, length } = arraySaver( | ||
textEncoder, | ||
dataView, | ||
arrayAdditionalAllocation, | ||
value | ||
); | ||
valuePointer = start; | ||
totalWrittenBytes += length; | ||
} else if (typeof value === "object") { | ||
const { start, length } = objectSaver(textEncoder, dataView, value); | ||
const { start, length } = objectSaver( | ||
textEncoder, | ||
dataView, | ||
arrayAdditionalAllocation, | ||
value | ||
); | ||
valuePointer = start; | ||
@@ -19,0 +39,0 @@ totalWrittenBytes += length; |
@@ -16,3 +16,3 @@ /* eslint-env jest */ | ||
test("initializeArrayBuffer", () => { | ||
const arrayBuffer = new ArrayBuffer(16); | ||
const arrayBuffer = new ArrayBuffer(20); | ||
@@ -25,3 +25,3 @@ initializeArrayBuffer(arrayBuffer); | ||
"2:0x00", | ||
"3:0x10", | ||
"3:0x00", | ||
"4:0x00", | ||
@@ -34,3 +34,3 @@ "5:0x00", | ||
"10:0x00", | ||
"11:0x10", | ||
"11:0x18", | ||
"12:0x00", | ||
@@ -40,2 +40,6 @@ "13:0x00", | ||
"15:0x00", | ||
"16:0x00", | ||
"17:0x00", | ||
"18:0x00", | ||
"19:0x18", | ||
] | ||
@@ -368,3 +372,3 @@ `); | ||
test("appendEntry", () => { | ||
const arrayBuffer = new ArrayBuffer(32); | ||
const arrayBuffer = new ArrayBuffer(40); | ||
const textEncoder = new utils.TextEncoder(); | ||
@@ -386,3 +390,3 @@ const dataView = new DataView(arrayBuffer); | ||
"length": 14, | ||
"start": 16, | ||
"start": 24, | ||
} | ||
@@ -396,3 +400,3 @@ `); | ||
"2:0x00", | ||
"3:0x1e", | ||
"3:0x00", | ||
"4:0x00", | ||
@@ -405,3 +409,3 @@ "5:0x00", | ||
"10:0x00", | ||
"11:0x10", | ||
"11:0x26", | ||
"12:0x00", | ||
@@ -411,18 +415,26 @@ "13:0x00", | ||
"15:0x00", | ||
"16:0x05", | ||
"16:0x00", | ||
"17:0x00", | ||
"18:0x0b", | ||
"19:0x69", | ||
"20:0x6d", | ||
"21:0x20", | ||
"22:0x61", | ||
"23:0x20", | ||
"24:0x73", | ||
"25:0x74", | ||
"26:0x72", | ||
"18:0x00", | ||
"19:0x18", | ||
"20:0x00", | ||
"21:0x00", | ||
"22:0x00", | ||
"23:0x00", | ||
"24:0x05", | ||
"25:0x00", | ||
"26:0x0b", | ||
"27:0x69", | ||
"28:0x6e", | ||
"29:0x67", | ||
"30:0x00", | ||
"31:0x00", | ||
"28:0x6d", | ||
"29:0x20", | ||
"30:0x61", | ||
"31:0x20", | ||
"32:0x73", | ||
"33:0x74", | ||
"34:0x72", | ||
"35:0x69", | ||
"36:0x6e", | ||
"37:0x67", | ||
"38:0x00", | ||
"39:0x00", | ||
] | ||
@@ -443,3 +455,3 @@ `); | ||
"length": 2, | ||
"start": 30, | ||
"start": 38, | ||
} | ||
@@ -452,3 +464,3 @@ `); | ||
"2:0x00", | ||
"3:0x20", | ||
"3:0x00", | ||
"4:0x00", | ||
@@ -461,3 +473,3 @@ "5:0x00", | ||
"10:0x00", | ||
"11:0x10", | ||
"11:0x28", | ||
"12:0x00", | ||
@@ -467,18 +479,26 @@ "13:0x00", | ||
"15:0x00", | ||
"16:0x05", | ||
"16:0x00", | ||
"17:0x00", | ||
"18:0x0b", | ||
"19:0x69", | ||
"20:0x6d", | ||
"21:0x20", | ||
"22:0x61", | ||
"23:0x20", | ||
"24:0x73", | ||
"25:0x74", | ||
"26:0x72", | ||
"18:0x00", | ||
"19:0x18", | ||
"20:0x00", | ||
"21:0x00", | ||
"22:0x00", | ||
"23:0x00", | ||
"24:0x05", | ||
"25:0x00", | ||
"26:0x0b", | ||
"27:0x69", | ||
"28:0x6e", | ||
"29:0x67", | ||
"30:0x06", | ||
"31:0x01", | ||
"28:0x6d", | ||
"29:0x20", | ||
"30:0x61", | ||
"31:0x20", | ||
"32:0x73", | ||
"33:0x74", | ||
"34:0x72", | ||
"35:0x69", | ||
"36:0x6e", | ||
"37:0x67", | ||
"38:0x06", | ||
"39:0x01", | ||
] | ||
@@ -485,0 +505,0 @@ `); |
import { ENTRY_TYPE } from "./entry-types"; | ||
import { Entry, ObjectPropEntry } from "./interfaces"; | ||
import { arrayBufferCopyTo } from "./utils"; | ||
const DEFAULT_ARRAY_BUFFER_SIZE = 10 ^ 6; | ||
@@ -21,7 +22,10 @@ | ||
// End of data pointer | ||
dataView.setUint32(0, 16); | ||
// global lock | ||
dataView.setInt32(0, 0); | ||
// End of data pointer / first free byte | ||
dataView.setUint32(8, 24); | ||
// first entry pointer | ||
dataView.setUint32(8, 16); | ||
dataView.setUint32(16, 24); | ||
@@ -111,3 +115,14 @@ return dataView; | ||
case ENTRY_TYPE.ARRAY: | ||
dataView.setUint32(cursor, entry.value); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
dataView.setUint32(cursor, entry.length); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
dataView.setUint32(cursor, entry.allocatedLength); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
break; | ||
default: | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
throw new Error(ENTRY_TYPE[entry.type] + " Not implemented yet"); | ||
@@ -128,6 +143,6 @@ } | ||
// End of data pointer | ||
const firstFreeByte = dataView.getUint32(0); | ||
const firstFreeByte = dataView.getUint32(8); | ||
const written = writeEntry(dataView, firstFreeByte, entry, textEncoder); | ||
dataView.setUint32(0, firstFreeByte + written); | ||
dataView.setUint32(8, firstFreeByte + written); | ||
@@ -182,8 +197,10 @@ return { | ||
entry.value = textDecoder.decode( | ||
// this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
new Uint8Array(dataView.buffer.slice(cursor, cursor + stringLength)) | ||
); | ||
// this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
// eslint-disable-next-line no-case-declarations | ||
const tempAB = new ArrayBuffer(stringLength); | ||
arrayBufferCopyTo(dataView.buffer, cursor, stringLength, tempAB, 0); | ||
entry.value = textDecoder.decode(tempAB); | ||
cursor += stringLength; | ||
@@ -213,11 +230,11 @@ | ||
// this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
// eslint-disable-next-line no-case-declarations | ||
const tempAB2 = new ArrayBuffer(keyStringLength); | ||
arrayBufferCopyTo(dataView.buffer, cursor, keyStringLength, tempAB2, 0); | ||
// eslint-disable-next-line no-case-declarations | ||
const objectPropsValue: ObjectPropEntry["value"] = { | ||
key: textDecoder.decode( | ||
// this wrapping is needed until: | ||
// https://github.com/whatwg/encoding/issues/172 | ||
new Uint8Array( | ||
dataView.buffer.slice(cursor, cursor + keyStringLength) | ||
) | ||
), | ||
key: textDecoder.decode(tempAB2), | ||
value: dataView.getUint32(cursor + keyStringLength), | ||
@@ -237,2 +254,11 @@ next: dataView.getUint32( | ||
case ENTRY_TYPE.ARRAY: | ||
entry.value = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
entry.length = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
entry.allocatedLength = dataView.getUint32(cursor); | ||
cursor += Uint32Array.BYTES_PER_ELEMENT; | ||
break; | ||
default: | ||
@@ -244,1 +270,9 @@ throw new Error(ENTRY_TYPE[entryType] + " Not implemented yet"); | ||
} | ||
export function reserveMemory(dataView: DataView, length: number) { | ||
const firstFreeByte = dataView.getUint32(8); | ||
dataView.setUint32(8, firstFreeByte + length); | ||
return firstFreeByte; | ||
} |
@@ -79,1 +79,16 @@ import { primitive, Entry } from "./interfaces"; | ||
} | ||
export function arrayBufferCopyTo( | ||
origin: ArrayBuffer, | ||
startByte: number, | ||
length: number, | ||
target: ArrayBuffer, | ||
toTargetByte: number | ||
) { | ||
const copyFrom = new Uint8Array(origin); | ||
const copyTo = new Uint8Array(target); | ||
for (let i = 0; i < length; i += 1) { | ||
copyTo[toTargetByte + i] = copyFrom[startByte + i]; | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
246692
86
6712
60
27