Browse Source

Implement iterator `next()` method using cursor

master
buttercat1791 1 year ago
parent
commit
7da145fb5e
  1. 180
      src/lib/data_structures/publication_tree.ts

180
src/lib/data_structures/publication_tree.ts

@ -26,12 +26,6 @@ export class PublicationTree implements AsyncIterable<NDKEvent> { @@ -26,12 +26,6 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
*/
private nodes: Map<string, PublicationTreeNode>;
/**
* A map of addresses in the tree to their corresponding lazy-loaded nodes. When a lazy node is
* retrieved, it is added to the {@link PublicationTree.nodes} map.
*/
private lazyNodes: Map<string, Lazy<PublicationTreeNode>> = new Map();
/**
* A map of addresses in the tree to their corresponding events.
*/
@ -117,37 +111,11 @@ export class PublicationTree implements AsyncIterable<NDKEvent> { @@ -117,37 +111,11 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
);
}
const lazyNode = new Lazy<PublicationTreeNode>(async () => {
const [kind, pubkey, dTag] = address.split(':');
const event = await this.ndk.fetchEvent({
kinds: [parseInt(kind)],
authors: [pubkey],
'#d': [dTag],
});
if (!event) {
throw new Error(
`PublicationTree: Event with address ${address} not found.`
parentNode.children!.push(
new Lazy<PublicationTreeNode>(() => this.resolveNode(address, parentNode))
);
}
const node: PublicationTreeNode = {
type: this.getNodeType(event),
address,
parent: parentNode,
children: [],
};
this.nodes.set(address, node);
this.events.set(address, event);
return node;
});
parentNode.children!.push(lazyNode);
this.lazyNodes.set(address, lazyNode);
}
/**
* Retrieves an event from the publication tree.
* @param address The address of the event to retrieve.
@ -168,30 +136,36 @@ export class PublicationTree implements AsyncIterable<NDKEvent> { @@ -168,30 +136,36 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
*/
setBookmark(address: string) {
this.bookmark = address;
this.#cursor.tryMoveTo(address);
}
[Symbol.asyncIterator](): AsyncIterator<NDKEvent> {
this.#cursor.tryMoveTo(this.bookmark);
return this;
}
async next(): Promise<IteratorResult<NDKEvent>> {
// If no bookmark is set, start at the first leaf. Retrieve that first leaf if necessary.
if (!this.bookmark) {
this.bookmark = this.leaves.at(0);
if (this.bookmark) {
const bookmarkEvent = await this.getEvent(this.bookmark);
return { done: false, value: bookmarkEvent! };
while (this.#cursor.target?.type !== PublicationTreeNodeType.Leaf) {
if (await this.#cursor.tryMoveToFirstChild()) {
continue;
}
const firstLeafEvent = await this.depthFirstRetrieve();
this.bookmark = firstLeafEvent!.tagAddress();
return { done: false, value: firstLeafEvent! };
if (await this.#cursor.tryMoveToNextSibling()) {
continue;
}
// TODO: Invoke a funciton to retrieve the next sibling of the bookmark.
if (await this.#cursor.tryMoveToParent()) {
continue;
}
if (this.#cursor.target?.type === PublicationTreeNodeType.Root) {
return { done: true, value: null };
}
}
const event = await this.getEvent(this.#cursor.target!.address);
return { done: false, value: event! };
}
// #region Private Methods
@ -252,42 +226,37 @@ export class PublicationTree implements AsyncIterable<NDKEvent> { @@ -252,42 +226,37 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
return null;
}
private async getNextSibling(address: string): Promise<NDKEvent | null> {
if (!this.leaves.includes(address)) {
throw new Error(
`PublicationTree: Address ${address} is not a leaf. Cannot retrieve next sibling.`
);
}
let currentNode = this.nodes.get(address);
if (!currentNode) {
return null;
}
private async resolveNode(
address: string,
parentNode: PublicationTreeNode
): Promise<PublicationTreeNode> {
const [kind, pubkey, dTag] = address.split(':');
const event = await this.ndk.fetchEvent({
kinds: [parseInt(kind)],
authors: [pubkey],
'#d': [dTag],
});
let parent = currentNode.parent;
if (!parent) {
if (!event) {
throw new Error(
`PublicationTree: Address ${address} has no parent. Cannot retrieve next sibling.`
`PublicationTree: Event with address ${address} not found.`
);
}
// TODO: Handle the case where the current node is the last leaf.
let nextSibling: PublicationTreeNode | null = null;
do {
const siblings: Lazy<PublicationTreeNode>[] = parent!.children!;
const currentIndex = siblings.findIndex(async sibling => (await sibling.value()).address === currentNode!.address);
nextSibling = (await siblings.at(currentIndex + 1)?.value()) ?? null;
const childAddresses = event.tags.filter(tag => tag[0] === 'a').map(tag => tag[1]);
const node: PublicationTreeNode = {
type: this.getNodeType(event),
address,
parent: parentNode,
children: childAddresses.map(
address => new Lazy<PublicationTreeNode>(() => this.resolveNode(address, node))
),
};
// If the next sibling has children, it is not a leaf.
if ((nextSibling?.children?.length ?? 0) > 0) {
currentNode = (await nextSibling!.children!.at(0)!.value())!;
parent = currentNode.parent;
nextSibling = null;
}
} while (nextSibling == null);
this.nodes.set(address, node);
this.events.set(address, event);
return this.getEvent(nextSibling!.address);
return node;
}
private getNodeType(event: NDKEvent): PublicationTreeNodeType {
@ -314,49 +283,52 @@ export class PublicationTree implements AsyncIterable<NDKEvent> { @@ -314,49 +283,52 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
// #region Iteration Cursor
Cursor = class {
private tree: PublicationTree;
private currentNode: PublicationTreeNode | null | undefined;
#cursor = new class {
target: PublicationTreeNode | null | undefined;
constructor(tree: PublicationTree, currentNode: PublicationTreeNode | null = null) {
this.tree = tree;
#tree: PublicationTree;
if (!currentNode) {
this.currentNode = this.tree.bookmark
? this.tree.nodes.get(this.tree.bookmark)
: null;
constructor(tree: PublicationTree) {
this.#tree = tree;
}
async tryMoveTo(address?: string) {
if (!address) {
const startEvent = await this.#tree.depthFirstRetrieve();
this.target = this.#tree.nodes.get(startEvent!.tagAddress());
} else {
this.target = this.#tree.nodes.get(address);
}
async moveToFirstChild(): Promise<boolean> {
if (!this.currentNode) {
throw new Error("Cursor: Current node is null or undefined.");
if (!this.target) {
return false;
}
const hasChildren = (this.currentNode.children?.length ?? 0) > 0;
const isLeaf = this.tree.leaves.includes(this.currentNode.address);
return true;
}
if (!hasChildren && isLeaf) {
return false;
async tryMoveToFirstChild(): Promise<boolean> {
if (!this.target) {
throw new Error("Cursor: Target node is null or undefined.");
}
if (!hasChildren && !isLeaf) {
// TODO: Fetch any missing children, then return the first child.
if (this.target.type === PublicationTreeNodeType.Leaf) {
return false;
}
this.currentNode = (await this.currentNode.children?.at(0)?.value())!;
this.target = (await this.target.children?.at(0)?.value())!;
return true;
}
async moveToNextSibling(): Promise<boolean> {
if (!this.currentNode) {
throw new Error("Cursor: Current node is null or undefined.");
async tryMoveToNextSibling(): Promise<boolean> {
if (!this.target) {
throw new Error("Cursor: Target node is null or undefined.");
}
const parent = this.currentNode.parent;
const parent = this.target.parent;
const siblings = parent?.children;
const currentIndex = siblings?.findIndex(async sibling =>
(await sibling.value()).address === this.currentNode!.address
(await sibling.value()).address === this.target!.address
);
const nextSibling = (await siblings?.at(currentIndex! + 1)?.value()) ?? null;
@ -364,24 +336,24 @@ export class PublicationTree implements AsyncIterable<NDKEvent> { @@ -364,24 +336,24 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
return false;
}
this.currentNode = nextSibling;
this.target = nextSibling;
return true;
}
moveToParent(): boolean {
if (!this.currentNode) {
throw new Error("Cursor: Current node is null or undefined.");
tryMoveToParent(): boolean {
if (!this.target) {
throw new Error("Cursor: Target node is null or undefined.");
}
const parent = this.currentNode.parent;
const parent = this.target.parent;
if (!parent) {
return false;
}
this.currentNode = parent;
this.target = parent;
return true;
}
};
}(this);
// #endregion
}
Loading…
Cancel
Save