UNPKG

@chainsafe/ssz

Version:
851 lines 34.2 kB
import { allocUnsafe } from "@chainsafe/as-sha256"; import { BranchNode, LeafNode, Tree, concatGindices, getHashComputations, getNode, merkleizeBlocksBytes, packedNodeRootsToBytes, packedRootsBytesToLeafNodes, setNode, subtreeFillToContents, zeroNode, } from "@chainsafe/persistent-merkle-tree"; import { byteArrayEquals } from "../util/byteArray.js"; import { cacheRoot, symbolCachedPermanentRoot } from "../util/merkleize.js"; import { namedClass } from "../util/named.js"; import { TreeView } from "../view/abstract.js"; import { TreeViewDU } from "../viewDU/abstract.js"; import { ArrayType } from "./array.js"; import { addLengthNode, assertValidArrayLength, getLengthFromRootNode, setChunksNode, value_deserializeFromBytesArrayBasic, value_serializeToBytesArrayBasic, } from "./arrayBasic.js"; import { minSizeArrayComposite, value_deserializeFromBytesArrayComposite, value_serializeToBytesArrayComposite, value_serializedSizeArrayComposite, } from "./arrayComposite.js"; import { PROGRESSIVE_LIST_MAX_SIZE, getNodesAtProgressiveDepth, merkleizeProgressiveBytes, progressiveChunkGindex, progressiveSubtreeDepth, progressiveSubtreeFillToContents, } from "./progressive.js"; const CHUNKS_GINDEX = BigInt(2); const LENGTH_GINDEX = BigInt(3); export class ProgressiveListBasicType extends ArrayType { elementType; typeName; itemsPerChunk; depth = 1; chunkDepth = 0; maxChunkCount = Number.MAX_SAFE_INTEGER; fixedSize = null; minSize = 0; maxSize = PROGRESSIVE_LIST_MAX_SIZE; isList = true; isViewMutable = true; limit = Number.MAX_SAFE_INTEGER; mixInLengthBlockBytes = new Uint8Array(64); mixInLengthBuffer = Buffer.from(this.mixInLengthBlockBytes.buffer, this.mixInLengthBlockBytes.byteOffset, this.mixInLengthBlockBytes.byteLength); defaultLen = 0; constructor(elementType, opts) { super(elementType, opts?.cachePermanentRootStruct); this.elementType = elementType; if (!elementType.isBasic) throw Error("elementType must be basic"); this.typeName = opts?.typeName ?? `ProgressiveList[${elementType.typeName}]`; this.itemsPerChunk = 32 / elementType.byteLength; } static named(elementType, opts) { return new (namedClass(ProgressiveListBasicType, opts.typeName))(elementType, opts); } getView(tree) { return new ProgressiveListBasicTreeView(this, tree); } getViewDU(node) { return new ProgressiveListBasicTreeViewDU(this, node); } commitView(view) { return view.node; } commitViewDU(view, hcOffset = 0, hcByLevel = null) { view.commit(hcOffset, hcByLevel); return view.node; } cacheOfViewDU() { return; } createFromProof(proof, root) { const rootNode = Tree.createFromProof(proof).rootNode; if (root !== undefined && !byteArrayEquals(rootNode.root, root)) { throw new Error("Proof does not match trusted root"); } return this.getView(new Tree(rootNode)); } value_serializedSize(value) { return value.length * this.elementType.byteLength; } value_serializeToBytes(output, offset, value) { return value_serializeToBytesArrayBasic(this.elementType, value.length, output, offset, value); } value_deserializeFromBytes(data, start, end) { return value_deserializeFromBytesArrayBasic(this.elementType, data, start, end, this); } tree_serializedSize(node) { return this.tree_getLength(node) * this.elementType.byteLength; } tree_serializeToBytes(output, offset, node) { const chunksNode = this.tree_getChunksNode(node); const length = this.tree_getLength(node); const size = length * this.elementType.byteLength; const chunkCount = Math.ceil(size / 32); const nodes = getNodesAtProgressiveDepth(chunksNode, chunkCount); packedNodeRootsToBytes(output.dataView, offset, size, nodes); return offset + size; } tree_deserializeFromBytes(data, start, end) { const length = (end - start) / this.elementType.byteLength; assertValidArrayLength(length, this, true); const nodes = packedRootsBytesToLeafNodes(data.dataView, start, end); return addLengthNode(progressiveSubtreeFillToContents(nodes), length); } tree_getLength(node) { return getLengthFromRootNode(node); } tree_setLength(tree, length) { tree.rootNode = addLengthNode(tree.rootNode.left, length); } tree_getChunksNode(node) { return node.left; } tree_chunksNodeOffset() { return 1; } tree_setChunksNode(rootNode, chunksNode, newLength, hcOffset = 0, hcByLevel = null) { return setChunksNode(rootNode, chunksNode, newLength, hcOffset, hcByLevel); } hashTreeRoot(value) { if (this.cachePermanentRootStruct) { const cachedRoot = value[symbolCachedPermanentRoot]; if (cachedRoot) { return cachedRoot; } } const root = allocUnsafe(32); this.hashTreeRootInto(value, root, 0, true); return root; } hashTreeRootInto(value, output, offset, safeCache = false) { if (this.cachePermanentRootStruct) { const cachedRoot = value[symbolCachedPermanentRoot]; if (cachedRoot) { output.set(cachedRoot, offset); return; } } const blockBytes = this.getBlocksBytes(value); const chunkCount = Math.ceil(this.value_serializedSize(value) / 32); merkleizeProgressiveBytes(blockBytes, chunkCount, this.mixInLengthBlockBytes, 0); this.mixInLengthBuffer.writeUIntLE(value.length, 32, 6); merkleizeBlocksBytes(this.mixInLengthBlockBytes, 2, output, offset); if (this.cachePermanentRootStruct) { cacheRoot(value, output, offset, safeCache); } } getBlocksBytes(value) { const byteLen = this.value_serializedSize(value); const blockByteLen = Math.ceil(byteLen / 64) * 64; if (blockByteLen > this.blocksBuffer.length) { this.blocksBuffer = new Uint8Array(blockByteLen); } const blockBytes = this.blocksBuffer.subarray(0, blockByteLen); const uint8Array = blockBytes.subarray(0, byteLen); const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); value_serializeToBytesArrayBasic(this.elementType, value.length, { uint8Array, dataView }, 0, value); blockBytes.subarray(byteLen).fill(0); return blockBytes; } getPropertyGindex(prop) { if (typeof prop !== "number") { throw Error(`Invalid array index: ${prop}`); } const chunkIndex = Math.floor(prop / this.itemsPerChunk); return concatGindices([CHUNKS_GINDEX, progressiveChunkGindex(chunkIndex)]); } tree_getLeafGindices(rootGindex, rootNode) { if (!rootNode) { throw new Error("ProgressiveList type requires tree argument to get leaves"); } const length = this.tree_getLength(rootNode); const chunkCount = Math.ceil(length / this.itemsPerChunk); const gindices = new Array(chunkCount); for (let i = 0; i < chunkCount; i++) { gindices[i] = concatGindices([rootGindex, CHUNKS_GINDEX, progressiveChunkGindex(i)]); } gindices.push(concatGindices([rootGindex, LENGTH_GINDEX])); return gindices; } tree_fromProofNode(node) { return { node, done: true }; } } export class ProgressiveListCompositeType extends ArrayType { elementType; typeName; itemsPerChunk = 1; depth = 1; chunkDepth = 0; maxChunkCount = Number.MAX_SAFE_INTEGER; fixedSize = null; minSize; maxSize; isList = true; isViewMutable = true; limit = Number.MAX_SAFE_INTEGER; mixInLengthBlockBytes = new Uint8Array(64); mixInLengthBuffer = Buffer.from(this.mixInLengthBlockBytes.buffer, this.mixInLengthBlockBytes.byteOffset, this.mixInLengthBlockBytes.byteLength); defaultLen = 0; constructor(elementType, opts) { super(elementType, opts?.cachePermanentRootStruct); this.elementType = elementType; if (elementType.isBasic) throw Error("elementType must not be basic"); this.typeName = opts?.typeName ?? `ProgressiveList[${elementType.typeName}]`; this.minSize = minSizeArrayComposite(elementType, 0); this.maxSize = PROGRESSIVE_LIST_MAX_SIZE; } static named(elementType, opts) { return new (namedClass(ProgressiveListCompositeType, opts.typeName))(elementType, opts); } getView(tree) { return new ProgressiveListCompositeTreeView(this, tree); } getViewDU(node, cache) { return new ProgressiveListCompositeTreeViewDU(this, node, cache); } commitView(view) { return view.node; } commitViewDU(view, hcOffset = 0, hcByLevel = null) { view.commit(hcOffset, hcByLevel); return view.node; } cacheOfViewDU() { return; } createFromProof(proof, root) { const rootNode = Tree.createFromProof(proof).rootNode; if (root !== undefined && !byteArrayEquals(rootNode.root, root)) { throw new Error("Proof does not match trusted root"); } return this.getView(new Tree(rootNode)); } value_serializedSize(value) { return value_serializedSizeArrayComposite(this.elementType, value.length, value); } value_serializeToBytes(output, offset, value) { return value_serializeToBytesArrayComposite(this.elementType, value.length, output, offset, value); } value_deserializeFromBytes(data, start, end, reuseBytes) { return value_deserializeFromBytesArrayComposite(this.elementType, data, start, end, this, reuseBytes); } tree_serializedSize(node) { const chunksNode = this.tree_getChunksNode(node); const length = this.tree_getLength(node); const nodes = getNodesAtProgressiveDepth(chunksNode, length); if (this.elementType.fixedSize === null) { let size = 0; for (const node of nodes) { size += 4 + this.elementType.tree_serializedSize(node); } return size; } return length * this.elementType.fixedSize; } tree_serializeToBytes(output, offset, node) { const chunksNode = this.tree_getChunksNode(node); const length = this.tree_getLength(node); const nodes = getNodesAtProgressiveDepth(chunksNode, length); if (this.elementType.fixedSize === null) { let variableIndex = offset + length * 4; for (let i = 0; i < nodes.length; i++) { output.dataView.setUint32(offset + i * 4, variableIndex - offset, true); variableIndex = this.elementType.tree_serializeToBytes(output, variableIndex, nodes[i]); } return variableIndex; } for (const node of nodes) { offset = this.elementType.tree_serializeToBytes(output, offset, node); } return offset; } tree_deserializeFromBytes(data, start, end) { const offsets = readOffsetsProgressiveListComposite(this.elementType.fixedSize, data.dataView, start, end, this); const length = offsets.length; const nodes = new Array(length); for (let i = 0; i < length; i++) { const startEl = start + offsets[i]; const endEl = i === length - 1 ? end : start + offsets[i + 1]; nodes[i] = this.elementType.tree_deserializeFromBytes(data, startEl, endEl); } return addLengthNode(progressiveSubtreeFillToContents(nodes), length); } tree_getLength(node) { return getLengthFromRootNode(node); } tree_setLength(tree, length) { tree.rootNode = addLengthNode(tree.rootNode.left, length); } tree_getChunksNode(node) { return node.left; } tree_chunksNodeOffset() { return 1; } tree_setChunksNode(rootNode, chunksNode, newLength, hcOffset = 0, hcByLevel = null) { return setChunksNode(rootNode, chunksNode, newLength, hcOffset, hcByLevel); } hashTreeRoot(value) { if (this.cachePermanentRootStruct) { const cachedRoot = value[symbolCachedPermanentRoot]; if (cachedRoot) { return cachedRoot; } } const root = allocUnsafe(32); this.hashTreeRootInto(value, root, 0, true); return root; } hashTreeRootInto(value, output, offset, safeCache = false) { if (this.cachePermanentRootStruct) { const cachedRoot = value[symbolCachedPermanentRoot]; if (cachedRoot) { output.set(cachedRoot, offset); return; } } const blockBytes = this.getBlocksBytes(value); merkleizeProgressiveBytes(blockBytes, value.length, this.mixInLengthBlockBytes, 0); this.mixInLengthBuffer.writeUIntLE(value.length, 32, 6); merkleizeBlocksBytes(this.mixInLengthBlockBytes, 2, output, offset); if (this.cachePermanentRootStruct) { cacheRoot(value, output, offset, safeCache); } } getBlocksBytes(value) { const blockBytesLen = Math.ceil(value.length / 2) * 64; if (blockBytesLen > this.blocksBuffer.length) { this.blocksBuffer = new Uint8Array(blockBytesLen); } const blockBytes = this.blocksBuffer.subarray(0, blockBytesLen); for (let i = 0; i < value.length; i++) { this.elementType.hashTreeRootInto(value[i], blockBytes, i * 32); } if (value.length % 2 === 1) { blockBytes.subarray(value.length * 32, value.length * 32 + 32).fill(0); } return blockBytes; } getPropertyGindex(prop) { if (typeof prop !== "number") { throw Error(`Invalid array index: ${prop}`); } return concatGindices([CHUNKS_GINDEX, progressiveChunkGindex(prop)]); } tree_getLeafGindices(rootGindex, rootNode) { if (!rootNode) { throw new Error("ProgressiveList type requires tree argument to get leaves"); } const length = this.tree_getLength(rootNode); const gindices = []; for (let i = 0; i < length; i++) { const elementGindexFromListRoot = concatGindices([CHUNKS_GINDEX, progressiveChunkGindex(i)]); const elementGindex = concatGindices([rootGindex, elementGindexFromListRoot]); if (this.elementType.isBasic) { gindices.push(elementGindex); } else if (this.elementType.fixedSize === null) { gindices.push(...this.elementType.tree_getLeafGindices(elementGindex, getNode(rootNode, elementGindexFromListRoot))); } else { gindices.push(...this.elementType.tree_getLeafGindices(elementGindex)); } } gindices.push(concatGindices([rootGindex, LENGTH_GINDEX])); return gindices; } tree_fromProofNode(node) { return { node, done: true }; } } export class ProgressiveListBasicTreeView extends TreeView { type; tree; constructor(type, tree) { super(); this.type = type; this.tree = tree; } get length() { return this.type.tree_getLength(this.tree.rootNode); } get node() { return this.tree.rootNode; } get(index) { const chunkIndex = Math.floor(index / this.type.itemsPerChunk); const leafNode = this.tree.getNode(chunkGindexFromListRoot(chunkIndex)); return this.type.elementType.tree_getFromPackedNode(leafNode, index); } set(index, value) { const length = this.length; if (index >= length) { throw Error(`Error setting index over length ${index} > ${length}`); } const chunkIndex = Math.floor(index / this.type.itemsPerChunk); const gindex = chunkGindexFromListRoot(chunkIndex); const leafNode = this.tree.getNode(gindex).clone(); this.type.elementType.tree_setToPackedNode(leafNode, index, value); this.tree.setNode(gindex, leafNode); } getAll(values) { const length = this.length; if (values && values.length !== length) { throw Error(`Expected ${length} values, got ${values.length}`); } const chunksNode = this.type.tree_getChunksNode(this.node); const chunkCount = Math.ceil(length / this.type.itemsPerChunk); const leafNodes = getNodesAtProgressiveDepth(chunksNode, chunkCount); values = values ?? new Array(length); for (let i = 0; i < length; i++) { values[i] = this.type.elementType.tree_getFromPackedNode(leafNodes[Math.floor(i / this.type.itemsPerChunk)], i); } return values; } } export class ProgressiveListBasicTreeViewDU extends TreeViewDU { type; _rootNode; constructor(type, _rootNode) { super(); this.type = type; this._rootNode = _rootNode; } get length() { return this.type.tree_getLength(this._rootNode); } get node() { return this._rootNode; } get cache() { return undefined; } commit(hcOffset = 0, hcByLevel = null) { if (hcByLevel !== null && this._rootNode.h0 === null) { getHashComputations(this._rootNode, hcOffset, hcByLevel); } } get(index) { const chunkIndex = Math.floor(index / this.type.itemsPerChunk); const leafNode = getNode(this._rootNode, chunkGindexFromListRoot(chunkIndex)); return this.type.elementType.tree_getFromPackedNode(leafNode, index); } set(index, value) { const length = this.length; if (index >= length) { throw Error(`Error setting index over length ${index} > ${length}`); } const chunkIndex = Math.floor(index / this.type.itemsPerChunk); const gindex = chunkGindexFromListRoot(chunkIndex); const leafNode = getNode(this._rootNode, gindex).clone(); this.type.elementType.tree_setToPackedNode(leafNode, index, value); this._rootNode = setNode(this._rootNode, gindex, leafNode); } push(value) { const length = this.length; if (length >= this.type.limit) { throw Error("Error pushing over limit"); } const chunkIndex = Math.floor(length / this.type.itemsPerChunk); const gindex = chunkGindexFromListRoot(chunkIndex); const leafNode = length % this.type.itemsPerChunk === 0 ? LeafNode.fromZero() : getNode(this._rootNode, gindex).clone(); this.type.elementType.tree_setToPackedNode(leafNode, length, value); const chunksNode = appendProgressiveChunk(this.type.tree_getChunksNode(this._rootNode), chunkIndex, leafNode); this._rootNode = this.type.tree_setChunksNode(this._rootNode, chunksNode, length + 1); } getAll(values) { const view = new ProgressiveListBasicTreeView(this.type, new Tree(this._rootNode)); return view.getAll(values); } /** * Returns a new ProgressiveListBasicTreeViewDU instance with the values from 0 to `index`. * To achieve it, rebinds the underlying tree zero-ing all nodes right of `chunkIndex`. * Also set all value right of `index` in the same chunk to 0. */ sliceTo(index) { if (index < 0) { throw new Error(`Does not support sliceTo() with negative index ${index}`); } const length = this.length; if (index >= length - 1) { return this; } const chunksNode = this.type.tree_getChunksNode(this._rootNode); const chunkIndex = Math.floor(index / this.type.itemsPerChunk); const nodePrev = getNode(this._rootNode, chunkGindexFromListRoot(chunkIndex)); const nodeChanged = LeafNode.fromZero(); for (let i = chunkIndex * this.type.itemsPerChunk; i <= index; i++) { const prevValue = this.type.elementType.tree_getFromPackedNode(nodePrev, i); this.type.elementType.tree_setToPackedNode(nodeChanged, i, prevValue); } const chunkCount = Math.ceil((index + 1) / this.type.itemsPerChunk); const nodes = getNodesAtProgressiveDepth(chunksNode, chunkCount); nodes[chunkIndex] = nodeChanged; const newChunksNode = progressiveSubtreeFillToContents(nodes); const newLength = index + 1; const newRootNode = this.type.tree_setChunksNode(this._rootNode, newChunksNode, newLength); return this.type.getViewDU(newRootNode); } /** * Returns a new ProgressiveListBasicTreeViewDU instance with the values from `index` to the end of list. */ sliceFrom(index) { return this.type.toViewDU(this.getAll().slice(index)); } clearCache() { // No cached data to clear. } } export class ProgressiveListCompositeTreeView extends TreeView { type; tree; constructor(type, tree) { super(); this.type = type; this.tree = tree; } get length() { return this.type.tree_getLength(this.tree.rootNode); } get node() { return this.tree.rootNode; } get(index) { const subtree = this.tree.getSubtree(chunkGindexFromListRoot(index)); return this.type.elementType.getView(subtree); } set(index, view) { const length = this.length; if (index >= length) { throw Error(`Error setting index over length ${index} > ${length}`); } this.tree.setNode(chunkGindexFromListRoot(index), this.type.elementType.commitView(view)); } getAllReadonlyValues(values) { const length = this.length; if (values && values.length !== length) { throw Error(`Expected ${length} values, got ${values.length}`); } const chunksNode = this.type.tree_getChunksNode(this.node); const nodes = getNodesAtProgressiveDepth(chunksNode, length); values = values ?? new Array(length); for (let i = 0; i < length; i++) { values[i] = this.type.elementType.tree_toValue(nodes[i]); } return values; } } export class ProgressiveListCompositeTreeViewDU extends TreeViewDU { type; _rootNode; nodes = []; caches = []; viewsChanged = new Map(); nodesPopulated = false; constructor(type, _rootNode, cache) { super(); this.type = type; this._rootNode = _rootNode; if (cache) { this.nodes = cache.nodes ?? []; this.caches = cache.caches; this.nodesPopulated = cache.nodesPopulated ?? false; } } get length() { return this.type.tree_getLength(this._rootNode); } get node() { return this._rootNode; } get cache() { return { nodes: this.nodes, caches: this.caches, nodesPopulated: this.nodesPopulated }; } commit(hcOffset = 0, hcByLevel = null) { for (const [index, view] of this.viewsChanged) { const gindex = chunkGindexFromListRoot(index); this._rootNode = setNode(this._rootNode, gindex, this.type.elementType.commitViewDU(view)); this.caches[index] = this.type.elementType.cacheOfViewDU(view); this.nodes[index] = getNode(this._rootNode, gindex); } this.viewsChanged.clear(); if (hcByLevel !== null && this._rootNode.h0 === null) { getHashComputations(this._rootNode, hcOffset, hcByLevel); } } get(index) { const changed = this.viewsChanged.get(index); if (changed) { return changed; } let node = this.nodes[index]; if (node === undefined) { node = getNode(this._rootNode, chunkGindexFromListRoot(index)); this.nodes[index] = node; } const view = this.type.elementType.getViewDU(node, this.caches[index]); if (this.type.elementType.isViewMutable) { this.viewsChanged.set(index, view); } return view; } /** * Get element at `index`. Returns a view of the Composite element type. * DOES NOT PROPAGATE CHANGES: use only for reads and to skip parent references. */ getReadonly(index) { const changed = this.viewsChanged.get(index); if (changed) { return changed; } let node = this.nodes[index]; if (node === undefined) { node = getNode(this._rootNode, chunkGindexFromListRoot(index)); this.nodes[index] = node; } return this.type.elementType.getViewDU(node, this.caches[index]); } set(index, view) { const length = this.length; if (index >= length) { throw Error(`Error setting index over length ${index} > ${length}`); } this.viewsChanged.set(index, view); } push(view) { this.commit(); const length = this.length; if (length >= this.type.limit) { throw Error("Error pushing over limit"); } const node = this.type.elementType.commitViewDU(view); const chunksNode = appendProgressiveChunk(this.type.tree_getChunksNode(this._rootNode), length, node); this._rootNode = this.type.tree_setChunksNode(this._rootNode, chunksNode, length + 1); if (this.nodesPopulated) { this.nodes[length] = node; } this.caches[length] = this.type.elementType.cacheOfViewDU(view); if (this.type.elementType.isViewMutable) { this.viewsChanged.set(length, view); } } /** * Returns all elements at every index, if an index is modified it will return the modified view. * No need to commit() before calling this function. */ getAllReadonly(views) { const length = this.length; if (views && views.length !== length) { throw Error(`Expected ${length} views, got ${views.length}`); } this.populateAllOldNodes(); views = views ?? new Array(length); for (let i = 0; i < length; i++) { views[i] = this.getReadonly(i); } return views; } /** * Apply `fn` to each ViewDU in the array. * Similar to getAllReadOnly(), no need to commit() before calling this function. * if an item is modified it will return the modified view. */ forEach(fn) { this.populateAllOldNodes(); for (let i = 0; i < this.length; i++) { fn(this.getReadonly(i), i); } } /** * WARNING: Returns all commited changes, if there are any pending changes commit them beforehand * @param values optional output parameter, if is provided it must be an array of the same length as this array */ getAllReadonlyValues(values) { const length = this.length; if (values && values.length !== length) { throw Error(`Expected ${length} values, got ${values.length}`); } this.populateAllNodes(); values = values ?? new Array(length); for (let i = 0; i < length; i++) { values[i] = this.type.elementType.tree_toValue(this.nodes[i]); } return values; } /** * Apply `fn` to each value in the array. */ forEachValue(fn) { this.populateAllNodes(); for (let i = 0; i < this.length; i++) { fn(this.type.elementType.tree_toValue(this.nodes[i]), i); } } /** * Get by range of indexes. Returns an array of views of the Composite element type. * This is similar to getAllReadonly() where we dont have to commit() before calling this function. */ getReadonlyByRange(startIndex, count) { if (startIndex < 0) { throw Error(`Error getting by range, startIndex < 0: ${startIndex}`); } if (count <= 0) { throw Error(`Error getting by range, count <= 0: ${count}`); } const length = this.length; if (startIndex >= length) { throw Error(`Error getting by range, startIndex >= length: ${startIndex} >= ${length}`); } count = Math.min(count, length - startIndex); const result = new Array(count); for (let i = 0; i < count; i++) { result[i] = this.getReadonly(startIndex + i); } return result; } /** * Returns a new ProgressiveListCompositeTreeViewDU instance with the values from 0 to `index`. * `index` is inclusive. */ sliceTo(index) { this.commit(); const rootNode = this._rootNode; const length = this.length; if (index >= length - 1) { return this; } const newLength = Math.max(index + 1, 0); const chunksNode = this.type.tree_getChunksNode(rootNode); const nodes = getNodesAtProgressiveDepth(chunksNode, newLength); const newChunksNode = progressiveSubtreeFillToContents(nodes); const newRootNode = this.type.tree_setChunksNode(rootNode, newChunksNode, newLength); return this.rootNodeToViewDU(newRootNode); } /** * Returns a new ProgressiveListCompositeTreeViewDU instance with the values from `index` to the end of list. */ sliceFrom(index) { this.commit(); this.populateAllNodes(); if (index < 0) { index += this.nodes.length; } if (index <= 0) { return this; } const nodes = index >= this.nodes.length ? [] : this.nodes.slice(index); const newChunksNode = progressiveSubtreeFillToContents(nodes); const newRootNode = this.type.tree_setChunksNode(this._rootNode, newChunksNode, nodes.length); return this.rootNodeToViewDU(newRootNode); } clearCache() { this.nodes = []; this.caches = []; this.nodesPopulated = false; this.viewsChanged.clear(); } populateAllNodes() { if (this.viewsChanged.size > 0) { throw Error("Must commit changes before reading all nodes"); } if (!this.nodesPopulated) { const chunksNode = this.type.tree_getChunksNode(this._rootNode); this.nodes = getNodesAtProgressiveDepth(chunksNode, this.length); this.nodesPopulated = true; } } populateAllOldNodes() { if (!this.nodesPopulated) { const chunksNode = this.type.tree_getChunksNode(this._rootNode); this.nodes = getNodesAtProgressiveDepth(chunksNode, this.length); this.nodesPopulated = true; } } rootNodeToViewDU(rootNode) { return this.type.getViewDU(rootNode); } } function chunkGindexFromListRoot(chunkIndex) { return concatGindices([CHUNKS_GINDEX, progressiveChunkGindex(chunkIndex)]); } function appendProgressiveChunk(chunksNode, chunkIndex, chunkNode) { const { subtreeIndex, subtreeStart } = progressiveSubtreeIndexAndStart(chunkIndex); if (chunkIndex !== subtreeStart) { return setNode(chunksNode, progressiveChunkGindex(chunkIndex), chunkNode); } const subtreeNode = subtreeFillToContents([chunkNode], progressiveSubtreeDepth(subtreeIndex)); const subtreeBranch = new BranchNode(subtreeNode, zeroNode(0)); if (subtreeIndex === 0) { return subtreeBranch; } return setNode(chunksNode, progressiveSubtreeBranchGindex(subtreeIndex), subtreeBranch); } function progressiveSubtreeIndexAndStart(chunkIndex) { let subtreeIndex = 0; let subtreeStart = 0; let subtreeLength = 1; while (chunkIndex >= subtreeStart + subtreeLength) { subtreeStart += subtreeLength; subtreeLength *= 4; subtreeIndex++; } return { subtreeIndex, subtreeStart }; } function progressiveSubtreeBranchGindex(subtreeIndex) { return concatGindices(Array.from({ length: subtreeIndex }, () => BigInt(3))); } function readOffsetsProgressiveListComposite(elementFixedSize, data, start, end, arrayProps) { const size = end - start; if (elementFixedSize === null) { const offsets = readVariableOffsetsProgressiveList(data, start, size); assertValidArrayLength(offsets.length, arrayProps); return offsets; } if (elementFixedSize === 0) { throw Error("element fixed length is 0"); } if (size % elementFixedSize !== 0) { throw Error(`size ${size} is not multiple of element fixedSize ${elementFixedSize}`); } const length = size / elementFixedSize; assertValidArrayLength(length, arrayProps); const offsets = new Uint32Array(length); for (let i = 0; i < length; i++) { offsets[i] = i * elementFixedSize; } return offsets; } function readVariableOffsetsProgressiveList(data, start, size) { if (size === 0) { return new Uint32Array(0); } if (size < 4) { throw Error(`Variable length list data is too short ${size}`); } const firstOffset = data.getUint32(start, true); if (firstOffset % 4 !== 0) { throw Error(`First offset must be a multiple of 4, got ${firstOffset}`); } if (firstOffset > size) { throw Error(`First offset out of bounds ${firstOffset} > ${size}`); } const length = firstOffset / 4; if (length === 0) { throw Error("Variable length list first offset must be greater than zero"); } const offsets = new Uint32Array(length); offsets[0] = firstOffset; for (let i = 1; i < length; i++) { const offset = data.getUint32(start + i * 4, true); if (offset < offsets[i - 1]) { throw Error(`Offsets must be increasing ${offset} < ${offsets[i - 1]}`); } if (offset > size) { throw Error(`Offset out of bounds ${offset} > ${size}`); } offsets[i] = offset; } return offsets; } //# sourceMappingURL=progressiveList.js.map