import {DataSourceType} from '../../data.js'; import * as log from '../../log/index.js'; import {Dict, uniqueId} from '../../util.js'; /** * A node in the dataflow tree. */ export abstract class DataFlowNode { private _children: DataFlowNode[] = []; private _parent: DataFlowNode = null; protected _hash: string | number; constructor( parent: DataFlowNode, public readonly debugName?: string, ) { if (parent) { this.parent = parent; } } /** * Clone this node with a deep copy but don't clone links to children or parents. */ public clone(): DataFlowNode { throw new Error('Cannot clone node'); } /** * Return a hash of the node. */ public abstract hash(): string | number; /** * Set of fields that this node depends on. */ public abstract dependentFields(): Set; /** * Set of fields that are being created by this node. */ public abstract producedFields(): Set; get parent() { return this._parent; } /** * Set the parent of the node and also add this node to the parent's children. */ set parent(parent: DataFlowNode) { this._parent = parent; if (parent) { parent.addChild(this); } } get children() { return this._children; } public numChildren() { return this._children.length; } public addChild(child: DataFlowNode, loc?: number) { // do not add the same child twice if (this._children.includes(child)) { log.warn(log.message.ADD_SAME_CHILD_TWICE); return; } if (loc !== undefined) { this._children.splice(loc, 0, child); } else { this._children.push(child); } } public removeChild(oldChild: DataFlowNode) { const loc = this._children.indexOf(oldChild); this._children.splice(loc, 1); return loc; } /** * Remove node from the dataflow. */ public remove() { let loc = this._parent.removeChild(this); for (const child of this._children) { // do not use the set method because we want to insert at a particular location child._parent = this._parent; this._parent.addChild(child, loc++); } } /** * Insert another node as a parent of this node. */ public insertAsParentOf(other: DataFlowNode) { const parent = other.parent; parent.removeChild(this); this.parent = parent; other.parent = this; } public swapWithParent() { const parent = this._parent; const newParent = parent.parent; // reconnect the children for (const child of this._children) { child.parent = parent; } // remove old links this._children = []; // equivalent to removing every child link one by one parent.removeChild(this); const loc = parent.parent.removeChild(parent); // swap two nodes but maintain order in children this._parent = newParent; newParent.addChild(this, loc); parent.parent = this; } } export class OutputNode extends DataFlowNode { private _source: string; private _name: string; public clone(): this { const cloneObj = new (this.constructor as any)(); cloneObj.debugName = `clone_${this.debugName}`; cloneObj._source = this._source; cloneObj._name = `clone_${this._name}`; cloneObj.type = this.type; cloneObj.refCounts = this.refCounts; cloneObj.refCounts[cloneObj._name] = 0; return cloneObj; } /** * @param source The name of the source. Will change in assemble. * @param type The type of the output node. * @param refCounts A global ref counter map. */ constructor( parent: DataFlowNode, source: string, public readonly type: DataSourceType, private readonly refCounts: Dict, ) { super(parent, source); this._source = this._name = source; if (this.refCounts && !(this._name in this.refCounts)) { this.refCounts[this._name] = 0; } } public dependentFields() { return new Set(); } public producedFields() { return new Set(); } public hash() { if (this._hash === undefined) { this._hash = `Output ${uniqueId()}`; } return this._hash; } /** * Request the datasource name and increase the ref counter. * * During the parsing phase, this will return the simple name such as 'main' or 'raw'. * It is crucial to request the name from an output node to mark it as a required node. * If nobody ever requests the name, this datasource will not be instantiated in the assemble phase. * * In the assemble phase, this will return the correct name. */ public getSource() { this.refCounts[this._name]++; return this._source; } public isRequired(): boolean { return !!this.refCounts[this._name]; } public setSource(source: string) { this._source = source; } }