JSFiddle - React, Tailwind, and code Playground
by Josh Pullen
JavaScript
/* Node values are recomputed when their definition changes
or when one of their dependencies from last run changes. */
class Node {
constructor(data, computeValue, graph = null) {
this.data = data;
this.computeValue = computeValue;
this.dependencies = [];
this.cachedValue = undefined;
this.listeners = {
update: []
};
this.graph = graph;
}
value() {
if (!this.graph) {
console.log("Cannot compute value of node without graph");
return null;
}
if (this.cachedValue !== undefined) {
return this.cachedValue;
}
const computedValue = this.computeValue(this.data, this.accessNodeValues());
this.cachedValue = computedValue;
return computedValue;
}
accessNodeValues() {
return new Proxy(this.graph.nodes, {
get: (obj, prop) => {
const node = obj[prop];
node.on("update", this.invalidateCache.bind(this));
return node;
}
});
}
invalidateCache() {
this.cachedValue = undefined;
}
update(data) {
this.data = { ...this.data, ...data };
this.invalidateCache();
this._fireEvent("update");
}
on(event, callback) {
this.listeners[event].push(callback);
}
_fireEvent(event) {
this.listeners[event].forEach(callback => callback());
this.listeners[event] = [];
}
}
class Graph {
constructor(nodes) {
this.nodes = nodes;
for (const node of Object.values(this.nodes)) {
node.graph = this;
}
}
}
const graph = new Graph({
nodeA: new Node(
{
name: "nodeA",
number: 5
},
(data) => data.number
),
nodeB: new Node(
{
name: "nodeB",
number: 15
},
(data) => data.number
),
nodeC: new Node(
{
name: "nodeC",
target: "nodeA"
},
(data, nodes) => nodes[data.target].value() ** 2
),
nodeD: new Node(
{
name: "nodeD",
},
(data, nodes) => nodes.nodeC.value() +...