1049 lines
36 KiB
JavaScript
1049 lines
36 KiB
JavaScript
var flexbuffers = {};
|
|
|
|
flexbuffers.BitWidth = {
|
|
WIDTH8: 0,
|
|
WIDTH16: 1,
|
|
WIDTH32: 2,
|
|
WIDTH64: 3,
|
|
};
|
|
|
|
flexbuffers.BitWidthUtil = {};
|
|
|
|
flexbuffers.BitWidthUtil.toByteWidth = (bitWidth) => {
|
|
return 1 << bitWidth;
|
|
};
|
|
|
|
flexbuffers.BitWidthUtil.iwidth = (value) => {
|
|
if (value >= -128 && value <= 127) return 0/*flexbuffers.BitWidth.WIDTH8*/;
|
|
if (value >= -32768 && value <= 32767) return 1/*flexbuffers.BitWidth.WIDTH16*/;
|
|
if (value >= -2147483648 && value <= 2147483647) return 2/*flexbuffers.BitWidth.WIDTH32*/;
|
|
return 3/*flexbuffers.BitWidth.WIDTH64*/;
|
|
};
|
|
|
|
flexbuffers.BitWidthUtil.fwidth = (value) => {
|
|
return value === Math.fround(value) ? 2 /*flexbuffers.BitWidth.WIDTH32*/: 3 /*flexbuffers.BitWidth.WIDTH64*/;
|
|
};
|
|
|
|
flexbuffers.BitWidthUtil.uwidth = (value) => {
|
|
if (value <= 255) return 0; //flexbuffers.BitWidth.WIDTH8;
|
|
if (value <= 65535) return 1; //flexbuffers.BitWidth.WIDTH16;
|
|
if (value <= 4294967295) return 2; //flexbuffers.BitWidth.WIDTH32;
|
|
return 3; //flexbuffers.BitWidth.WIDTH64;
|
|
};
|
|
|
|
flexbuffers.BitWidthUtil.fromByteWidth = (value) => {
|
|
if (value === 1) return 0; //flexbuffers.BitWidth.WIDTH8;
|
|
if (value === 2) return 1; //flexbuffers.BitWidth.WIDTH16;
|
|
if (value === 4) return 2; //flexbuffers.BitWidth.WIDTH32;
|
|
return 3; //flexbuffers.BitWidth.WIDTH64;
|
|
};
|
|
|
|
flexbuffers.BitWidthUtil.paddingSize = (bufSize, scalarSize) => {
|
|
return (~bufSize + 1) & (scalarSize - 1);
|
|
};
|
|
|
|
flexbuffers.ValueType = {
|
|
NULL: 0,
|
|
INT: 1,
|
|
UINT: 2,
|
|
FLOAT: 3,
|
|
KEY: 4,
|
|
STRING: 5,
|
|
INDIRECT_INT: 6,
|
|
INDIRECT_UINT: 7,
|
|
INDIRECT_FLOAT: 8,
|
|
MAP: 9,
|
|
VECTOR: 10,
|
|
VECTOR_INT: 11,
|
|
VECTOR_UINT: 12,
|
|
VECTOR_FLOAT: 13,
|
|
VECTOR_KEY: 14,
|
|
VECTOR_STRING_DEPRECATED: 15,
|
|
VECTOR_INT2: 16,
|
|
VECTOR_UINT2: 17,
|
|
VECTOR_FLOAT2: 18,
|
|
VECTOR_INT3: 19,
|
|
VECTOR_UINT3: 20,
|
|
VECTOR_FLOAT3: 21,
|
|
VECTOR_INT4: 22,
|
|
VECTOR_UINT4: 23,
|
|
VECTOR_FLOAT4: 24,
|
|
BLOB: 25,
|
|
BOOL: 26,
|
|
VECTOR_BOOL: 36,
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil = {};
|
|
|
|
flexbuffers.ValueTypeUtil.isInline = (value) => {
|
|
return value === flexbuffers.ValueType.BOOL
|
|
|| value <= flexbuffers.ValueType.FLOAT;
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.isNumber = (value) => {
|
|
return value >= flexbuffers.ValueType.INT
|
|
&& value <= flexbuffers.ValueType.FLOAT;
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.isIndirectNumber = (value) => {
|
|
return value >= flexbuffers.ValueType.INDIRECT_INT
|
|
&& value <= flexbuffers.ValueType.INDIRECT_FLOAT;
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.isTypedVectorElement = (value) => {
|
|
return value === flexbuffers.ValueType.BOOL
|
|
|| (value >= flexbuffers.ValueType.INT
|
|
&& value <= flexbuffers.ValueType.STRING);
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.isTypedVector = (value) => {
|
|
return value === flexbuffers.ValueType.VECTOR_BOOL
|
|
|| (value >= flexbuffers.ValueType.VECTOR_INT
|
|
&& value <= flexbuffers.ValueType.VECTOR_STRING_DEPRECATED);
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.isFixedTypedVector = (value) => {
|
|
return value >= flexbuffers.ValueType.VECTOR_INT2
|
|
&& value <= flexbuffers.ValueType.VECTOR_FLOAT4;
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.isAVector = (value) => {
|
|
return flexbuffers.ValueTypeUtil.isTypedVector(value)
|
|
|| flexbuffers.ValueTypeUtil.isFixedTypedVector(value)
|
|
|| value === flexbuffers.ValueType.VECTOR;
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.toTypedVector = (valueType, length) => {
|
|
if (length === 0) return valueType - flexbuffers.ValueType.INT + flexbuffers.ValueType.VECTOR_INT;
|
|
if (length === 2) return valueType - flexbuffers.ValueType.INT + flexbuffers.ValueType.VECTOR_INT2;
|
|
if (length === 3) return valueType - flexbuffers.ValueType.INT + flexbuffers.ValueType.VECTOR_INT3;
|
|
if (length === 4) return valueType - flexbuffers.ValueType.INT + flexbuffers.ValueType.VECTOR_INT4;
|
|
throw "Unexpected length " + length;
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.typedVectorElementType = (valueType) => {
|
|
return valueType - flexbuffers.ValueType.VECTOR_INT + flexbuffers.ValueType.INT;
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.fixedTypedVectorElementType = (valueType) => {
|
|
return ((valueType - flexbuffers.ValueType.VECTOR_INT2) % 3) + flexbuffers.ValueType.INT;
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.fixedTypedVectorElementSize = (valueType) => {
|
|
// The x / y >> 0 trick is to have an int division. Suppose to be faster than Math.floor()
|
|
return (((valueType - flexbuffers.ValueType.VECTOR_INT2) / 3) >> 0) + 2;
|
|
};
|
|
|
|
flexbuffers.ValueTypeUtil.packedType = (valueType, bitWidth) => {
|
|
return bitWidth | (valueType << 2);
|
|
};
|
|
|
|
flexbuffers.toReference = (buffer) => {
|
|
|
|
// Add to readInt, readUInt, readFloat in order to check for offset bugs
|
|
function validateOffset(dataView, offset, width) {
|
|
if (dataView.byteLength <= offset + width || offset & (flexbuffers.BitWidthUtil.toByteWidth(width) - 1) !== 0) {
|
|
throw "Bad offset: " + offset + ", width: " + width;
|
|
}
|
|
}
|
|
|
|
function readInt(dataView, offset, width) {
|
|
if (width < 2) {
|
|
if (width < 1) {
|
|
return dataView.getInt8(offset);
|
|
} else {
|
|
return dataView.getInt16(offset, true);
|
|
}
|
|
} else {
|
|
if (width < 3) {
|
|
return dataView.getInt32(offset, true)
|
|
} else {
|
|
if (dataView.setBigInt64 === undefined) {
|
|
return {
|
|
low: dataView.getInt32(offset, true),
|
|
high: dataView.getInt32(offset + 4, true)
|
|
}
|
|
}
|
|
return dataView.getBigInt64(offset, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
function readUInt(dataView, offset, width) {
|
|
if (width < 2) {
|
|
if (width < 1) {
|
|
return dataView.getUint8(offset);
|
|
} else {
|
|
return dataView.getUint16(offset, true);
|
|
}
|
|
} else {
|
|
if (width < 3) {
|
|
return dataView.getUint32(offset, true)
|
|
} else {
|
|
if (dataView.getBigUint64 === undefined) {
|
|
return {
|
|
low: dataView.getUint32(offset, true),
|
|
high: dataView.getUint32(offset + 4, true)
|
|
}
|
|
}
|
|
return dataView.getBigUint64(offset, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
function readFloat(dataView, offset, width) {
|
|
if (width < 2 /*flexbuffers.BitWidth.WIDTH32*/) {
|
|
throw "Bad width: " + width;
|
|
}
|
|
if (width === 2 /*flexbuffers.BitWidth.WIDTH32*/) {
|
|
return dataView.getFloat32(offset, true);
|
|
}
|
|
return dataView.getFloat64(offset, true);
|
|
}
|
|
|
|
function indirect(dataView, offset, width) {
|
|
const step = readUInt(dataView, offset, width);
|
|
return offset - step;
|
|
}
|
|
|
|
function keyIndex(key, dataView, offset, parentWidth, byteWidth, length) {
|
|
const input = toUTF8Array(key);
|
|
const keysVectorOffset = indirect(dataView, offset, parentWidth) - byteWidth * 3;
|
|
const bitWidth = flexbuffers.BitWidthUtil.fromByteWidth(byteWidth);
|
|
const indirectOffset = keysVectorOffset - readUInt(dataView, keysVectorOffset, bitWidth);
|
|
const _byteWidth = readUInt(dataView, keysVectorOffset + byteWidth, bitWidth);
|
|
let low = 0;
|
|
let high = length - 1;
|
|
while (low <= high) {
|
|
const mid = (high + low) >> 1;
|
|
const dif = diffKeys(input, mid, dataView, indirectOffset, _byteWidth);
|
|
if (dif === 0) return mid;
|
|
if (dif < 0) {
|
|
high = mid - 1;
|
|
} else {
|
|
low = mid + 1;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function diffKeys(input, index, dataView, offset, width) {
|
|
const keyOffset = offset + index * width;
|
|
const keyIndirectOffset = keyOffset - readUInt(dataView, keyOffset, flexbuffers.BitWidthUtil.fromByteWidth(width));
|
|
for (let i = 0; i < input.length; i++) {
|
|
const dif = input[i] - dataView.getUint8(keyIndirectOffset + i);
|
|
if (dif !== 0) {
|
|
return dif;
|
|
}
|
|
}
|
|
return dataView.getUint8(keyIndirectOffset + input.length) === 0 ? 0 : -1;
|
|
}
|
|
|
|
function valueForIndexWithKey(index, key, dataView, offset, parentWidth, byteWidth, length, path) {
|
|
const _indirect = indirect(dataView, offset, parentWidth);
|
|
const elementOffset = _indirect + index * byteWidth;
|
|
const packedType = dataView.getUint8(_indirect + length * byteWidth + index);
|
|
return Reference(dataView, elementOffset, flexbuffers.BitWidthUtil.fromByteWidth(byteWidth), packedType, `${path}/${key}`)
|
|
}
|
|
|
|
function keyForIndex(index, dataView, offset, parentWidth, byteWidth) {
|
|
const keysVectorOffset = indirect(dataView, offset, parentWidth) - byteWidth * 3;
|
|
const bitWidth = flexbuffers.BitWidthUtil.fromByteWidth(byteWidth);
|
|
const indirectOffset = keysVectorOffset - readUInt(dataView, keysVectorOffset, bitWidth);
|
|
const _byteWidth = readUInt(dataView, keysVectorOffset + byteWidth, bitWidth);
|
|
const keyOffset = indirectOffset + index * _byteWidth;
|
|
const keyIndirectOffset = keyOffset - readUInt(dataView, keyOffset, flexbuffers.BitWidthUtil.fromByteWidth(_byteWidth));
|
|
let length = 0;
|
|
while (dataView.getUint8(keyIndirectOffset + length) !== 0) {
|
|
length++;
|
|
}
|
|
return fromUTF8Array(new Uint8Array(dataView.buffer, keyIndirectOffset, length));
|
|
}
|
|
|
|
function Reference(dataView, offset, parentWidth, packedType, path) {
|
|
const byteWidth = 1 << (packedType & 3);
|
|
const valueType = packedType >> 2;
|
|
let length = -1;
|
|
return {
|
|
isNull: function() { return valueType === flexbuffers.ValueType.NULL; },
|
|
isNumber: function() { return flexbuffers.ValueTypeUtil.isNumber(valueType) || flexbuffers.ValueTypeUtil.isIndirectNumber(valueType); },
|
|
isFloat: function() { return flexbuffers.ValueType.FLOAT === valueType || flexbuffers.ValueType.INDIRECT_FLOAT === valueType; },
|
|
isInt: function() { return this.isNumber() && !this.isFloat(); },
|
|
isString: function() { return flexbuffers.ValueType.STRING === valueType || flexbuffers.ValueType.KEY === valueType; },
|
|
isBool: function() { return flexbuffers.ValueType.BOOL === valueType; },
|
|
isBlob: function() { return flexbuffers.ValueType.BLOB === valueType; },
|
|
isVector: function() { return flexbuffers.ValueTypeUtil.isAVector(valueType); },
|
|
isMap: function() { return flexbuffers.ValueType.MAP === valueType; },
|
|
|
|
boolValue: function() {
|
|
if (this.isBool()) {
|
|
return readInt(dataView, offset, parentWidth) > 0;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
intValue: function() {
|
|
if (valueType === flexbuffers.ValueType.INT) {
|
|
return readInt(dataView, offset, parentWidth);
|
|
}
|
|
if (valueType === flexbuffers.ValueType.UINT) {
|
|
return readUInt(dataView, offset, parentWidth);
|
|
}
|
|
if (valueType === flexbuffers.ValueType.INDIRECT_INT) {
|
|
return readInt(dataView, indirect(dataView, offset, parentWidth), flexbuffers.BitWidthUtil.fromByteWidth(byteWidth));
|
|
}
|
|
if (valueType === flexbuffers.ValueType.INDIRECT_UINT) {
|
|
return readUInt(dataView, indirect(dataView, offset, parentWidth), flexbuffers.BitWidthUtil.fromByteWidth(byteWidth));
|
|
}
|
|
return null;
|
|
},
|
|
|
|
floatValue: function() {
|
|
if (valueType === flexbuffers.ValueType.FLOAT) {
|
|
return readFloat(dataView, offset, parentWidth);
|
|
}
|
|
if (valueType === flexbuffers.ValueType.INDIRECT_FLOAT) {
|
|
return readFloat(dataView, indirect(dataView, offset, parentWidth), flexbuffers.BitWidthUtil.fromByteWidth(byteWidth));
|
|
}
|
|
return null;
|
|
},
|
|
|
|
numericValue: function() { return this.floatValue() || this.intValue()},
|
|
|
|
stringValue: function() {
|
|
if (valueType === flexbuffers.ValueType.STRING || valueType === flexbuffers.ValueType.KEY) {
|
|
const begin = indirect(dataView, offset, parentWidth);
|
|
return fromUTF8Array(new Uint8Array(dataView.buffer, begin, this.length()));
|
|
}
|
|
return null;
|
|
},
|
|
|
|
blobValue: function() {
|
|
if (this.isBlob()) {
|
|
const begin = indirect(dataView, offset, parentWidth);
|
|
return new Uint8Array(dataView.buffer, begin, this.length());
|
|
}
|
|
return null;
|
|
},
|
|
|
|
get: function(key) {
|
|
const length = this.length();
|
|
if (Number.isInteger(key) && flexbuffers.ValueTypeUtil.isAVector(valueType)) {
|
|
if (key >= length || key < 0) {
|
|
throw `Key: [${key}] is not applicable on ${path} of ${valueType} length: ${length}`;
|
|
}
|
|
const _indirect = indirect(dataView, offset, parentWidth);
|
|
const elementOffset = _indirect + key * byteWidth;
|
|
let _packedType = dataView.getUint8(_indirect + length * byteWidth + key);
|
|
if (flexbuffers.ValueTypeUtil.isTypedVector(valueType)) {
|
|
const _valueType = flexbuffers.ValueTypeUtil.typedVectorElementType(valueType);
|
|
_packedType = flexbuffers.ValueTypeUtil.packedType(_valueType, flexbuffers.BitWidth.WIDTH8);
|
|
} else if (flexbuffers.ValueTypeUtil.isFixedTypedVector(valueType)) {
|
|
const _valueType = flexbuffers.ValueTypeUtil.fixedTypedVectorElementType(valueType);
|
|
_packedType = flexbuffers.ValueTypeUtil.packedType(_valueType, flexbuffers.BitWidth.WIDTH8);
|
|
}
|
|
return Reference(dataView, elementOffset, flexbuffers.BitWidthUtil.fromByteWidth(byteWidth), _packedType, `${path}[${key}]`);
|
|
}
|
|
if (typeof key === 'string') {
|
|
const index = keyIndex(key, dataView, offset, parentWidth, byteWidth, length);
|
|
if (index !== null) {
|
|
return valueForIndexWithKey(index, key, dataView, offset, parentWidth, byteWidth, length, path)
|
|
}
|
|
}
|
|
throw `Key [${key}] is not applicable on ${path} of ${valueType}`;
|
|
},
|
|
|
|
length: function() {
|
|
let size;
|
|
if (length > -1) {
|
|
return length;
|
|
}
|
|
if (flexbuffers.ValueTypeUtil.isFixedTypedVector(valueType)) {
|
|
length = flexbuffers.ValueTypeUtil.fixedTypedVectorElementSize(valueType);
|
|
} else if (valueType === flexbuffers.ValueType.BLOB
|
|
|| valueType === flexbuffers.ValueType.MAP
|
|
|| flexbuffers.ValueTypeUtil.isAVector(valueType)) {
|
|
length = readUInt(dataView, indirect(dataView, offset, parentWidth) - byteWidth, flexbuffers.BitWidthUtil.fromByteWidth(byteWidth))
|
|
} else if (valueType === flexbuffers.ValueType.NULL) {
|
|
length = 0;
|
|
} else if (valueType === flexbuffers.ValueType.STRING) {
|
|
const _indirect = indirect(dataView, offset, parentWidth);
|
|
let sizeByteWidth = byteWidth;
|
|
size = readUInt(dataView, _indirect - sizeByteWidth, flexbuffers.BitWidthUtil.fromByteWidth(byteWidth));
|
|
while (dataView.getInt8(_indirect + size) !== 0) {
|
|
sizeByteWidth <<= 1;
|
|
size = readUInt(dataView, _indirect - sizeByteWidth, flexbuffers.BitWidthUtil.fromByteWidth(byteWidth));
|
|
}
|
|
length = size;
|
|
} else if (valueType === flexbuffers.ValueType.KEY) {
|
|
const _indirect = indirect(dataView, offset, parentWidth);
|
|
size = 1;
|
|
while (dataView.getInt8(_indirect + size) !== 0) {
|
|
size++;
|
|
}
|
|
length = size;
|
|
} else {
|
|
length = 1;
|
|
}
|
|
return length;
|
|
},
|
|
|
|
toObject: function() {
|
|
const length = this.length();
|
|
if (this.isVector()) {
|
|
let result = [];
|
|
for (let i = 0; i < length; i++) {
|
|
result.push(this.get(i).toObject());
|
|
}
|
|
return result;
|
|
}
|
|
if (this.isMap()) {
|
|
let result = {};
|
|
for (let i = 0; i < length; i++) {
|
|
let key = keyForIndex(i, dataView, offset, parentWidth, byteWidth);
|
|
result[key] = valueForIndexWithKey(i, key, dataView, offset, parentWidth, byteWidth, length, path).toObject();
|
|
}
|
|
return result;
|
|
}
|
|
if (this.isNull()) {
|
|
return null;
|
|
}
|
|
if (this.isBool()) {
|
|
return this.boolValue();
|
|
}
|
|
if (this.isNumber()) {
|
|
return this.numericValue();
|
|
}
|
|
return this.blobValue() || this.stringValue();
|
|
}
|
|
}
|
|
}
|
|
|
|
const len = buffer.byteLength;
|
|
|
|
if (len < 3) {
|
|
throw "Buffer needs to be bigger than 3";
|
|
}
|
|
|
|
const dataView = new DataView(buffer);
|
|
const byteWidth = dataView.getUint8(len - 1);
|
|
const packedType = dataView.getUint8(len - 2);
|
|
const parentWidth = flexbuffers.BitWidthUtil.fromByteWidth(byteWidth);
|
|
const offset = len - byteWidth - 2;
|
|
|
|
return Reference(dataView, offset, parentWidth, packedType, "/")
|
|
};
|
|
|
|
flexbuffers.toObject = (buffer) => {
|
|
return flexbuffers.toReference(buffer).toObject();
|
|
};
|
|
|
|
flexbuffers.builder = (size = 2048, deduplicateString = true, deduplicateKeys = true, deduplicateKeyVectors = true) => {
|
|
let buffer = new ArrayBuffer(size > 0 ? size : 2048);
|
|
let view = new DataView(buffer);
|
|
const stack = [];
|
|
const stackPointers = [];
|
|
let offset = 0;
|
|
let finished = false;
|
|
const stringLookup = {};
|
|
const keyLookup = {};
|
|
const keyVectorLookup = {};
|
|
const indirectIntLookup = {};
|
|
const indirectUIntLookup = {};
|
|
const indirectFloatLookup = {};
|
|
|
|
let dedupStrings = deduplicateString;
|
|
let dedupKeys = deduplicateKeys;
|
|
let dedupKeyVectors = deduplicateKeyVectors;
|
|
|
|
function align(width) {
|
|
const byteWidth = flexbuffers.BitWidthUtil.toByteWidth(width);
|
|
offset += flexbuffers.BitWidthUtil.paddingSize(offset, byteWidth);
|
|
return byteWidth;
|
|
}
|
|
|
|
function computeOffset(newValueSize) {
|
|
const targetOffset = offset + newValueSize;
|
|
let size = buffer.byteLength;
|
|
const prevSize = size;
|
|
while (size < targetOffset) {
|
|
size <<= 1;
|
|
}
|
|
if (prevSize < size) {
|
|
const prevBuffer = buffer;
|
|
buffer = new ArrayBuffer(size);
|
|
view = new DataView(buffer);
|
|
new Uint8Array(buffer).set(new Uint8Array(prevBuffer), 0);
|
|
}
|
|
return targetOffset;
|
|
}
|
|
|
|
function pushInt(value, width) {
|
|
if (width === flexbuffers.BitWidth.WIDTH8) {
|
|
view.setInt8(offset, value);
|
|
} else if (width === flexbuffers.BitWidth.WIDTH16) {
|
|
view.setInt16(offset, value, true);
|
|
} else if (width === flexbuffers.BitWidth.WIDTH32) {
|
|
view.setInt32(offset, value, true);
|
|
} else if (width === flexbuffers.BitWidth.WIDTH64) {
|
|
view.setBigInt64(offset, BigInt(value), true);
|
|
} else {
|
|
throw `Unexpected width: ${width} for value: ${value}`;
|
|
}
|
|
}
|
|
|
|
function pushUInt(value, width) {
|
|
if (width === flexbuffers.BitWidth.WIDTH8) {
|
|
view.setUint8(offset, value);
|
|
} else if (width === flexbuffers.BitWidth.WIDTH16) {
|
|
view.setUint16(offset, value, true);
|
|
} else if (width === flexbuffers.BitWidth.WIDTH32) {
|
|
view.setUint32(offset, value, true);
|
|
} else if (width === flexbuffers.BitWidth.WIDTH64) {
|
|
view.setBigUint64(offset, BigInt(value), true);
|
|
} else {
|
|
throw `Unexpected width: ${width} for value: ${value}`;
|
|
}
|
|
}
|
|
|
|
function writeInt(value, byteWidth) {
|
|
const newOffset = computeOffset(byteWidth);
|
|
pushInt(value, flexbuffers.BitWidthUtil.fromByteWidth(byteWidth));
|
|
offset = newOffset;
|
|
}
|
|
|
|
function writeUInt(value, byteWidth) {
|
|
const newOffset = computeOffset(byteWidth);
|
|
pushUInt(value, flexbuffers.BitWidthUtil.fromByteWidth(byteWidth));
|
|
offset = newOffset;
|
|
}
|
|
|
|
function writeBlob(arrayBuffer) {
|
|
const length = arrayBuffer.byteLength;
|
|
const bitWidth = flexbuffers.BitWidthUtil.uwidth(length);
|
|
const byteWidth = align(bitWidth);
|
|
writeUInt(length, byteWidth);
|
|
const blobOffset = offset;
|
|
const newOffset = computeOffset(length);
|
|
new Uint8Array(buffer).set(new Uint8Array(arrayBuffer), blobOffset);
|
|
stack.push(offsetStackValue(blobOffset, flexbuffers.ValueType.BLOB, bitWidth));
|
|
offset = newOffset;
|
|
}
|
|
|
|
function writeString(str) {
|
|
if (dedupStrings && stringLookup.hasOwnProperty(str)) {
|
|
stack.push(stringLookup[str]);
|
|
return;
|
|
}
|
|
const utf8 = toUTF8Array(str);
|
|
const length = utf8.length;
|
|
const bitWidth = flexbuffers.BitWidthUtil.uwidth(length);
|
|
const byteWidth = align(bitWidth);
|
|
writeUInt(length, byteWidth);
|
|
const stringOffset = offset;
|
|
const newOffset = computeOffset(length + 1);
|
|
new Uint8Array(buffer).set(utf8, stringOffset);
|
|
const stackValue = offsetStackValue(stringOffset, flexbuffers.ValueType.STRING, bitWidth);
|
|
stack.push(stackValue);
|
|
if (dedupStrings) {
|
|
stringLookup[str] = stackValue;
|
|
}
|
|
offset = newOffset;
|
|
}
|
|
|
|
function writeKey(str) {
|
|
if (dedupKeys && keyLookup.hasOwnProperty(str)) {
|
|
stack.push(keyLookup[str]);
|
|
return;
|
|
}
|
|
const utf8 = toUTF8Array(str);
|
|
const length = utf8.length;
|
|
const newOffset = computeOffset(length + 1);
|
|
new Uint8Array(buffer).set(utf8, offset);
|
|
const stackValue = offsetStackValue(offset, flexbuffers.ValueType.KEY, flexbuffers.BitWidth.WIDTH8);
|
|
stack.push(stackValue);
|
|
if (dedupKeys) {
|
|
keyLookup[str] = stackValue;
|
|
}
|
|
offset = newOffset;
|
|
}
|
|
|
|
function writeStackValue(value, byteWidth) {
|
|
const newOffset = computeOffset(byteWidth);
|
|
if (value.isOffset) {
|
|
const relativeOffset = offset - value.offset;
|
|
if (byteWidth === 8 || BigInt(relativeOffset) < (1n << BigInt(byteWidth * 8))) {
|
|
writeUInt(relativeOffset, byteWidth);
|
|
} else {
|
|
throw `Unexpected size ${byteWidth}. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new`
|
|
}
|
|
} else {
|
|
value.writeToBuffer(byteWidth);
|
|
}
|
|
offset = newOffset;
|
|
}
|
|
|
|
function integrityCheckOnValueAddition() {
|
|
if (finished) {
|
|
throw "Adding values after finish is prohibited";
|
|
}
|
|
if (stackPointers.length !== 0 && stackPointers[stackPointers.length - 1].isVector === false) {
|
|
if (stack[stack.length - 1].type !== flexbuffers.ValueType.KEY) {
|
|
throw "Adding value to a map before adding a key is prohibited";
|
|
}
|
|
}
|
|
}
|
|
|
|
function integrityCheckOnKeyAddition() {
|
|
if (finished) {
|
|
throw "Adding values after finish is prohibited";
|
|
}
|
|
if (stackPointers.length === 0 || stackPointers[stackPointers.length - 1].isVector) {
|
|
throw "Adding key before starting a map is prohibited";
|
|
}
|
|
}
|
|
|
|
function startVector() {
|
|
stackPointers.push({stackPosition: stack.length, isVector: true});
|
|
}
|
|
|
|
function startMap(presorted = false) {
|
|
stackPointers.push({stackPosition: stack.length, isVector: false, presorted: presorted});
|
|
}
|
|
|
|
function endVector(stackPointer) {
|
|
const vecLength = stack.length - stackPointer.stackPosition;
|
|
const vec = createVector(stackPointer.stackPosition, vecLength, 1);
|
|
stack.splice(stackPointer.stackPosition, vecLength);
|
|
stack.push(vec);
|
|
}
|
|
|
|
function endMap(stackPointer) {
|
|
if (!stackPointer.presorted) {
|
|
sort(stackPointer);
|
|
}
|
|
let keyVectorHash = "";
|
|
for (let i = stackPointer.stackPosition; i < stack.length; i += 2) {
|
|
keyVectorHash += `,${stack[i].offset}`;
|
|
}
|
|
const vecLength = (stack.length - stackPointer.stackPosition) >> 1;
|
|
|
|
if (dedupKeyVectors && !keyVectorLookup.hasOwnProperty(keyVectorHash)) {
|
|
keyVectorLookup[keyVectorHash] = createVector(stackPointer.stackPosition, vecLength, 2);
|
|
}
|
|
const keysStackValue = dedupKeyVectors ? keyVectorLookup[keyVectorHash] : createVector(stackPointer.stackPosition, vecLength, 2);
|
|
const valuesStackValue = createVector(stackPointer.stackPosition + 1, vecLength, 2, keysStackValue);
|
|
stack.splice(stackPointer.stackPosition, vecLength << 1);
|
|
stack.push(valuesStackValue);
|
|
}
|
|
|
|
function sort(stackPointer) {
|
|
function shouldFlip(v1, v2) {
|
|
if (v1.type !== flexbuffers.ValueType.KEY || v2.type !== flexbuffers.ValueType.KEY) {
|
|
throw `Stack values are not keys ${v1} | ${v2}. Check if you combined [addKey] with add... method calls properly.`
|
|
}
|
|
let c1, c2;
|
|
let index = 0;
|
|
do {
|
|
c1 = view.getUint8(v1.offset + index);
|
|
c2 = view.getUint8(v2.offset + index);
|
|
if (c2 < c1) return true;
|
|
if (c1 < c2) return false;
|
|
index += 1;
|
|
} while (c1 !== 0 && c2 !== 0);
|
|
return false;
|
|
}
|
|
|
|
function swap(stack, flipIndex, i) {
|
|
if (flipIndex === i) return;
|
|
const k = stack[flipIndex];
|
|
const v = stack[flipIndex + 1];
|
|
stack[flipIndex] = stack[i];
|
|
stack[flipIndex + 1] = stack[i + 1];
|
|
stack[i] = k;
|
|
stack[i + 1] = v;
|
|
}
|
|
|
|
function selectionSort() {
|
|
for (let i = stackPointer.stackPosition; i < stack.length; i += 2) {
|
|
let flipIndex = i;
|
|
for (let j = i + 2; j < stack.length; j += 2) {
|
|
if (shouldFlip(stack[flipIndex], stack[j])) {
|
|
flipIndex = j;
|
|
}
|
|
}
|
|
if (flipIndex !== i) {
|
|
swap(stack, flipIndex, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
function smaller(v1, v2) {
|
|
if (v1.type !== flexbuffers.ValueType.KEY || v2.type !== flexbuffers.ValueType.KEY) {
|
|
throw `Stack values are not keys ${v1} | ${v2}. Check if you combined [addKey] with add... method calls properly.`
|
|
}
|
|
if(v1.offset === v2.offset) {
|
|
return false;
|
|
}
|
|
let c1, c2;
|
|
let index = 0;
|
|
do {
|
|
c1 = view.getUint8(v1.offset + index);
|
|
c2 = view.getUint8(v2.offset + index);
|
|
if(c1 < c2) return true;
|
|
if(c2 < c1) return false;
|
|
index += 1;
|
|
} while (c1 !== 0 && c2 !== 0);
|
|
return false;
|
|
}
|
|
|
|
function quickSort(left, right) {
|
|
|
|
if (left < right) {
|
|
let mid = left + (((right - left) >> 2)) * 2;
|
|
let pivot = stack[mid],
|
|
left_new = left,
|
|
right_new = right;
|
|
|
|
do {
|
|
while (smaller(stack[left_new], pivot)) {
|
|
left_new += 2;
|
|
}
|
|
while (smaller(pivot, stack[right_new])) {
|
|
right_new -= 2;
|
|
}
|
|
if (left_new <= right_new) {
|
|
swap(stack, left_new, right_new);
|
|
left_new += 2;
|
|
right_new -= 2;
|
|
}
|
|
} while (left_new <= right_new);
|
|
|
|
quickSort(left, right_new);
|
|
quickSort(left_new, right);
|
|
|
|
}
|
|
}
|
|
|
|
let sorted = true;
|
|
for (let i = stackPointer.stackPosition; i < stack.length - 2; i += 2) {
|
|
if (shouldFlip(stack[i], stack[i + 2])) {
|
|
sorted = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!sorted) {
|
|
if (stack.length - stackPointer.stackPosition > 40) {
|
|
quickSort(stackPointer.stackPosition, stack.length - 2);
|
|
} else {
|
|
selectionSort();
|
|
}
|
|
}
|
|
}
|
|
|
|
function end() {
|
|
if (stackPointers.length < 1) return;
|
|
const pointer = stackPointers.pop();
|
|
if (pointer.isVector) {
|
|
endVector(pointer);
|
|
} else {
|
|
endMap(pointer);
|
|
}
|
|
}
|
|
|
|
function createVector(start, vecLength, step, keys = null) {
|
|
let bitWidth = flexbuffers.BitWidthUtil.uwidth(vecLength);
|
|
let prefixElements = 1;
|
|
if (keys !== null) {
|
|
const elementWidth = keys.elementWidth(offset, 0);
|
|
if (elementWidth > bitWidth) {
|
|
bitWidth = elementWidth;
|
|
}
|
|
prefixElements += 2;
|
|
}
|
|
let vectorType = flexbuffers.ValueType.KEY;
|
|
let typed = keys === null;
|
|
for (let i = start; i < stack.length; i += step) {
|
|
const elementWidth = stack[i].elementWidth(offset, i + prefixElements);
|
|
if (elementWidth > bitWidth) {
|
|
bitWidth = elementWidth;
|
|
}
|
|
if (i === start) {
|
|
vectorType = stack[i].type;
|
|
typed &= flexbuffers.ValueTypeUtil.isTypedVectorElement(vectorType);
|
|
} else {
|
|
if (vectorType !== stack[i].type) {
|
|
typed = false;
|
|
}
|
|
}
|
|
}
|
|
const byteWidth = align(bitWidth);
|
|
const fix = typed && flexbuffers.ValueTypeUtil.isNumber(vectorType) && vecLength >= 2 && vecLength <= 4;
|
|
if (keys !== null) {
|
|
writeStackValue(keys, byteWidth);
|
|
writeUInt(1 << keys.width, byteWidth);
|
|
}
|
|
if (!fix) {
|
|
writeUInt(vecLength, byteWidth);
|
|
}
|
|
const vecOffset = offset;
|
|
for (let i = start; i < stack.length; i += step) {
|
|
writeStackValue(stack[i], byteWidth);
|
|
}
|
|
if (!typed) {
|
|
for (let i = start; i < stack.length; i += step) {
|
|
writeUInt(stack[i].storedPackedType(), 1);
|
|
}
|
|
}
|
|
if (keys !== null) {
|
|
return offsetStackValue(vecOffset, flexbuffers.ValueType.MAP, bitWidth);
|
|
}
|
|
if (typed) {
|
|
const vType = flexbuffers.ValueTypeUtil.toTypedVector(vectorType, fix ? vecLength : 0);
|
|
return offsetStackValue(vecOffset, vType, bitWidth);
|
|
}
|
|
return offsetStackValue(vecOffset, flexbuffers.ValueType.VECTOR, bitWidth);
|
|
}
|
|
|
|
function StackValue(type, width, value, _offset) {
|
|
return {
|
|
type: type,
|
|
width: width,
|
|
value: value,
|
|
offset: _offset,
|
|
elementWidth: function (size, index) {
|
|
if (flexbuffers.ValueTypeUtil.isInline(this.type)) return this.width;
|
|
for (let i = 0; i < 4; i++) {
|
|
const width = 1 << i;
|
|
const offsetLoc = size + flexbuffers.BitWidthUtil.paddingSize(size, width) + index * width;
|
|
const offset = offsetLoc - this.offset;
|
|
const bitWidth = flexbuffers.BitWidthUtil.uwidth(offset);
|
|
if (1 << bitWidth === width) {
|
|
return bitWidth;
|
|
}
|
|
}
|
|
throw `Element is unknown. Size: ${size} at index: ${index}. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new`;
|
|
},
|
|
writeToBuffer: function (byteWidth) {
|
|
const newOffset = computeOffset(byteWidth);
|
|
if (this.type === flexbuffers.ValueType.FLOAT) {
|
|
if (this.width === flexbuffers.BitWidth.WIDTH32) {
|
|
view.setFloat32(offset, this.value, true);
|
|
} else {
|
|
view.setFloat64(offset, this.value, true);
|
|
}
|
|
} else if (this.type === flexbuffers.ValueType.INT) {
|
|
const bitWidth = flexbuffers.BitWidthUtil.fromByteWidth(byteWidth);
|
|
pushInt(value, bitWidth);
|
|
} else if (this.type === flexbuffers.ValueType.UINT) {
|
|
const bitWidth = flexbuffers.BitWidthUtil.fromByteWidth(byteWidth);
|
|
pushUInt(value, bitWidth);
|
|
} else if (this.type === flexbuffers.ValueType.NULL) {
|
|
pushInt(0, this.width);
|
|
} else if (this.type === flexbuffers.ValueType.BOOL) {
|
|
pushInt(value ? 1 : 0, this.width);
|
|
} else {
|
|
throw `Unexpected type: ${type}. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new`
|
|
}
|
|
offset = newOffset;
|
|
},
|
|
storedWidth: function (width = flexbuffers.BitWidth.WIDTH8) {
|
|
return flexbuffers.ValueTypeUtil.isInline(this.type) ? Math.max(width, this.width) : this.width;
|
|
},
|
|
storedPackedType: function (width = flexbuffers.BitWidth.WIDTH8) {
|
|
return flexbuffers.ValueTypeUtil.packedType(this.type, this.storedWidth(width));
|
|
},
|
|
isOffset: !flexbuffers.ValueTypeUtil.isInline(type)
|
|
}
|
|
}
|
|
|
|
function nullStackValue() {
|
|
return StackValue(flexbuffers.ValueType.NULL, flexbuffers.BitWidth.WIDTH8);
|
|
}
|
|
|
|
function boolStackValue(value) {
|
|
return StackValue(flexbuffers.ValueType.BOOL, flexbuffers.BitWidth.WIDTH8, value);
|
|
}
|
|
|
|
function intStackValue(value) {
|
|
return StackValue(flexbuffers.ValueType.INT, flexbuffers.BitWidthUtil.iwidth(value), value);
|
|
}
|
|
|
|
function uintStackValue(value) {
|
|
return StackValue(flexbuffers.ValueType.UINT, flexbuffers.BitWidthUtil.uwidth(value), value);
|
|
}
|
|
|
|
function floatStackValue(value) {
|
|
return StackValue(flexbuffers.ValueType.FLOAT, flexbuffers.BitWidthUtil.fwidth(value), value);
|
|
}
|
|
|
|
function offsetStackValue(offset, valueType, bitWidth) {
|
|
return StackValue(valueType, bitWidth, null, offset);
|
|
}
|
|
|
|
function finishBuffer() {
|
|
if (stack.length !== 1) {
|
|
throw `Stack has to be exactly 1, but it is ${stack.length}. You have to end all started vectors and maps before calling [finish]`;
|
|
}
|
|
const value = stack[0];
|
|
const byteWidth = align(value.elementWidth(offset, 0));
|
|
writeStackValue(value, byteWidth);
|
|
writeUInt(value.storedPackedType(), 1);
|
|
writeUInt(byteWidth, 1);
|
|
finished = true;
|
|
}
|
|
|
|
return {
|
|
add: function (value) {
|
|
integrityCheckOnValueAddition();
|
|
if (typeof value === 'undefined') {
|
|
throw "You need to provide a value";
|
|
}
|
|
if (value === null) {
|
|
stack.push(nullStackValue());
|
|
} else if (typeof value === "boolean") {
|
|
stack.push(boolStackValue(value));
|
|
} else if (typeof value === "bigint") {
|
|
stack.push(intStackValue(value));
|
|
} else if (typeof value == 'number') {
|
|
if (Number.isInteger(value)) {
|
|
stack.push(intStackValue(value));
|
|
} else {
|
|
stack.push(floatStackValue(value));
|
|
}
|
|
} else if (ArrayBuffer.isView(value)){
|
|
writeBlob(value.buffer);
|
|
} else if (typeof value === 'string' || value instanceof String) {
|
|
writeString(value);
|
|
} else if (Array.isArray(value)) {
|
|
startVector();
|
|
for (let i = 0; i < value.length; i++) {
|
|
this.add(value[i]);
|
|
}
|
|
end();
|
|
} else if (typeof value === 'object'){
|
|
const properties = Object.getOwnPropertyNames(value).sort();
|
|
startMap(true);
|
|
for (let i = 0; i < properties.length; i++) {
|
|
const key = properties[i];
|
|
this.addKey(key);
|
|
this.add(value[key]);
|
|
}
|
|
end();
|
|
} else {
|
|
throw `Unexpected value input ${value}`;
|
|
}
|
|
},
|
|
finish: function() {
|
|
if (!finished) {
|
|
finishBuffer();
|
|
}
|
|
const result = buffer.slice(0, offset);
|
|
return new Uint8Array(result);
|
|
},
|
|
isFinished: function() {
|
|
return finished;
|
|
},
|
|
addKey: function(key) {
|
|
integrityCheckOnKeyAddition();
|
|
writeKey(key);
|
|
},
|
|
addInt: function(value, indirect = false, deduplicate = false) {
|
|
integrityCheckOnValueAddition();
|
|
if (!indirect) {
|
|
stack.push(intStackValue(value));
|
|
return;
|
|
}
|
|
if (deduplicate && indirectIntLookup.hasOwnProperty(value)) {
|
|
stack.push(indirectIntLookup[value]);
|
|
return;
|
|
}
|
|
const stackValue = intStackValue(value);
|
|
const byteWidth = align(stackValue.width);
|
|
const newOffset = computeOffset(byteWidth);
|
|
const valueOffset = offset;
|
|
stackValue.writeToBuffer(byteWidth);
|
|
const stackOffset = offsetStackValue(valueOffset, flexbuffers.ValueType.INDIRECT_INT, stackValue.width);
|
|
stack.push(stackOffset);
|
|
offset = newOffset;
|
|
if (deduplicate) {
|
|
indirectIntLookup[value] = stackOffset;
|
|
}
|
|
},
|
|
addUInt: function(value, indirect = false, deduplicate = false) {
|
|
integrityCheckOnValueAddition();
|
|
if (!indirect) {
|
|
stack.push(uintStackValue(value));
|
|
return;
|
|
}
|
|
if (deduplicate && indirectUIntLookup.hasOwnProperty(value)) {
|
|
stack.push(indirectUIntLookup[value]);
|
|
return;
|
|
}
|
|
const stackValue = uintStackValue(value);
|
|
const byteWidth = align(stackValue.width);
|
|
const newOffset = computeOffset(byteWidth);
|
|
const valueOffset = offset;
|
|
stackValue.writeToBuffer(byteWidth);
|
|
const stackOffset = offsetStackValue(valueOffset, flexbuffers.ValueType.INDIRECT_UINT, stackValue.width);
|
|
stack.push(stackOffset);
|
|
offset = newOffset;
|
|
if (deduplicate) {
|
|
indirectUIntLookup[value] = stackOffset;
|
|
}
|
|
},
|
|
addFloat: function(value, indirect = false, deduplicate = false) {
|
|
integrityCheckOnValueAddition();
|
|
if (!indirect) {
|
|
stack.push(floatStackValue(value));
|
|
return;
|
|
}
|
|
if (deduplicate && indirectFloatLookup.hasOwnProperty(value)) {
|
|
stack.push(indirectFloatLookup[value]);
|
|
return;
|
|
}
|
|
const stackValue = floatStackValue(value);
|
|
const byteWidth = align(stackValue.width);
|
|
const newOffset = computeOffset(byteWidth);
|
|
const valueOffset = offset;
|
|
stackValue.writeToBuffer(byteWidth);
|
|
const stackOffset = offsetStackValue(valueOffset, flexbuffers.ValueType.INDIRECT_FLOAT, stackValue.width);
|
|
stack.push(stackOffset);
|
|
offset = newOffset;
|
|
if (deduplicate) {
|
|
indirectFloatLookup[value] = stackOffset;
|
|
}
|
|
},
|
|
startVector: function() {
|
|
startVector();
|
|
},
|
|
startMap: function() {
|
|
startMap();
|
|
},
|
|
end: function() {
|
|
end();
|
|
}
|
|
};
|
|
};
|
|
|
|
flexbuffers.encode = (object, size = 2048, deduplicateStrings = true, deduplicateKeys = true, deduplicateKeyVectors = true) => {
|
|
const builder = flexbuffers.builder(size > 0 ? size : 2048, deduplicateStrings, deduplicateKeys, deduplicateKeyVectors);
|
|
builder.add(object);
|
|
return builder.finish();
|
|
};
|
|
|
|
|
|
|
|
function fromUTF8Array(data) {
|
|
const decoder = new TextDecoder();
|
|
return decoder.decode(data);
|
|
}
|
|
|
|
function toUTF8Array(str) {
|
|
const encoder = new TextEncoder();
|
|
return encoder.encode(str);
|
|
}
|
|
// Exports for Node.js and RequireJS
|
|
this.flexbuffers = flexbuffers;
|