@chainsafe/ssz
Version:
Simple Serialize
851 lines • 34.2 kB
JavaScript
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