tracker: testing tree thing

This commit is contained in:
nick-delirium 2025-04-16 09:26:02 +02:00
parent b91f5df89f
commit a9701ba57d
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
4 changed files with 140 additions and 2 deletions

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker",
"description": "The OpenReplay tracker main package",
"version": "16.1.3",
"version": "16.1.2-beta.2",
"keywords": [
"logging",
"replay"

View file

@ -98,6 +98,13 @@ export default class Nodes {
return id
}
unregisterNodeById(id: number): void {
const node = this.nodes.get(id)
if (node) {
this.unregisterNode(node)
}
}
cleanTree() {
// sadly we keep empty items in array here resulting in some memory still being used
// but its still better than keeping dead nodes or undef elements

View file

@ -21,6 +21,7 @@ import {
hasTag,
isCommentNode,
} from '../guards.js'
import vElTree from './vTree.js'
const iconCache = {}
const svgUrlCache = {}
@ -181,6 +182,11 @@ enum RecentsType {
}
export default abstract class Observer {
/** object tree where key is node id, value is null if it has no children, or object with same structure */
private readonly vTree = new vElTree((id: number) => {
this.app.nodes.unregisterNodeById(id)
this.app.send(RemoveNode(id))
})
private readonly observer: MutationObserver
private readonly commited: Array<boolean | undefined> = []
private readonly recents: Map<number, RecentsType> = new Map()
@ -412,6 +418,7 @@ export default abstract class Observer {
if (id !== undefined && this.recents.get(id) === RecentsType.Removed) {
// Sending RemoveNode only for parent to maintain
this.app.send(RemoveNode(id))
this.vTree.removeNode(id)
// Unregistering all the children in order to clear the memory
const walker = document.createTreeWalker(
@ -506,7 +513,7 @@ export default abstract class Observer {
;(el as HTMLElement | SVGElement).style.width = `${width}px`
;(el as HTMLElement | SVGElement).style.height = `${height}px`
}
this.vTree.addNode(id, parentID ?? null)
this.app.send(CreateElementNode(id, parentID, index, el.tagName, isSVGElement(node)))
}
for (let i = 0; i < el.attributes.length; i++) {
@ -514,6 +521,7 @@ export default abstract class Observer {
this.sendNodeAttribute(id, el, attr.nodeName, attr.value)
}
} else if (isTextNode(node)) {
this.vTree.addNode(id, parentID ?? null)
// for text node id != 0, hence parentID !== undefined and parent is Element
this.app.send(CreateTextNode(id, parentID as number, index))
this.sendNodeData(id, parent as Element, node.data)
@ -586,5 +594,6 @@ export default abstract class Observer {
disconnect(): void {
this.observer.disconnect()
this.clear()
this.vTree.clearAll()
}
}

View file

@ -0,0 +1,122 @@
export default class VirtualNodeTree {
tree: any = { 0: null }
nodeParents: any = { 0: null}
constructor(
private readonly onRemoveNode: (id: number) => void
) {}
addNode(id: number, parentId: number | null = null) {
this.nodeParents[id] = parentId;
console.log(id, parentId, this.tree, this.nodeParents)
if (parentId === null) {
this.tree[id] = null;
} else {
const parentNode = this.findNode(parentId);
if (parentNode === null) {
this.updateNode(parentId, {});
}
this.findNode(parentId)[id] = null;
}
}
removeNode(id: number) {
console.log('del', id, this)
if (!(id in this.nodeParents)) {
// throw new Error(`Node ${id} doesn't exist`); ? since its just for tracking, nothing
return;
}
const childrenIds = Object.keys(this.nodeParents).filter(
nodeId => this.nodeParents[nodeId] === id
);
for (const childId of childrenIds) {
this.removeNode(parseInt(childId));
}
const parentId = this.nodeParents[id];
if (parentId === null) {
delete this.tree[id];
} else {
const parentNode = this.findNode(parentId);
delete parentNode[id];
this.onRemoveNode(id);
if (Object.keys(parentNode).length === 0) {
this.updateNode(parentId, null);
}
}
delete this.nodeParents[id];
}
getPath(id: number) {
if (!(id in this.nodeParents) && id !== 0) {
// throw new Error(`Node ${id} doesn't exist`);
return;
}
const path: any[] = [];
let currentId = id;
while (currentId !== null) {
path.unshift(currentId);
currentId = this.nodeParents[currentId];
}
return path.join('.');
}
findNode(id: number) {
const path = this.getPath(id);
if (!path) return;
return this.getNodeByPath(path);
}
updateNode(id: number, value: any) {
const path = this.getPath(id);
if (!path) return;
this.setNodeByPath(path, value);
}
getNodeByPath(path: string) {
const ids = path.split('.');
let node = this.tree;
for (const id of ids) {
node = node[id];
if (node === undefined) return null;
}
return node;
}
setNodeByPath(path: string, value: number) {
const ids = path.split('.');
let node = this.tree;
for (let i = 0; i < ids.length - 1; i++) {
node = node[ids[i]];
}
node[ids[ids.length - 1]] = value;
}
clearAll = () => {
this.tree = {};
this.nodeParents = {};
}
}
// TODO add tests
// const tree = new VirtualNodeTree();
// tree.addNode(0);
// tree.addNode(1, 0);
// tree.addNode(2, 1);
// tree.addNode(3, 1);
// tree.addNode(4, 3);
// console.log({ ...tree.tree });
// tree.removeNode(1)
// console.log(tree.tree)