avocado-old/packages/state/packer.js

344 lines
8.8 KiB
JavaScript
Raw Normal View History

2019-04-16 21:39:50 -05:00
import * as I from 'immutable';
import msgpack from 'msgpack-lite';
2019-04-12 17:59:28 -05:00
const PACKER_STEP_OP_ADD = 0;
2019-04-08 07:31:02 -05:00
const PACKER_STEP_OP_REPLACE = 1;
2019-04-12 17:59:28 -05:00
const PACKER_STEP_OP_REMOVE = 2;
2019-04-08 07:31:02 -05:00
2019-04-19 14:04:07 -05:00
const PACKER_STEP_VALUE_BOOLEAN = 0;
const PACKER_STEP_VALUE_BYTE = 1;
const PACKER_STEP_VALUE_UBYTE = 2;
const PACKER_STEP_VALUE_SHORT = 3;
const PACKER_STEP_VALUE_USHORT = 4;
const PACKER_STEP_VALUE_INT = 5;
const PACKER_STEP_VALUE_UINT = 6;
const PACKER_STEP_VALUE_FLOAT = 7;
const PACKER_STEP_VALUE_STRING = 8;
const PACKER_STEP_VALUE_JSON = 9;
2019-04-08 07:31:02 -05:00
2019-04-05 22:40:04 -05:00
export class Packer {
constructor() {
this._keyCaret = 0;
this._keys = {};
}
2019-04-08 07:31:02 -05:00
computeMessageLength(preparedSteps) {
// num of steps + packed.
return 4 + preparedSteps.reduce((length, step) => {
// op + path + value type + value.
2019-04-16 21:39:50 -05:00
let valueLength;
if (PACKER_STEP_OP_REMOVE === step.get('op')) {
valueLength = 0;
}
else {
valueLength = this.constructor.packedValueLength(
step.get('value')
);
}
return length + 1 + 4 + 1 + valueLength;
2019-04-08 07:31:02 -05:00
}, 0);
}
computeNewKeys(steps) {
2019-04-11 12:52:51 -05:00
const newKeys = [[], []];
2019-04-16 21:39:50 -05:00
steps.forEach((step) => {
const path = step.get('path');
2019-04-05 22:40:04 -05:00
if (!this._keys[path]) {
2019-04-11 12:52:51 -05:00
this._keys[path] = this._keyCaret++;
newKeys[0].push(path);
newKeys[1].push(this._keys[path]);
2019-04-05 22:40:04 -05:00
}
});
2019-04-08 07:31:02 -05:00
return newKeys;
}
pack(steps) {
2019-04-11 12:52:51 -05:00
return this.packSteps(steps);
2019-04-05 22:40:04 -05:00
}
2019-04-08 07:31:02 -05:00
// Binary packed steps.
packSteps(steps) {
const preparedSteps = this.prepareSteps(steps);
const messageLength = this.computeMessageLength(preparedSteps);
2019-04-11 15:26:13 -05:00
const packedSteps = Buffer.allocUnsafe(messageLength);
2019-04-08 07:31:02 -05:00
let caret = 0;
// Number of steps.
2019-04-16 21:39:50 -05:00
packedSteps.writeUInt32LE(preparedSteps.size, caret)
2019-04-08 07:31:02 -05:00
caret += 4;
2019-04-16 21:39:50 -05:00
preparedSteps.forEach((step) => {
2019-04-08 07:31:02 -05:00
// Op.
2019-04-16 21:39:50 -05:00
const op = step.get('op');
2019-04-11 15:26:13 -05:00
packedSteps.writeUInt8(op, caret)
2019-04-08 07:31:02 -05:00
caret += 1;
// Path.
2019-04-16 21:39:50 -05:00
const path = step.get('path');
2019-04-11 15:26:13 -05:00
packedSteps.writeUInt32LE(path, caret)
2019-04-08 07:31:02 -05:00
caret += 4;
2019-04-09 09:22:26 -05:00
if (PACKER_STEP_OP_REMOVE === op) {
2019-04-08 07:37:39 -05:00
return;
}
2019-04-08 07:31:02 -05:00
// Value.
2019-04-16 21:39:50 -05:00
const value = step.get('value');
2019-04-11 15:26:13 -05:00
packedSteps.writeUInt8(value.type, caret)
2019-04-08 07:31:02 -05:00
caret += 1;
switch (value.type) {
2019-04-19 14:04:07 -05:00
case PACKER_STEP_VALUE_BOOLEAN:
packedSteps.writeInt8(value.data ? 1 : 0, caret)
caret += 1;
break;
2019-04-08 07:31:02 -05:00
case PACKER_STEP_VALUE_BYTE:
2019-04-11 15:26:13 -05:00
packedSteps.writeInt8(value.data, caret)
2019-04-08 07:31:02 -05:00
caret += 1;
break;
case PACKER_STEP_VALUE_UBYTE:
2019-04-11 15:26:13 -05:00
packedSteps.writeUInt8(value.data, caret)
2019-04-08 07:31:02 -05:00
caret += 1;
break;
case PACKER_STEP_VALUE_SHORT:
2019-04-11 15:26:13 -05:00
packedSteps.writeInt16LE(value.data, caret)
2019-04-08 07:31:02 -05:00
caret += 2;
break;
case PACKER_STEP_VALUE_USHORT:
2019-04-11 15:26:13 -05:00
packedSteps.writeUInt16LE(value.data, caret)
2019-04-08 07:31:02 -05:00
caret += 2;
break;
case PACKER_STEP_VALUE_INT:
2019-04-11 15:26:13 -05:00
packedSteps.writeInt32LE(value.data, caret)
2019-04-08 07:31:02 -05:00
caret += 4;
break;
case PACKER_STEP_VALUE_UINT:
2019-04-11 15:26:13 -05:00
packedSteps.writeUInt32LE(value.data, caret)
2019-04-08 07:31:02 -05:00
caret += 4;
break;
case PACKER_STEP_VALUE_FLOAT:
2019-04-11 15:26:13 -05:00
packedSteps.writeFloatLE(value.data, caret);
2019-04-08 07:31:02 -05:00
caret += 4;
break;
case PACKER_STEP_VALUE_STRING:
case PACKER_STEP_VALUE_JSON:
const bufferLength = value.data.length;
packedSteps.writeUInt32LE(bufferLength, caret)
2019-04-08 07:31:02 -05:00
caret += 4;
for (let i = 0; i < bufferLength; ++i) {
packedSteps.writeUInt8(value.data.readUInt8(i), caret)
2019-04-08 07:31:02 -05:00
caret += 1;
}
break;
2019-04-08 07:31:02 -05:00
}
});
return packedSteps;
}
static prepareOpType(op) {
switch (op) {
case 'add':
return PACKER_STEP_OP_ADD;
case 'replace':
return PACKER_STEP_OP_REPLACE;
case 'remove':
return PACKER_STEP_OP_REMOVE;
}
}
prepareSteps(steps) {
2019-04-16 21:39:50 -05:00
return steps.map((step) => {
const op = step.get('op');
const rawStep = {
2019-04-08 07:31:02 -05:00
op: this.constructor.prepareOpType(op),
2019-04-16 21:39:50 -05:00
path: this._keys[step.get('path')],
2019-04-08 07:31:02 -05:00
};
2019-04-16 21:39:50 -05:00
if ('remove' !== op) {
rawStep.value = this.constructor.prepareValue(step.get('value'));
}
return I.Map(rawStep);
2019-04-08 07:31:02 -05:00
});
}
static prepareValue(value) {
2019-04-19 14:04:07 -05:00
if ('boolean' === typeof value) {
return {
type: PACKER_STEP_VALUE_BOOLEAN,
data: value,
};
}
2019-04-08 07:31:02 -05:00
if (Number.isInteger(value)) {
if (value < 0) {
if (value >= -128 && value < 128) {
return {
type: PACKER_STEP_VALUE_BYTE,
data: value,
2019-04-19 14:04:07 -05:00
};
2019-04-08 07:31:02 -05:00
}
if (value >= -32768 && value < 32768) {
return {
type: PACKER_STEP_VALUE_SHORT,
data: value,
2019-04-19 14:04:07 -05:00
};
2019-04-08 07:31:02 -05:00
}
return {
type: PACKER_STEP_VALUE_INT,
data: value,
2019-04-19 14:04:07 -05:00
};
2019-04-08 07:31:02 -05:00
}
if (value < 256) {
return {
type: PACKER_STEP_VALUE_UBYTE,
data: value,
};
}
if (value < 65536) {
return {
type: PACKER_STEP_VALUE_USHORT,
data: value,
};
}
return {
type: PACKER_STEP_VALUE_UINT,
data: value,
};
}
if (Number.isFinite(value)) {
return {
type: PACKER_STEP_VALUE_FLOAT,
data: value,
};
}
if ('string' === typeof value) {
return {
type: PACKER_STEP_VALUE_STRING,
data: Buffer.from(value),
2019-04-08 07:31:02 -05:00
};
}
2019-04-19 14:04:07 -05:00
if (!value.toJS) {
console.log(value);
}
2019-04-08 07:31:02 -05:00
return {
type: PACKER_STEP_VALUE_JSON,
2019-04-16 21:39:50 -05:00
data: msgpack.encode(value.toJS()),
2019-04-08 07:31:02 -05:00
};
}
static packedValueLength({type, data}) {
switch (type) {
2019-04-19 14:04:07 -05:00
case PACKER_STEP_VALUE_BOOLEAN:
2019-04-08 07:31:02 -05:00
case PACKER_STEP_VALUE_BYTE:
case PACKER_STEP_VALUE_UBYTE:
return 1;
case PACKER_STEP_VALUE_SHORT:
case PACKER_STEP_VALUE_USHORT:
return 2;
case PACKER_STEP_VALUE_INT:
case PACKER_STEP_VALUE_UINT:
return 4;
case PACKER_STEP_VALUE_FLOAT:
return 4;
case PACKER_STEP_VALUE_STRING:
case PACKER_STEP_VALUE_JSON:
return 4 + data.length;
}
}
2019-04-05 22:40:04 -05:00
}
export class Unpacker {
constructor() {
this._keys = {};
}
2019-04-11 12:52:51 -05:00
registerKeys(keys) {
for (let i = 0; i < keys[0].length; ++i) {
this._keys[keys[1][i]] = keys[0][i];
2019-04-05 22:40:04 -05:00
}
2019-04-11 12:52:51 -05:00
}
unpack(packedSteps) {
2019-04-08 07:31:02 -05:00
let caret = 0;
2019-04-11 15:26:13 -05:00
const numberOfSteps = packedSteps.readUInt32LE(caret);
2019-04-08 07:31:02 -05:00
caret += 4;
const steps = new Array(numberOfSteps);
for (let i = 0; i < numberOfSteps; ++i) {
2019-04-11 15:26:13 -05:00
const packedOp = packedSteps.readUInt8(caret);
2019-04-08 07:31:02 -05:00
caret += 1;
2019-04-11 15:26:13 -05:00
const packedPath = packedSteps.readUInt32LE(caret);
2019-04-08 07:31:02 -05:00
caret += 4;
2019-04-08 07:37:39 -05:00
steps[i] = {
op: this.constructor.unpackOp(packedOp),
path: this._keys[packedPath],
};
if (PACKER_STEP_OP_REMOVE === packedOp) {
continue;
}
2019-04-11 15:26:13 -05:00
const valueType = packedSteps.readUInt8(caret);
2019-04-08 07:31:02 -05:00
caret += 1;
let value;
switch (valueType) {
2019-04-19 14:04:07 -05:00
case PACKER_STEP_VALUE_BOOLEAN:
value = !!packedSteps.readInt8(caret);
caret += 1;
break;
2019-04-08 07:31:02 -05:00
case PACKER_STEP_VALUE_BYTE:
2019-04-11 15:26:13 -05:00
value = packedSteps.readInt8(caret);
2019-04-08 07:31:02 -05:00
caret += 1;
break;
case PACKER_STEP_VALUE_UBYTE:
2019-04-11 15:26:13 -05:00
value = packedSteps.readUInt8(caret);
2019-04-08 07:31:02 -05:00
caret += 1;
break;
case PACKER_STEP_VALUE_SHORT:
2019-04-11 15:26:13 -05:00
value = packedSteps.readInt16LE(caret);
2019-04-08 07:31:02 -05:00
caret += 2;
break;
case PACKER_STEP_VALUE_USHORT:
2019-04-11 15:26:13 -05:00
value = packedSteps.readUInt16LE(caret);
2019-04-08 07:31:02 -05:00
caret += 2;
break;
case PACKER_STEP_VALUE_INT:
2019-04-11 15:26:13 -05:00
value = packedSteps.readInt32LE(caret);
2019-04-08 07:31:02 -05:00
caret += 4;
break;
case PACKER_STEP_VALUE_UINT:
2019-04-11 15:26:13 -05:00
value = packedSteps.readUInt32LE(caret);
2019-04-08 07:31:02 -05:00
caret += 4;
break;
case PACKER_STEP_VALUE_FLOAT:
2019-04-11 15:26:13 -05:00
value = packedSteps.readFloatLE(caret);
2019-04-08 07:31:02 -05:00
caret += 4;
break;
case PACKER_STEP_VALUE_STRING:
case PACKER_STEP_VALUE_JSON:
const bufferLength = packedSteps.readUInt32LE(caret);
2019-04-08 07:31:02 -05:00
caret += 4;
value = Buffer.allocUnsafe(bufferLength);
for (let j = 0; j < bufferLength; ++j) {
value.writeUInt8(packedSteps.readUInt8(caret), j);
2019-04-08 07:31:02 -05:00
caret += 1;
}
break;
}
if (PACKER_STEP_VALUE_STRING === valueType) {
value = value.toString();
}
2019-04-08 07:31:02 -05:00
if (PACKER_STEP_VALUE_JSON === valueType) {
value = msgpack.decode(value);
2019-04-08 07:31:02 -05:00
}
2019-04-08 07:37:39 -05:00
steps[i].value = value;
2019-04-08 07:31:02 -05:00
}
return steps;
}
static unpackOp(packedOp) {
switch (packedOp) {
case PACKER_STEP_OP_ADD:
return 'add';
case PACKER_STEP_OP_REPLACE:
return 'replace';
case PACKER_STEP_OP_REMOVE:
return 'remove';
}
2019-04-05 22:40:04 -05:00
}
}