125 lines
4.2 KiB
JavaScript
125 lines
4.2 KiB
JavaScript
|
|
/**
|
||
|
|
* Bl is a list of byte chunks, similar to https://github.com/rvagg/bl but for
|
||
|
|
* writing rather than reading.
|
||
|
|
* A Bl object accepts set() operations for individual bytes and copyTo() for
|
||
|
|
* inserting byte arrays. These write operations don't automatically increment
|
||
|
|
* the internal cursor so its "length" won't be changed. Instead, increment()
|
||
|
|
* must be called to extend its length to cover the inserted data.
|
||
|
|
* The toBytes() call will convert all internal memory to a single Uint8Array of
|
||
|
|
* the correct length, truncating any data that is stored but hasn't been
|
||
|
|
* included by an increment().
|
||
|
|
* get() can retrieve a single byte.
|
||
|
|
* All operations (except toBytes()) take an "offset" argument that will perform
|
||
|
|
* the write at the offset _from the current cursor_. For most operations this
|
||
|
|
* will be `0` to write at the current cursor position but it can be ahead of
|
||
|
|
* the current cursor. Negative offsets probably work but are untested.
|
||
|
|
*/
|
||
|
|
|
||
|
|
// TODO: ipjs doesn't support this, only for test files: https://github.com/mikeal/ipjs/blob/master/src/package/testFile.js#L39
|
||
|
|
import { alloc, concat, slice } from './byte-utils.js'
|
||
|
|
|
||
|
|
// the ts-ignores in this file are almost all for the `Uint8Array|number[]` duality that exists
|
||
|
|
// for perf reasons. Consider better approaches to this or removing it entirely, it is quite
|
||
|
|
// risky because of some assumptions about small chunks === number[] and everything else === Uint8Array.
|
||
|
|
|
||
|
|
const defaultChunkSize = 256
|
||
|
|
|
||
|
|
export class Bl {
|
||
|
|
/**
|
||
|
|
* @param {number} [chunkSize]
|
||
|
|
*/
|
||
|
|
constructor (chunkSize = defaultChunkSize) {
|
||
|
|
this.chunkSize = chunkSize
|
||
|
|
/** @type {number} */
|
||
|
|
this.cursor = 0
|
||
|
|
/** @type {number} */
|
||
|
|
this.maxCursor = -1
|
||
|
|
/** @type {(Uint8Array|number[])[]} */
|
||
|
|
this.chunks = []
|
||
|
|
// keep the first chunk around if we can to save allocations for future encodes
|
||
|
|
/** @type {Uint8Array|number[]|null} */
|
||
|
|
this._initReuseChunk = null
|
||
|
|
}
|
||
|
|
|
||
|
|
reset () {
|
||
|
|
this.cursor = 0
|
||
|
|
this.maxCursor = -1
|
||
|
|
if (this.chunks.length) {
|
||
|
|
this.chunks = []
|
||
|
|
}
|
||
|
|
if (this._initReuseChunk !== null) {
|
||
|
|
this.chunks.push(this._initReuseChunk)
|
||
|
|
this.maxCursor = this._initReuseChunk.length - 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array|number[]} bytes
|
||
|
|
*/
|
||
|
|
push (bytes) {
|
||
|
|
let topChunk = this.chunks[this.chunks.length - 1]
|
||
|
|
const newMax = this.cursor + bytes.length
|
||
|
|
if (newMax <= this.maxCursor + 1) {
|
||
|
|
// we have at least one chunk and we can fit these bytes into that chunk
|
||
|
|
const chunkPos = topChunk.length - (this.maxCursor - this.cursor) - 1
|
||
|
|
// @ts-ignore
|
||
|
|
topChunk.set(bytes, chunkPos)
|
||
|
|
} else {
|
||
|
|
// can't fit it in
|
||
|
|
if (topChunk) {
|
||
|
|
// trip the last chunk to `cursor` if we need to
|
||
|
|
const chunkPos = topChunk.length - (this.maxCursor - this.cursor) - 1
|
||
|
|
if (chunkPos < topChunk.length) {
|
||
|
|
// @ts-ignore
|
||
|
|
this.chunks[this.chunks.length - 1] = topChunk.subarray(0, chunkPos)
|
||
|
|
this.maxCursor = this.cursor - 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (bytes.length < 64 && bytes.length < this.chunkSize) {
|
||
|
|
// make a new chunk and copy the new one into it
|
||
|
|
topChunk = alloc(this.chunkSize)
|
||
|
|
this.chunks.push(topChunk)
|
||
|
|
this.maxCursor += topChunk.length
|
||
|
|
if (this._initReuseChunk === null) {
|
||
|
|
this._initReuseChunk = topChunk
|
||
|
|
}
|
||
|
|
// @ts-ignore
|
||
|
|
topChunk.set(bytes, 0)
|
||
|
|
} else {
|
||
|
|
// push the new bytes in as its own chunk
|
||
|
|
this.chunks.push(bytes)
|
||
|
|
this.maxCursor += bytes.length
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this.cursor += bytes.length
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {boolean} [reset]
|
||
|
|
* @returns {Uint8Array}
|
||
|
|
*/
|
||
|
|
toBytes (reset = false) {
|
||
|
|
let byts
|
||
|
|
if (this.chunks.length === 1) {
|
||
|
|
const chunk = this.chunks[0]
|
||
|
|
if (reset && this.cursor > chunk.length / 2) {
|
||
|
|
/* c8 ignore next 2 */
|
||
|
|
// @ts-ignore
|
||
|
|
byts = this.cursor === chunk.length ? chunk : chunk.subarray(0, this.cursor)
|
||
|
|
this._initReuseChunk = null
|
||
|
|
this.chunks = []
|
||
|
|
} else {
|
||
|
|
// @ts-ignore
|
||
|
|
byts = slice(chunk, 0, this.cursor)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// @ts-ignore
|
||
|
|
byts = concat(this.chunks, this.cursor)
|
||
|
|
}
|
||
|
|
if (reset) {
|
||
|
|
this.reset()
|
||
|
|
}
|
||
|
|
return byts
|
||
|
|
}
|
||
|
|
}
|