Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 26 additions & 36 deletions src/direct/decoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,27 @@ const $ = (cache, index, value) => {

/**
* @param {Uint8Array} input
* @param {number} index
*/
const number = (input, index) => {
while (index < 8) u8a8[index++] = input[i++];
const number = input => {
u8a8[0] = input[i++];
u8a8[1] = input[i++];
u8a8[2] = input[i++];
u8a8[3] = input[i++];
u8a8[4] = input[i++];
u8a8[5] = input[i++];
u8a8[6] = input[i++];
u8a8[7] = input[i++];
};

/**
* @param {Uint8Array} input
* @param {number} i
* @returns {number}
*/
const size = (input, i) => {
for (let j = 0; j < 4; j++) u8a8[j] = input[i++];
const size = input => {
u8a8[0] = input[i++];
u8a8[1] = input[i++];
u8a8[2] = input[i++];
u8a8[3] = input[i++];
return dv.getUint32(0, true);
};

Expand All @@ -76,23 +84,19 @@ const size = (input, i) => {
const deflate = (input, cache) => {
switch (input[i++]) {
case NUMBER: {
number(input, 0);
number(input);
return dv.getFloat64(0, true);
}
case UI8: return input[i++];
case OBJECT: {
const object = $(cache, i - 1, {});
const length = size(input, i);
i += 4;
for (let j = 0; j < length; j++)
for (let j = 0, length = size(input); j < length; j++)
object[deflate(input, cache)] = deflate(input, cache);
return object;
}
case ARRAY: {
const array = $(cache, i - 1, []);
const length = size(input, i);
i += 4;
for (let j = 0; j < length; j++)
for (let j = 0, length = size(input); j < length; j++)
array.push(deflate(input, cache));
return array;
}
Expand All @@ -103,32 +107,28 @@ const deflate = (input, cache) => {
}
case BUFFER: {
const index = i - 1;
const length = size(input, i);
return $(cache, index, input.slice(i += 4, i += length).buffer);
const length = size(input);
return $(cache, index, input.slice(i, i += length).buffer);
}
case STRING: {
const index = i - 1;
const length = size(input, i);
const length = size(input);
// this could be a subarray but it's not supported on the Web and
// it wouldn't work with arrays instead of typed arrays.
return $(cache, index, textDecoder.decode(input.slice(i += 4, i += length)));
return $(cache, index, textDecoder.decode(input.slice(i, i += length)));
}
case DATE: {
return $(cache, i - 1, new Date(deflate(input, cache)));
}
case MAP: {
const map = $(cache, i - 1, new Map);
const length = size(input, i);
i += 4;
for (let j = 0; j < length; j++)
for (let j = 0, length = size(input); j < length; j++)
map.set(deflate(input, cache), deflate(input, cache));
return map;
}
case SET: {
const set = $(cache, i - 1, new Set);
const length = size(input, i);
i += 4;
for (let j = 0; j < length; j++)
for (let j = 0, length = size(input); j < length; j++)
set.add(deflate(input, cache));
return set;
}
Expand All @@ -153,20 +153,10 @@ const deflate = (input, cache) => {
case ZERO: return 0;
case N_ZERO: return -0;
case NULL: return null;
case BIGINT: {
number(input, 0);
return dv.getBigInt64(0, true);
}
case BIGUINT: {
number(input, 0);
return dv.getBigUint64(0, true);
}
case BIGINT: return (number(input), dv.getBigInt64(0, true));
case BIGUINT: return (number(input), dv.getBigUint64(0, true));
case SYMBOL: return fromSymbol(deflate(input, cache));
case RECURSION: {
const index = size(input, i);
i += 4;
return cache.get(index);
}
case RECURSION: return cache.get(size(input));
// this covers functions too
default: return undefined;
}
Expand Down
38 changes: 30 additions & 8 deletions src/direct/encoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
import { isArray, isView, push } from '../utils/index.js';
import { encoder as textEncoder } from '../utils/text.js';
import { toSymbol } from '../utils/symbol.js';
import { dv, u8a8, u8a4 } from './views.js';
import { dv, u8a8 } from './views.js';
import { toTag } from '../utils/global.js';

/** @typedef {Map<number, number[]>} Cache */
Expand All @@ -56,7 +56,7 @@ const process = (input, output, cache) => {
const unknown = !value;
if (unknown) {
dv.setUint32(0, output.length, true);
cache.set(input, [u8a4[0], u8a4[1], u8a4[2], u8a4[3]]);
cache.set(input, [u8a8[0], u8a8[1], u8a8[2], u8a8[3]]);
}
else
output.push(RECURSION, value[0], value[1], value[2], value[3]);
Expand All @@ -70,7 +70,7 @@ const process = (input, output, cache) => {
*/
const set = (output, type, length) => {
dv.setUint32(0, length, true);
output.push(type, u8a4[0], u8a4[1], u8a4[2], u8a4[3]);
output.push(type, u8a8[0], u8a8[1], u8a8[2], u8a8[3]);
};

/**
Expand Down Expand Up @@ -117,7 +117,7 @@ const inflate = (input, output, cache) => {
case input instanceof ArrayBuffer: {
const ui8a = new Uint8Array(input);
set(output, BUFFER, ui8a.length);
push(output, ui8a);
pushView(output, ui8a);
break;
}
case input instanceof Date:
Expand Down Expand Up @@ -173,7 +173,7 @@ const inflate = (input, output, cache) => {
if (process(input, output, cache)) {
const encoded = textEncoder.encode(input);
set(output, STRING, encoded.length);
push(output, encoded);
pushView(output, encoded);
}
break;
}
Expand Down Expand Up @@ -204,28 +204,50 @@ const inflate = (input, output, cache) => {
}
};

let pushView = push;

/**
* @param {any} value
* @returns {number[]}
*/
export const encode = value => {
const output = [];
pushView = push;
inflate(value, output, new Map);
return output;
};

/**
* @param {{ byteOffset?: number }} [options]
* @param {{ byteOffset?: number, splitViews?: boolean }} [options]
* @returns {(value: any, buffer: SharedArrayBuffer) => number}
*/
export const encoder = ({ byteOffset = 0 } = {}) => (value, buffer) => {
const output = encode(value);
export const encoder = ({ byteOffset = 0, splitViews = false } = {}) => (value, buffer) => {
let output = [], views = output;
pushView = splitViews ?
(views = [], (output, value) => {
const length = value.length;
// avoid complexity for small buffers (short keys and whatnot)
if (length < 129) output.push.apply(output, value);
else {
views.push([output.length, value]);
output.length += value.length;
}
}) :
push
;
inflate(value, output, new Map);
const length = output.length;
const size = length + byteOffset;
if (buffer.byteLength < size) {
//@ts-ignore
buffer.grow(size);
}
new Uint8Array(buffer, byteOffset, length).set(output);
if (splitViews) {
for (let i = 0, length = views.length; i < length; i++) {
const [offset, value] = views[i];
new Uint8Array(buffer, byteOffset + offset, value.length).set(value);
}
}
return length;
};
1 change: 0 additions & 1 deletion src/direct/views.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const buffer = new ArrayBuffer(8);
export const dv = new DataView(buffer);
export const u8a8 = new Uint8Array(buffer);
export const u8a4 = new Uint8Array(buffer, 0, 4);
4 changes: 2 additions & 2 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@ export const toKey = value => typeof value === 'string' ?
const MAX_ARGS = 0x7FFF;

export const push = (output, value) => {
for (let i = 0, length = value.length; i < length; i += MAX_ARGS)
output.push.apply(output, value.subarray(i, i + MAX_ARGS));
for (let $ = output.push, i = 0, length = value.length; i < length; i += MAX_ARGS)
$.apply(output, value.subarray(i, i + MAX_ARGS));
};
32 changes: 22 additions & 10 deletions src/utils/typed.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,34 @@ import { fromArray } from './index.js';
/** @typedef {[ArrayBufferLike|number[], number]} BufferDetails */
/** @typedef {[string, BufferDetails, number, number]} ViewDetails */

export const arrayBuffer = (length, maxByteLength, value) => {
const buffer = maxByteLength ? new ArrayBuffer(length, { maxByteLength }) : new ArrayBuffer(length);
new Uint8Array(buffer).set(value);
return buffer;
};
/**
* @param {number} length
* @param {number} maxByteLength
* @returns {ArrayBufferLike}
*/
const resizable = (length, maxByteLength) => new ArrayBuffer(length, { maxByteLength });

/**
* @param {BufferDetails} details
* @param {boolean} direct
* @returns {ArrayBufferLike}
*/
export const fromBuffer = ([value, maxByteLength], direct) => arrayBuffer(
direct ? /** @type {ArrayBufferLike} */ (value).byteLength : /** @type {number[]} */ (value).length,
maxByteLength,
value,
);
export const fromBuffer = ([value, maxByteLength], direct) => {
const length = direct ? /** @type {ArrayBufferLike} */ (value).byteLength : /** @type {number[]} */ (value).length;
if (direct) {
if (maxByteLength) {
const buffer = resizable(length, maxByteLength);
new Uint8Array(buffer).set(new Uint8Array(/** @type {ArrayBufferLike} */ (value)));
value = buffer;
}
}
else {
const buffer = maxByteLength ? resizable(length, maxByteLength) : new ArrayBuffer(length);
new Uint8Array(buffer).set(/** @type {number[]} */ (value));
value = buffer;
}
return /** @type {ArrayBufferLike} */ (value);
};

/**
* @param {ViewDetails} details
Expand Down
Loading