@geckos.io/typed-array-buffer-schema
Version:
A Schema based Object to Buffer converter
396 lines • 18.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Serialize = void 0;
const lib_1 = require("./lib");
const set_1 = __importDefault(require("lodash/set"));
const deep_sort_object_1 = require("./deep-sort-object");
class Serialize {
constructor(schema, bufferSize) {
this.schema = schema;
this.bufferSize = bufferSize;
this._buffer = new ArrayBuffer(0);
this._dataView = new DataView(this._buffer);
this._bytes = 0;
}
refresh() {
this._buffer = new ArrayBuffer(this.bufferSize * 1024);
this._dataView = new DataView(this._buffer);
this._bytes = 0;
}
cropString(str, length) {
return str.padEnd(length, ' ').slice(0, length);
}
isSpecialType(prop) {
let propKeys = Object.keys(prop).filter(k => k != 'type' && k != 'digits' && k != 'length');
return !propKeys.length;
}
boolArrayToInt(array) {
// start with 1 to avoid errors such as 010 -> 10
// now it will be 1010 which will not simplify
let string = '1';
for (var i = 0; i < array.length; i++) {
string += +!!array[i];
}
return parseInt(string, 2);
}
intToBoolArray(int) {
// convert string to array, map the numbers to bools,
// and remove the initial 1
return [...(int >>> 0).toString(2)].map(e => (e == '0' ? false : true)).slice(1);
}
flatten(schema, data) {
let flat = [];
// https://stackoverflow.com/a/15589677/12656855
const flatten = (schema, data) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
// add the schema id to flat[] (its a String8 with 5 characters, the first char is #)
if (schema === null || schema === void 0 ? void 0 : schema._id)
flat.push({ d: schema._id, t: 'String8' });
else if ((_a = schema === null || schema === void 0 ? void 0 : schema[0]) === null || _a === void 0 ? void 0 : _a._id)
flat.push({ d: schema[0]._id, t: 'String8' });
// if it is a schema
if (schema === null || schema === void 0 ? void 0 : schema._struct)
schema = schema._struct;
// if it is a schema[]
else if ((_b = schema === null || schema === void 0 ? void 0 : schema[0]) === null || _b === void 0 ? void 0 : _b._struct)
schema = schema[0]._struct;
// console.log('-------')
// console.log('data', typeof data, data)
let property;
for (property in data) {
if (data.hasOwnProperty(property)) {
if (typeof data[property] === 'object') {
// if data is array, but schemas is flat, use index 0 on the next iteration
if (Array.isArray(data)) {
flatten(schema, data[parseInt(property)]);
}
else if (((_c = schema[property]) === null || _c === void 0 ? void 0 : _c._type) === 'BitArray8' || ((_d = schema[property]) === null || _d === void 0 ? void 0 : _d._type) === 'BitArray16') {
flat.push({
d: this.boolArrayToInt(data[property]),
t: schema[property]._type
});
}
else
flatten(schema[property], data[property]);
}
//---
else {
// handle specialTypes e.g.: "x: { type: int16, digits: 2 }"
if (((_f = (_e = schema[property]) === null || _e === void 0 ? void 0 : _e.type) === null || _f === void 0 ? void 0 : _f._type) && this.isSpecialType(schema[property])) {
if ((_g = schema[property]) === null || _g === void 0 ? void 0 : _g.digits) {
data[property] *= Math.pow(10, schema[property].digits);
data[property] = parseInt(data[property].toFixed(0));
}
if ((_h = schema[property]) === null || _h === void 0 ? void 0 : _h.length) {
const length = (_j = schema[property]) === null || _j === void 0 ? void 0 : _j.length;
data[property] = this.cropString(data[property], length);
}
flat.push({ d: data[property], t: schema[property].type._type });
}
else {
if ((_k = schema[property]) === null || _k === void 0 ? void 0 : _k._type) {
// crop strings to default length of 12 characters if nothing else is specified
if (((_l = schema[property]) === null || _l === void 0 ? void 0 : _l._type) === 'String8' || ((_m = schema[property]) === null || _m === void 0 ? void 0 : _m._type) === 'String16') {
data[property] = this.cropString(data[property], 12);
}
flat.push({ d: data[property], t: schema[property]._type });
}
}
}
}
else {
}
}
};
flatten(schema, data);
return flat;
}
toBuffer(state) {
let worldState = (0, deep_sort_object_1.deepSortObject)(state);
// deep clone the worldState
const data = JSON.parse(JSON.stringify(worldState));
this.refresh();
const flat = this.flatten(this.schema, data);
// to buffer
flat.forEach((f, i) => {
if (f.t === 'String8') {
for (let j = 0; j < f.d.length; j++) {
this._dataView.setUint8(this._bytes, f.d[j].charCodeAt(0));
this._bytes++;
}
}
else if (f.t === 'String16') {
for (let j = 0; j < f.d.length; j++) {
this._dataView.setUint16(this._bytes, f.d[j].charCodeAt(0));
this._bytes += 2;
}
}
else if (f.t === 'Int8Array') {
this._dataView.setInt8(this._bytes, f.d);
this._bytes++;
}
else if (f.t === 'Uint8Array') {
this._dataView.setUint8(this._bytes, f.d);
this._bytes++;
}
else if (f.t === 'Int16Array') {
this._dataView.setInt16(this._bytes, f.d);
this._bytes += 2;
}
else if (f.t === 'Uint16Array') {
this._dataView.setUint16(this._bytes, f.d);
this._bytes += 2;
}
else if (f.t === 'Int32Array') {
this._dataView.setInt32(this._bytes, f.d);
this._bytes += 4;
}
else if (f.t === 'Uint32Array') {
this._dataView.setUint32(this._bytes, f.d);
this._bytes += 4;
}
else if (f.t === 'BigInt64Array') {
this._dataView.setBigInt64(this._bytes, BigInt(f.d));
this._bytes += 8;
}
else if (f.t === 'BigUint64Array') {
this._dataView.setBigUint64(this._bytes, BigInt(f.d));
this._bytes += 8;
}
else if (f.t === 'Float32Array') {
this._dataView.setFloat32(this._bytes, f.d);
this._bytes += 4;
}
else if (f.t === 'Float64Array') {
this._dataView.setFloat64(this._bytes, f.d);
this._bytes += 8;
}
else if (f.t === 'BitArray8') {
this._dataView.setUint8(this._bytes, f.d);
this._bytes++;
}
else if (f.t === 'BitArray16') {
this._dataView.setUint16(this._bytes, f.d);
this._bytes += 2;
}
else {
console.log('ERROR: Something unexpected happened!');
}
});
const newBuffer = new ArrayBuffer(this._bytes);
const view = new DataView(newBuffer);
// copy all data to a new (resized) ArrayBuffer
for (let i = 0; i < this._bytes; i++) {
view.setUint8(i, this._dataView.getUint8(i));
}
return newBuffer;
}
fromBuffer(buffer) {
// 35 is #
// check where, in the buffer, the schemas are
let index = 0;
let indexes = [];
const view = new DataView(buffer);
const int8 = Array.from(new Int8Array(buffer));
while (index > -1) {
index = int8.indexOf(35, index);
if (index !== -1) {
indexes.push(index);
index++;
}
}
// get the schema ids
let schemaIds = [];
indexes.forEach(index => {
let id = '';
for (let i = 0; i < 5; i++) {
let char = String.fromCharCode(int8[index + i]);
id += char;
}
schemaIds.push(id);
});
// assemble all info about the schemas we need
let schemas = [];
schemaIds.forEach((id, i) => {
// check if the schemaId exists
// (this can be, for example, if charCode 35 is not really a #)
const schemaId = lib_1.Lib._schemas.get(id);
if (schemaId)
schemas.push({ id, schema: lib_1.Lib._schemas.get(id), startsAt: indexes[i] + 5 });
});
// schemas[] contains now all the schemas we need to fromBuffer the bufferArray
// lets begin the serialization
let data = {}; // holds all the data we want to give back
let bytes = 0; // the current bytes of arrayBuffer iteration
let dataPerSchema = {};
const deserializeSchema = (struct) => {
var _a, _b;
let data = {};
if (typeof struct === 'object') {
for (let property in struct) {
if (struct.hasOwnProperty(property)) {
const prop = struct[property];
// handle specialTypes e.g.: "x: { type: int16, digits: 2 }"
let specialTypes;
if (((_a = prop === null || prop === void 0 ? void 0 : prop.type) === null || _a === void 0 ? void 0 : _a._type) || ((_b = prop === null || prop === void 0 ? void 0 : prop.type) === null || _b === void 0 ? void 0 : _b._bytes) || this.isSpecialType(prop)) {
specialTypes = prop;
prop._type = prop.type._type;
prop._bytes = prop.type._bytes;
}
if (prop && prop['_type'] && prop['_bytes']) {
const _type = prop['_type'];
const _bytes = prop['_bytes'];
let value;
if (_type === 'String8') {
value = '';
const length = prop.length || 12;
for (let i = 0; i < length; i++) {
const char = String.fromCharCode(view.getUint8(bytes));
value += char;
bytes++;
}
}
if (_type === 'String16') {
value = '';
const length = prop.length || 12;
for (let i = 0; i < length; i++) {
const char = String.fromCharCode(view.getUint16(bytes));
value += char;
bytes += 2;
}
}
if (_type === 'Int8Array') {
value = view.getInt8(bytes);
bytes += _bytes;
}
if (_type === 'Uint8Array') {
value = view.getUint8(bytes);
bytes += _bytes;
}
if (_type === 'Int16Array') {
value = view.getInt16(bytes);
bytes += _bytes;
}
if (_type === 'Uint16Array') {
value = view.getUint16(bytes);
bytes += _bytes;
}
if (_type === 'Int32Array') {
value = view.getInt32(bytes);
bytes += _bytes;
}
if (_type === 'Uint32Array') {
value = view.getUint32(bytes);
bytes += _bytes;
}
if (_type === 'BigInt64Array') {
value = parseInt(view.getBigInt64(bytes).toString());
bytes += _bytes;
}
if (_type === 'BigUint64Array') {
value = parseInt(view.getBigUint64(bytes).toString());
bytes += _bytes;
}
if (_type === 'Float32Array') {
value = view.getFloat32(bytes);
bytes += _bytes;
}
if (_type === 'Float64Array') {
value = view.getFloat64(bytes);
bytes += _bytes;
}
if (_type === 'BitArray8') {
value = this.intToBoolArray(view.getUint8(bytes));
bytes += _bytes;
}
if (_type === 'BitArray16') {
value = this.intToBoolArray(view.getUint16(bytes));
bytes += _bytes;
}
// apply special types options
if (typeof value === 'number' && (specialTypes === null || specialTypes === void 0 ? void 0 : specialTypes.digits)) {
value *= Math.pow(10, -specialTypes.digits);
value = parseFloat(value.toFixed(specialTypes.digits));
}
data = { ...data, [property]: value };
}
}
}
}
return data;
};
schemas.forEach((s, i) => {
var _a, _b, _c;
let struct = (_a = s.schema) === null || _a === void 0 ? void 0 : _a.struct;
let start = s.startsAt;
let end = buffer.byteLength;
let id = ((_b = s.schema) === null || _b === void 0 ? void 0 : _b.id) || 'XX';
if (id === 'XX')
console.error('ERROR: Something went horribly wrong!');
try {
end = schemas[i + 1].startsAt - 5;
}
catch { }
// TOOD(yandeu) bytes is not accurate since it includes child schemas
const length = ((_c = s.schema) === null || _c === void 0 ? void 0 : _c.bytes) || 1;
// determine how many iteration we have to make in this schema
// the players array maybe contains 5 player, so we have to make 5 iterations
const iterations = (end - start) / length;
for (let i = 0; i < iterations; i++) {
bytes = start + i * length;
// gets the data from this schema
let schemaData = deserializeSchema(struct);
if (iterations <= 1)
dataPerSchema[id] = { ...schemaData };
else {
if (typeof dataPerSchema[id] === 'undefined')
dataPerSchema[id] = [];
dataPerSchema[id].push(schemaData);
}
}
});
// add dataPerScheme to data
data = {};
const populateData = (obj, key, value, path = '', isArray = false) => {
if (obj && obj._id && obj._id === key) {
let p = path.replace(/_struct\./, '').replace(/\.$/, '');
// if it is a schema[], but only has one set, we manually have to make sure it transforms to an array
if (isArray && !Array.isArray(value))
value = [value];
// '' is the top level
if (p === '')
data = { ...data, ...value };
else
(0, set_1.default)(data, p, value);
}
else {
for (const props in obj) {
if (obj.hasOwnProperty(props)) {
if (typeof obj[props] === 'object') {
let p = Array.isArray(obj) ? '' : `${props}.`;
populateData(obj[props], key, value, path + p, Array.isArray(obj));
}
//obj
}
}
}
};
// to it backwards (don't remember why this is needed, but it works without it)
// for (let i = Object.keys(dataPerSchema).length - 1; i >= 0; i--) {
// const key = Object.keys(dataPerSchema)[i]
// const value = dataPerSchema[key]
// populateData(this.schema, key, value, '')
// }
for (let i = 0; i < Object.keys(dataPerSchema).length; i++) {
const key = Object.keys(dataPerSchema)[i];
const value = dataPerSchema[key];
populateData(this.schema, key, value, '');
}
return data;
}
}
exports.Serialize = Serialize;
//# sourceMappingURL=serialize.js.map