forked from vega/vega-lite
/
dataflow.ts
201 lines (167 loc) · 4.69 KB
/
dataflow.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import {DataSourceType} from '../../data';
import * as log from '../../log';
import {Dict, uniqueId} from '../../util';
/**
* 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<string>;
/**
* Set of fields that are being created by this node.
*/
public abstract producedFields(): Set<string>;
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<number>
) {
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<string>();
}
public producedFields() {
return new Set<string>();
}
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;
}
}