diff options
Diffstat (limited to 'node_modules/capnp-ts/src/serialization/packing.js')
| -rw-r--r-- | node_modules/capnp-ts/src/serialization/packing.js | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/node_modules/capnp-ts/src/serialization/packing.js b/node_modules/capnp-ts/src/serialization/packing.js new file mode 100644 index 0000000..ac359d3 --- /dev/null +++ b/node_modules/capnp-ts/src/serialization/packing.js @@ -0,0 +1,274 @@ +"use strict"; +/** + * @author jdiaz5513 + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.unpack = exports.pack = exports.getZeroByteCount = exports.getUnpackedByteLength = exports.getTagByte = exports.getHammingWeight = void 0; +const constants_1 = require("../constants"); +const errors_1 = require("../errors"); +/** + * Compute the Hamming weight (number of bits set to 1) of a number. Used to figure out how many bytes follow a tag byte + * while computing the size of a packed message. + * + * WARNING: Using this with floating point numbers will void your warranty. + * + * @param {number} x A real integer. + * @returns {number} The hamming weight (integer). + */ +function getHammingWeight(x) { + // Thanks, HACKMEM! + let w = x - ((x >> 1) & 0x55555555); + w = (w & 0x33333333) + ((w >> 2) & 0x33333333); + return (((w + (w >> 4)) & 0x0f0f0f0f) * 0x01010101) >> 24; +} +exports.getHammingWeight = getHammingWeight; +/** + * Compute the tag byte from the 8 bytes of a 64-bit word. + * + * @param {byte} a The first byte. + * @param {byte} b The second byte. + * @param {byte} c The third byte. + * @param {byte} d The fourth byte. + * @param {byte} e The fifth byte. + * @param {byte} f The sixth byte. + * @param {byte} g The seventh byte. + * @param {byte} h The eighth byte (phew!). + * @returns {number} The tag byte. + */ +function getTagByte(a, b, c, d, e, f, g, h) { + // Yes, it's pretty. Don't touch it. + return ((a === 0 ? 0 : 0b00000001) | + (b === 0 ? 0 : 0b00000010) | + (c === 0 ? 0 : 0b00000100) | + (d === 0 ? 0 : 0b00001000) | + (e === 0 ? 0 : 0b00010000) | + (f === 0 ? 0 : 0b00100000) | + (g === 0 ? 0 : 0b01000000) | + (h === 0 ? 0 : 0b10000000)); +} +exports.getTagByte = getTagByte; +/** + * Efficiently calculate the length of a packed Cap'n Proto message. + * + * @export + * @param {ArrayBuffer} packed The packed message. + * @returns {number} The length of the unpacked message in bytes. + */ +function getUnpackedByteLength(packed) { + const p = new Uint8Array(packed); + let wordLength = 0; + let lastTag = 0x77; + for (let i = 0; i < p.byteLength;) { + const tag = p[i]; + if (lastTag === 0 /* ZERO */) { + wordLength += tag; + i++; + lastTag = 0x77; + } + else if (lastTag === 255 /* SPAN */) { + wordLength += tag; + i += tag * 8 + 1; + lastTag = 0x77; + } + else { + wordLength++; + i += getHammingWeight(tag) + 1; + lastTag = tag; + } + } + return wordLength * 8; +} +exports.getUnpackedByteLength = getUnpackedByteLength; +/** + * Compute the number of zero bytes that occur in a given 64-bit word, provided as eight separate bytes. + * + * @param {byte} a The first byte. + * @param {byte} b The second byte. + * @param {byte} c The third byte. + * @param {byte} d The fourth byte. + * @param {byte} e The fifth byte. + * @param {byte} f The sixth byte. + * @param {byte} g The seventh byte. + * @param {byte} h The eighth byte (phew!). + * @returns {number} The number of these bytes that are zero. + */ +function getZeroByteCount(a, b, c, d, e, f, g, h) { + return ((a === 0 ? 1 : 0) + + (b === 0 ? 1 : 0) + + (c === 0 ? 1 : 0) + + (d === 0 ? 1 : 0) + + (e === 0 ? 1 : 0) + + (f === 0 ? 1 : 0) + + (g === 0 ? 1 : 0) + + (h === 0 ? 1 : 0)); +} +exports.getZeroByteCount = getZeroByteCount; +/** + * Pack a section of a Cap'n Proto message into a compressed format. This will efficiently compress zero bytes (which + * are common in idiomatic Cap'n Proto messages) into a compact form. + * + * For stream-framed messages this is called once for the frame header and once again for each segment in the message. + * + * The returned array buffer is trimmed to the exact size of the packed message with a single copy operation at the end. + * This should be decent on CPU time but does require quite a lot of memory (a normal array is filled up with each + * packed byte until the packing is complete). + * + * @export + * @param {ArrayBuffer} unpacked The message to pack. + * @param {number} [byteOffset] Starting byte offset to read bytes from, defaults to 0. + * @param {number} [byteLength] Total number of bytes to read, defaults to the remainder of the buffer contents. + * @returns {ArrayBuffer} A packed version of the message. + */ +function pack(unpacked, byteOffset = 0, byteLength) { + if (unpacked.byteLength % 8 !== 0) + throw new Error(errors_1.MSG_PACK_NOT_WORD_ALIGNED); + const src = new Uint8Array(unpacked, byteOffset, byteLength); + // TODO: Maybe we should do this with buffers? This costs more than 8x the final compressed size in temporary RAM. + const dst = []; + /* Just have to be sure it's neither ZERO nor SPAN. */ + let lastTag = 0x77; + /** This is where we need to remember to write the SPAN tag (0xff). */ + let spanTagOffset = NaN; + /** How many words have been copied during the current span. */ + let spanWordLength = 0; + /** + * When this hits zero, we've had PACK_SPAN_THRESHOLD zero bytes pass by and it's time to bail from the span. + */ + let spanThreshold = constants_1.PACK_SPAN_THRESHOLD; + for (let srcByteOffset = 0; srcByteOffset < src.byteLength; srcByteOffset += 8) { + /** Read in the entire word. Yes, this feels silly but it's fast! */ + const a = src[srcByteOffset]; + const b = src[srcByteOffset + 1]; + const c = src[srcByteOffset + 2]; + const d = src[srcByteOffset + 3]; + const e = src[srcByteOffset + 4]; + const f = src[srcByteOffset + 5]; + const g = src[srcByteOffset + 6]; + const h = src[srcByteOffset + 7]; + const tag = getTagByte(a, b, c, d, e, f, g, h); + /** If this is true we'll skip the normal word write logic after the switch statement. */ + let skipWriteWord = true; + switch (lastTag) { + case 0 /* ZERO */: + // We're writing a span of words with all zeroes in them. See if we need to bail out of the fast path. + if (tag !== 0 /* ZERO */ || spanWordLength >= 0xff) { + // There's a bit in there or we got too many zeroes. Damn, we need to bail. + dst.push(spanWordLength); + spanWordLength = 0; + skipWriteWord = false; + } + else { + // Kay, let's quickly inc this and go. + spanWordLength++; + } + break; + case 255 /* SPAN */: { + // We're writing a span of nonzero words. + const zeroCount = getZeroByteCount(a, b, c, d, e, f, g, h); + // See if we need to bail now. + spanThreshold -= zeroCount; + if (spanThreshold <= 0 || spanWordLength >= 0xff) { + // Alright, time to get packing again. Write the number of words we skipped to the beginning of the span. + dst[spanTagOffset] = spanWordLength; + spanWordLength = 0; + spanThreshold = constants_1.PACK_SPAN_THRESHOLD; + // We have to write this word normally. + skipWriteWord = false; + } + else { + // Just write this word verbatim. + dst.push(a, b, c, d, e, f, g, h); + spanWordLength++; + } + break; + } + default: + // Didn't get a special tag last time, let's write this as normal. + skipWriteWord = false; + break; + } + // A goto is fast, idk why people keep hatin'. + if (skipWriteWord) + continue; + dst.push(tag); + lastTag = tag; + if (a !== 0) + dst.push(a); + if (b !== 0) + dst.push(b); + if (c !== 0) + dst.push(c); + if (d !== 0) + dst.push(d); + if (e !== 0) + dst.push(e); + if (f !== 0) + dst.push(f); + if (g !== 0) + dst.push(g); + if (h !== 0) + dst.push(h); + // Record the span tag offset if needed, making sure to actually leave room for it. + if (tag === 255 /* SPAN */) { + spanTagOffset = dst.length; + dst.push(0); + } + } + // We're done. If we were writing a span let's finish it. + if (lastTag === 0 /* ZERO */) { + dst.push(spanWordLength); + } + else if (lastTag === 255 /* SPAN */) { + dst[spanTagOffset] = spanWordLength; + } + return new Uint8Array(dst).buffer; +} +exports.pack = pack; +/** + * Unpack a compressed Cap'n Proto message into a new ArrayBuffer. + * + * Unlike the `pack` function, this is able to efficiently determine the exact size needed for the output buffer and + * runs considerably more efficiently. + * + * @export + * @param {ArrayBuffer} packed An array buffer containing the packed message. + * @returns {ArrayBuffer} The unpacked message. + */ +function unpack(packed) { + // We have no choice but to read the packed buffer one byte at a time. + const src = new Uint8Array(packed); + const dst = new Uint8Array(new ArrayBuffer(getUnpackedByteLength(packed))); + /** The last tag byte that we've seen - it starts at a "neutral" value. */ + let lastTag = 0x77; + for (let srcByteOffset = 0, dstByteOffset = 0; srcByteOffset < src.byteLength;) { + const tag = src[srcByteOffset]; + if (lastTag === 0 /* ZERO */) { + // We have a span of zeroes. New array buffers are guaranteed to be initialized to zero so we just seek ahead. + dstByteOffset += tag * 8; + srcByteOffset++; + lastTag = 0x77; + } + else if (lastTag === 255 /* SPAN */) { + // We have a span of unpacked bytes. Copy them verbatim from the source buffer. + const spanByteLength = tag * 8; + dst.set(src.subarray(srcByteOffset + 1, srcByteOffset + 1 + spanByteLength), dstByteOffset); + dstByteOffset += spanByteLength; + srcByteOffset += 1 + spanByteLength; + lastTag = 0x77; + } + else { + // Okay, a normal tag. Let's read past the tag and copy bytes that have a bit set in the tag. + srcByteOffset++; + for (let i = 1; i <= 0b10000000; i <<= 1) { + // We only need to actually touch `dst` if there's a nonzero byte (it's already initialized to zeroes). + if ((tag & i) !== 0) + dst[dstByteOffset] = src[srcByteOffset++]; + dstByteOffset++; + } + lastTag = tag; + } + } + return dst.buffer; +} +exports.unpack = unpack; +//# sourceMappingURL=packing.js.map
\ No newline at end of file |
