mirror of
https://github.com/titanscouting/tra-analysis.git
synced 2025-01-17 02:15:56 +00:00
460 lines
12 KiB
JavaScript
460 lines
12 KiB
JavaScript
"use strict";
|
|
module.exports = Writer;
|
|
|
|
var util = require("./util/minimal");
|
|
|
|
var BufferWriter; // cyclic
|
|
|
|
var LongBits = util.LongBits,
|
|
base64 = util.base64,
|
|
utf8 = util.utf8;
|
|
|
|
/**
|
|
* Constructs a new writer operation instance.
|
|
* @classdesc Scheduled writer operation.
|
|
* @constructor
|
|
* @param {function(*, Uint8Array, number)} fn Function to call
|
|
* @param {number} len Value byte length
|
|
* @param {*} val Value to write
|
|
* @ignore
|
|
*/
|
|
function Op(fn, len, val) {
|
|
|
|
/**
|
|
* Function to call.
|
|
* @type {function(Uint8Array, number, *)}
|
|
*/
|
|
this.fn = fn;
|
|
|
|
/**
|
|
* Value byte length.
|
|
* @type {number}
|
|
*/
|
|
this.len = len;
|
|
|
|
/**
|
|
* Next operation.
|
|
* @type {Writer.Op|undefined}
|
|
*/
|
|
this.next = undefined;
|
|
|
|
/**
|
|
* Value to write.
|
|
* @type {*}
|
|
*/
|
|
this.val = val; // type varies
|
|
}
|
|
|
|
/* istanbul ignore next */
|
|
function noop() {} // eslint-disable-line no-empty-function
|
|
|
|
/**
|
|
* Constructs a new writer state instance.
|
|
* @classdesc Copied writer state.
|
|
* @memberof Writer
|
|
* @constructor
|
|
* @param {Writer} writer Writer to copy state from
|
|
* @ignore
|
|
*/
|
|
function State(writer) {
|
|
|
|
/**
|
|
* Current head.
|
|
* @type {Writer.Op}
|
|
*/
|
|
this.head = writer.head;
|
|
|
|
/**
|
|
* Current tail.
|
|
* @type {Writer.Op}
|
|
*/
|
|
this.tail = writer.tail;
|
|
|
|
/**
|
|
* Current buffer length.
|
|
* @type {number}
|
|
*/
|
|
this.len = writer.len;
|
|
|
|
/**
|
|
* Next state.
|
|
* @type {State|null}
|
|
*/
|
|
this.next = writer.states;
|
|
}
|
|
|
|
/**
|
|
* Constructs a new writer instance.
|
|
* @classdesc Wire format writer using `Uint8Array` if available, otherwise `Array`.
|
|
* @constructor
|
|
*/
|
|
function Writer() {
|
|
|
|
/**
|
|
* Current length.
|
|
* @type {number}
|
|
*/
|
|
this.len = 0;
|
|
|
|
/**
|
|
* Operations head.
|
|
* @type {Object}
|
|
*/
|
|
this.head = new Op(noop, 0, 0);
|
|
|
|
/**
|
|
* Operations tail
|
|
* @type {Object}
|
|
*/
|
|
this.tail = this.head;
|
|
|
|
/**
|
|
* Linked forked states.
|
|
* @type {Object|null}
|
|
*/
|
|
this.states = null;
|
|
|
|
// When a value is written, the writer calculates its byte length and puts it into a linked
|
|
// list of operations to perform when finish() is called. This both allows us to allocate
|
|
// buffers of the exact required size and reduces the amount of work we have to do compared
|
|
// to first calculating over objects and then encoding over objects. In our case, the encoding
|
|
// part is just a linked list walk calling operations with already prepared values.
|
|
}
|
|
|
|
/**
|
|
* Creates a new writer.
|
|
* @function
|
|
* @returns {BufferWriter|Writer} A {@link BufferWriter} when Buffers are supported, otherwise a {@link Writer}
|
|
*/
|
|
Writer.create = util.Buffer
|
|
? function create_buffer_setup() {
|
|
return (Writer.create = function create_buffer() {
|
|
return new BufferWriter();
|
|
})();
|
|
}
|
|
/* istanbul ignore next */
|
|
: function create_array() {
|
|
return new Writer();
|
|
};
|
|
|
|
/**
|
|
* Allocates a buffer of the specified size.
|
|
* @param {number} size Buffer size
|
|
* @returns {Uint8Array} Buffer
|
|
*/
|
|
Writer.alloc = function alloc(size) {
|
|
return new util.Array(size);
|
|
};
|
|
|
|
// Use Uint8Array buffer pool in the browser, just like node does with buffers
|
|
/* istanbul ignore else */
|
|
if (util.Array !== Array)
|
|
Writer.alloc = util.pool(Writer.alloc, util.Array.prototype.subarray);
|
|
|
|
/**
|
|
* Pushes a new operation to the queue.
|
|
* @param {function(Uint8Array, number, *)} fn Function to call
|
|
* @param {number} len Value byte length
|
|
* @param {number} val Value to write
|
|
* @returns {Writer} `this`
|
|
* @private
|
|
*/
|
|
Writer.prototype._push = function push(fn, len, val) {
|
|
this.tail = this.tail.next = new Op(fn, len, val);
|
|
this.len += len;
|
|
return this;
|
|
};
|
|
|
|
function writeByte(val, buf, pos) {
|
|
buf[pos] = val & 255;
|
|
}
|
|
|
|
function writeVarint32(val, buf, pos) {
|
|
while (val > 127) {
|
|
buf[pos++] = val & 127 | 128;
|
|
val >>>= 7;
|
|
}
|
|
buf[pos] = val;
|
|
}
|
|
|
|
/**
|
|
* Constructs a new varint writer operation instance.
|
|
* @classdesc Scheduled varint writer operation.
|
|
* @extends Op
|
|
* @constructor
|
|
* @param {number} len Value byte length
|
|
* @param {number} val Value to write
|
|
* @ignore
|
|
*/
|
|
function VarintOp(len, val) {
|
|
this.len = len;
|
|
this.next = undefined;
|
|
this.val = val;
|
|
}
|
|
|
|
VarintOp.prototype = Object.create(Op.prototype);
|
|
VarintOp.prototype.fn = writeVarint32;
|
|
|
|
/**
|
|
* Writes an unsigned 32 bit value as a varint.
|
|
* @param {number} value Value to write
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.uint32 = function write_uint32(value) {
|
|
// here, the call to this.push has been inlined and a varint specific Op subclass is used.
|
|
// uint32 is by far the most frequently used operation and benefits significantly from this.
|
|
this.len += (this.tail = this.tail.next = new VarintOp(
|
|
(value = value >>> 0)
|
|
< 128 ? 1
|
|
: value < 16384 ? 2
|
|
: value < 2097152 ? 3
|
|
: value < 268435456 ? 4
|
|
: 5,
|
|
value)).len;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Writes a signed 32 bit value as a varint.
|
|
* @function
|
|
* @param {number} value Value to write
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.int32 = function write_int32(value) {
|
|
return value < 0
|
|
? this._push(writeVarint64, 10, LongBits.fromNumber(value)) // 10 bytes per spec
|
|
: this.uint32(value);
|
|
};
|
|
|
|
/**
|
|
* Writes a 32 bit value as a varint, zig-zag encoded.
|
|
* @param {number} value Value to write
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.sint32 = function write_sint32(value) {
|
|
return this.uint32((value << 1 ^ value >> 31) >>> 0);
|
|
};
|
|
|
|
function writeVarint64(val, buf, pos) {
|
|
while (val.hi) {
|
|
buf[pos++] = val.lo & 127 | 128;
|
|
val.lo = (val.lo >>> 7 | val.hi << 25) >>> 0;
|
|
val.hi >>>= 7;
|
|
}
|
|
while (val.lo > 127) {
|
|
buf[pos++] = val.lo & 127 | 128;
|
|
val.lo = val.lo >>> 7;
|
|
}
|
|
buf[pos++] = val.lo;
|
|
}
|
|
|
|
/**
|
|
* Writes an unsigned 64 bit value as a varint.
|
|
* @param {Long|number|string} value Value to write
|
|
* @returns {Writer} `this`
|
|
* @throws {TypeError} If `value` is a string and no long library is present.
|
|
*/
|
|
Writer.prototype.uint64 = function write_uint64(value) {
|
|
var bits = LongBits.from(value);
|
|
return this._push(writeVarint64, bits.length(), bits);
|
|
};
|
|
|
|
/**
|
|
* Writes a signed 64 bit value as a varint.
|
|
* @function
|
|
* @param {Long|number|string} value Value to write
|
|
* @returns {Writer} `this`
|
|
* @throws {TypeError} If `value` is a string and no long library is present.
|
|
*/
|
|
Writer.prototype.int64 = Writer.prototype.uint64;
|
|
|
|
/**
|
|
* Writes a signed 64 bit value as a varint, zig-zag encoded.
|
|
* @param {Long|number|string} value Value to write
|
|
* @returns {Writer} `this`
|
|
* @throws {TypeError} If `value` is a string and no long library is present.
|
|
*/
|
|
Writer.prototype.sint64 = function write_sint64(value) {
|
|
var bits = LongBits.from(value).zzEncode();
|
|
return this._push(writeVarint64, bits.length(), bits);
|
|
};
|
|
|
|
/**
|
|
* Writes a boolish value as a varint.
|
|
* @param {boolean} value Value to write
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.bool = function write_bool(value) {
|
|
return this._push(writeByte, 1, value ? 1 : 0);
|
|
};
|
|
|
|
function writeFixed32(val, buf, pos) {
|
|
buf[pos ] = val & 255;
|
|
buf[pos + 1] = val >>> 8 & 255;
|
|
buf[pos + 2] = val >>> 16 & 255;
|
|
buf[pos + 3] = val >>> 24;
|
|
}
|
|
|
|
/**
|
|
* Writes an unsigned 32 bit value as fixed 32 bits.
|
|
* @param {number} value Value to write
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.fixed32 = function write_fixed32(value) {
|
|
return this._push(writeFixed32, 4, value >>> 0);
|
|
};
|
|
|
|
/**
|
|
* Writes a signed 32 bit value as fixed 32 bits.
|
|
* @function
|
|
* @param {number} value Value to write
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.sfixed32 = Writer.prototype.fixed32;
|
|
|
|
/**
|
|
* Writes an unsigned 64 bit value as fixed 64 bits.
|
|
* @param {Long|number|string} value Value to write
|
|
* @returns {Writer} `this`
|
|
* @throws {TypeError} If `value` is a string and no long library is present.
|
|
*/
|
|
Writer.prototype.fixed64 = function write_fixed64(value) {
|
|
var bits = LongBits.from(value);
|
|
return this._push(writeFixed32, 4, bits.lo)._push(writeFixed32, 4, bits.hi);
|
|
};
|
|
|
|
/**
|
|
* Writes a signed 64 bit value as fixed 64 bits.
|
|
* @function
|
|
* @param {Long|number|string} value Value to write
|
|
* @returns {Writer} `this`
|
|
* @throws {TypeError} If `value` is a string and no long library is present.
|
|
*/
|
|
Writer.prototype.sfixed64 = Writer.prototype.fixed64;
|
|
|
|
/**
|
|
* Writes a float (32 bit).
|
|
* @function
|
|
* @param {number} value Value to write
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.float = function write_float(value) {
|
|
return this._push(util.float.writeFloatLE, 4, value);
|
|
};
|
|
|
|
/**
|
|
* Writes a double (64 bit float).
|
|
* @function
|
|
* @param {number} value Value to write
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.double = function write_double(value) {
|
|
return this._push(util.float.writeDoubleLE, 8, value);
|
|
};
|
|
|
|
var writeBytes = util.Array.prototype.set
|
|
? function writeBytes_set(val, buf, pos) {
|
|
buf.set(val, pos); // also works for plain array values
|
|
}
|
|
/* istanbul ignore next */
|
|
: function writeBytes_for(val, buf, pos) {
|
|
for (var i = 0; i < val.length; ++i)
|
|
buf[pos + i] = val[i];
|
|
};
|
|
|
|
/**
|
|
* Writes a sequence of bytes.
|
|
* @param {Uint8Array|string} value Buffer or base64 encoded string to write
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.bytes = function write_bytes(value) {
|
|
var len = value.length >>> 0;
|
|
if (!len)
|
|
return this._push(writeByte, 1, 0);
|
|
if (util.isString(value)) {
|
|
var buf = Writer.alloc(len = base64.length(value));
|
|
base64.decode(value, buf, 0);
|
|
value = buf;
|
|
}
|
|
return this.uint32(len)._push(writeBytes, len, value);
|
|
};
|
|
|
|
/**
|
|
* Writes a string.
|
|
* @param {string} value Value to write
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.string = function write_string(value) {
|
|
var len = utf8.length(value);
|
|
return len
|
|
? this.uint32(len)._push(utf8.write, len, value)
|
|
: this._push(writeByte, 1, 0);
|
|
};
|
|
|
|
/**
|
|
* Forks this writer's state by pushing it to a stack.
|
|
* Calling {@link Writer#reset|reset} or {@link Writer#ldelim|ldelim} resets the writer to the previous state.
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.fork = function fork() {
|
|
this.states = new State(this);
|
|
this.head = this.tail = new Op(noop, 0, 0);
|
|
this.len = 0;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Resets this instance to the last state.
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.reset = function reset() {
|
|
if (this.states) {
|
|
this.head = this.states.head;
|
|
this.tail = this.states.tail;
|
|
this.len = this.states.len;
|
|
this.states = this.states.next;
|
|
} else {
|
|
this.head = this.tail = new Op(noop, 0, 0);
|
|
this.len = 0;
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Resets to the last state and appends the fork state's current write length as a varint followed by its operations.
|
|
* @returns {Writer} `this`
|
|
*/
|
|
Writer.prototype.ldelim = function ldelim() {
|
|
var head = this.head,
|
|
tail = this.tail,
|
|
len = this.len;
|
|
this.reset().uint32(len);
|
|
if (len) {
|
|
this.tail.next = head.next; // skip noop
|
|
this.tail = tail;
|
|
this.len += len;
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Finishes the write operation.
|
|
* @returns {Uint8Array} Finished buffer
|
|
*/
|
|
Writer.prototype.finish = function finish() {
|
|
var head = this.head.next, // skip noop
|
|
buf = this.constructor.alloc(this.len),
|
|
pos = 0;
|
|
while (head) {
|
|
head.fn(head.val, buf, pos);
|
|
pos += head.len;
|
|
head = head.next;
|
|
}
|
|
// this.head = this.tail = null;
|
|
return buf;
|
|
};
|
|
|
|
Writer._configure = function(BufferWriter_) {
|
|
BufferWriter = BufferWriter_;
|
|
};
|