Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/ssz/src/view/arrayBasic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,18 @@ export class ArrayBasicTreeView<ElementType extends BasicType<unknown>> extends

/**
* Get all values of this array as Basic element type values, from index zero to `this.length - 1`
* @param values optional output parameter, if is provided it must be an array of the same length as this array
*/
getAll(): ValueOf<ElementType>[] {
getAll(values?: ValueOf<ElementType>[]): ValueOf<ElementType>[] {
if (values && values.length !== this.length) {
throw Error(`Expected ${this.length} values, got ${values.length}`);
}
const length = this.length;
const chunksNode = this.type.tree_getChunksNode(this.node);
const chunkCount = Math.ceil(length / this.type.itemsPerChunk);
const leafNodes = getNodesAtDepth(chunksNode, this.type.chunkDepth, 0, chunkCount) as LeafNode[];

const values = new Array<ValueOf<ElementType>>(length);
values = values ?? new Array<ValueOf<ElementType>>(length);
const itemsPerChunk = this.type.itemsPerChunk; // Prevent many access in for loop below
const lenFullNodes = Math.floor(length / itemsPerChunk);
const remainder = length % itemsPerChunk;
Expand Down
16 changes: 12 additions & 4 deletions packages/ssz/src/view/arrayComposite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,16 @@ export class ArrayCompositeTreeView<
* Returns an array of views of all elements in the array, from index zero to `this.length - 1`.
* The returned views don't have a parent hook to this View's Tree, so changes in the returned views won't be
* propagated upwards. To get linked element Views use `this.get()`
* @param views optional output parameter, if is provided it must be an array of the same length as this array
*/
getAllReadonly(): CompositeView<ElementType>[] {
getAllReadonly(views?: CompositeView<ElementType>[]): CompositeView<ElementType>[] {
if (views && views.length !== this.length) {
throw Error(`Expected ${this.length} views, got ${views.length}`);
}
const length = this.length;
const chunksNode = this.type.tree_getChunksNode(this.node);
const nodes = getNodesAtDepth(chunksNode, this.type.chunkDepth, 0, length);
const views = new Array<CompositeView<ElementType>>(length);
views = views ?? new Array<CompositeView<ElementType>>(length);
for (let i = 0; i < length; i++) {
// TODO: Optimize
views[i] = this.type.elementType.getView(new Tree(nodes[i]));
Expand All @@ -90,12 +94,16 @@ export class ArrayCompositeTreeView<
* Returns an array of values of all elements in the array, from index zero to `this.length - 1`.
* The returned values are not Views so any changes won't be propagated upwards.
* To get linked element Views use `this.get()`
* @param values optional output parameter, if is provided it must be an array of the same length as this array
*/
getAllReadonlyValues(): ValueOf<ElementType>[] {
getAllReadonlyValues(values?: ValueOf<ElementType>[]): ValueOf<ElementType>[] {
if (values && values.length !== this.length) {
throw Error(`Expected ${this.length} values, got ${values.length}`);
}
const length = this.length;
const chunksNode = this.type.tree_getChunksNode(this.node);
const nodes = getNodesAtDepth(chunksNode, this.type.chunkDepth, 0, length);
const values = new Array<ValueOf<ElementType>>(length);
values = values ?? new Array<ValueOf<ElementType>>(length);
for (let i = 0; i < length; i++) {
values[i] = this.type.elementType.tree_toValue(nodes[i]);
}
Expand Down
8 changes: 6 additions & 2 deletions packages/ssz/src/viewDU/arrayBasic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,12 @@ export class ArrayBasicTreeViewDU<ElementType extends BasicType<unknown>> extend

/**
* Get all values of this array as Basic element type values, from index zero to `this.length - 1`
* @param values optional output parameter, if is provided it must be an array of the same length as this array
*/
getAll(): ValueOf<ElementType>[] {
getAll(values?: ValueOf<ElementType>[]): ValueOf<ElementType>[] {
if (values && values.length !== this._length) {
throw Error(`Expected ${this._length} values, got ${values.length}`);
}
if (!this.nodesPopulated) {
const nodesPrev = this.nodes;
const chunksNode = this.type.tree_getChunksNode(this.node);
Expand All @@ -125,7 +129,7 @@ export class ArrayBasicTreeViewDU<ElementType extends BasicType<unknown>> extend
this.nodesPopulated = true;
}

const values = new Array<ValueOf<ElementType>>(this._length);
values = values ?? new Array<ValueOf<ElementType>>(this._length);
const itemsPerChunk = this.type.itemsPerChunk; // Prevent many access in for loop below
const lenFullNodes = Math.floor(this._length / itemsPerChunk);
const remainder = this._length % itemsPerChunk;
Expand Down
38 changes: 34 additions & 4 deletions packages/ssz/src/viewDU/arrayComposite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,31 +147,61 @@ export class ArrayCompositeTreeViewDU<
/**
* 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.
* @param views optional output parameter, if is provided it must be an array of the same length as this array
*/
getAllReadonly(): CompositeViewDU<ElementType>[] {
getAllReadonly(views?: CompositeViewDU<ElementType>[]): CompositeViewDU<ElementType>[] {
if (views && views.length !== this._length) {
throw Error(`Expected ${this._length} views, got ${views.length}`);
}
this.populateAllOldNodes();

const views = new Array<CompositeViewDU<ElementType>>(this._length);
views = views ?? new Array<CompositeViewDU<ElementType>>(this._length);
for (let i = 0; i < this._length; i++) {
// this will get pending change first, if not it will get from the `this.nodes` array
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: (viewDU: CompositeViewDU<ElementType>, index: number) => void): void {
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(): ValueOf<ElementType>[] {
getAllReadonlyValues(values?: ValueOf<ElementType>[]): ValueOf<ElementType>[] {
if (values && values.length !== this._length) {
throw Error(`Expected ${this._length} values, got ${values.length}`);
}
this.populateAllNodes();

const values = new Array<ValueOf<ElementType>>(this._length);
values = values ?? new Array<ValueOf<ElementType>>(this._length);
for (let i = 0; i < this._length; i++) {
values[i] = this.type.elementType.tree_toValue(this.nodes[i]);
}
return values;
}

/**
* Apply `fn` to each value in the array
*/
forEachValue(fn: (value: ValueOf<ElementType>, index: number) => void): void {
this.populateAllNodes();
for (let i = 0; i < this._length; i++) {
fn(this.type.elementType.tree_toValue(this.nodes[i]), i);
}
}

/**
* When we need to compute HashComputations (hcByLevel != null):
* - if old _rootNode is hashed, then only need to put pending changes to hcByLevel
Expand Down
39 changes: 35 additions & 4 deletions packages/ssz/test/unit/byType/listComposite/tree.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {describe, it, expect, beforeEach} from "vitest";
import {
ByteVectorType,
CompositeView,
ContainerNodeStructType,
ContainerType,
Expand Down Expand Up @@ -354,23 +355,53 @@ describe("ListCompositeType ViewDU batchHashTreeRoot", () => {
}
});

describe("ListCompositeType getAllReadOnly - no commit", () => {
it("getAllReadOnly() without commit", () => {
describe("ListCompositeType", () => {
let listView: ListCompositeTreeViewDU<ByteVectorType>;

beforeEach(() => {
const listType = new ListCompositeType(ssz.Root, 1024);
const listLength = 2;
const list = Array.from({length: listLength}, (_, i) => Buffer.alloc(32, i));
const listView = listType.toViewDU(list);
listView = listType.toViewDU(list);
expect(listView.getAllReadonly()).to.deep.equal(list);
});

it("getAllReadOnly()", () => {
// modify
listView.set(0, Buffer.alloc(32, 1));
// push
listView.push(Buffer.alloc(32, 1));

// getAllReadOnly() without commit, now all items should be the same
expect(listView.getAllReadonly()).to.deep.equal(Array.from({length: 3}, () => Buffer.alloc(32, 1)));
const all = listView.getAllReadonly();
expect(all).to.deep.equal(Array.from({length: 3}, () => Buffer.alloc(32, 1)));

const out = new Array<Buffer>(3).fill(Buffer.alloc(32, 0));
// getAllReadonly() with "out" parameter of the same length
const all2 = listView.getAllReadonly(out);
expect(all2 === out).to.be.true;
expect(out).to.deep.equal(all);

// getAllReadOnlyValues() will throw
expect(() => listView.getAllReadonlyValues()).toThrow("Must commit changes before reading all nodes");
});

it("forEach()", () => {
listView.forEach((item, i) => expect(item).to.deep.equal(Buffer.alloc(32, i)));
});

it("getAllReadonlyValues()", () => {
// no param
expect(listView.getAllReadonlyValues()).to.deep.equal(Array.from({length: 2}, (_, i) => Buffer.alloc(32, i)));
const out = new Array<Buffer>(2).fill(Buffer.alloc(32, 0));

// with "out" param
const all = listView.getAllReadonlyValues(out);
expect(all === out).to.be.true;
expect(out).to.be.deep.equal(Array.from({length: 2}, (_, i) => Buffer.alloc(32, i)));
});

it("forEachValue()", () => {
listView.forEachValue((item, i) => expect(item).to.deep.equal(Buffer.alloc(32, i)));
});
});