123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.GraphState = exports.DummyListener = exports.FanOutListener = void 0;
- const delta_1 = require("./delta");
- const delta_2 = require("./delta");
- // A 'proxy' GraphStateListener that multicasts graph state operations to a bunch of GraphStateListeners.
- class FanOutListener {
- constructor(listeners) {
- this.listeners = listeners;
- }
- createNode(ns) {
- this.listeners.forEach(m => m.createNode(ns));
- }
- createValue(vs) {
- this.listeners.forEach(m => m.createValue(vs));
- }
- deleteNode(id) {
- this.listeners.forEach(m => m.deleteNode(id));
- }
- deleteValue(value) {
- this.listeners.forEach(m => m.deleteValue(value));
- }
- createLinkToNode(sourceId, label, targetId) {
- this.listeners.forEach(m => m.createLinkToNode(sourceId, label, targetId));
- }
- createLinkToValue(sourceId, label, targetValue) {
- this.listeners.forEach(m => m.createLinkToValue(sourceId, label, targetValue));
- }
- deleteLink(sourceId, label) {
- this.listeners.forEach(m => m.deleteLink(sourceId, label));
- }
- }
- exports.FanOutListener = FanOutListener;
- class DummyListener {
- createNode(ns) { }
- createValue(vs) { }
- deleteNode(id) { }
- deleteValue(value) { }
- createLinkToNode(sourceId, label, targetId) { }
- createLinkToValue(sourceId, label, targetValue) { }
- deleteLink(sourceId, label) { }
- }
- exports.DummyListener = DummyListener;
- const DUMMY = new DummyListener();
- // Helper
- function removeFromArray(arr, cb) {
- arr.splice(arr.findIndex(cb), 1);
- }
- class Common {
- constructor() {
- // For every currently incoming edge, the label of the edge, the delta that set the edge, and the source of the edge.
- this.currentlyIncoming = [];
- // For every previously incoming edge, the delta that set the edge to point somewhere else.
- this.previouslyIncoming = new Set();
- }
- addIncoming(label, delta, srcState, listener) {
- this.currentlyIncoming.push([label, delta, srcState]);
- }
- noLongerIncoming(overwritten, overwriter, listener) {
- removeFromArray(this.currentlyIncoming, ([_, d]) => d === overwritten);
- this.previouslyIncoming.add(overwriter);
- }
- // only called when undoing a Delta
- unAddIncoming(delta, listener) {
- removeFromArray(this.currentlyIncoming, ([_, d]) => d === delta);
- }
- // only called when undoing a Delta
- incomingAgain(label, unOverwritten, srcState, unOverwriter, listener) {
- this.currentlyIncoming.push([label, unOverwritten, srcState]);
- this.previouslyIncoming.delete(unOverwriter);
- }
- getIncomingEdges() {
- return this.currentlyIncoming.map(([label, _, srcState]) => [label, srcState]);
- }
- getReads(label) {
- return [];
- }
- // idempotent - may create some new deltas but does not execute them
- getIncomingEdgeDependenciesForDelete(registry) {
- const newDeltas = this.currentlyIncoming.map(([label, u, ns]) => {
- return registry.newEdgeUpdate(u.overwrite(), null, [], ns.getReads(label).slice());
- });
- return [[...this.previouslyIncoming].concat(newDeltas), newDeltas];
- }
- }
- // In order to edit a graph, we must know what operations most recently "touched" every node, and every edge. This is because new edit operations can depend on earlier operations (that they overwrite).
- // This class captures, for a single node, a set of most-recent operations. It also has methods for editing the node. These methods are "pure" (they have no side-effects): they only return Deltas that capture the change. The change doesn't happen until those Deltas are (re)played, with GraphState.
- class NodeState extends Common {
- constructor(creation) {
- super();
- this.type = "node";
- // For every outgoing edge, the Delta that set this edge to its current value
- this.outgoingDeltas = new Map();
- // For every outgoing edge, the set of deltas that read the value of this edge.
- // A new EdgeUpdate of this edge must depend ("after") on all these reads.
- // Careful: the values (which are arrays) are updated in-place as GraphState (un)executes deltas!
- this.outgoingReads = new Map();
- // All currently outgoing edges. Edges that were set to null will not be part of this mapping.
- this.outgoing = new Map();
- this.isDeleted = false; // has the node been deleted?
- this.creation = creation;
- }
- isNode(uuid) {
- return this.creation.id == uuid;
- }
- isValue(value) {
- return false;
- }
- asTarget() {
- return this.creation;
- }
- createLinkTo(sourceId, label, listener) {
- listener.createLinkToNode(sourceId, label, this.creation.id);
- }
- getReads(label) {
- return this.outgoingReads.get(label) || [];
- }
- // Has no side effects - instead returns the deltas that capture the creation or update of the given outgoing edge
- getDeltaForSetEdge(registry, label, target, reads = []) {
- const previousEdgeUpdate = this.outgoingDeltas.get(label);
- if (previousEdgeUpdate === undefined) {
- return registry.newEdgeUpdate(this.creation.createOutgoingEdge(label), target, reads, []);
- }
- else {
- return registry.newEdgeUpdate(previousEdgeUpdate.overwrite(), target, reads, this.getReads(label).slice());
- }
- }
- // Has no side effects - instead returns the deltas that capture the deletion of this node (and its incoming+outgoing edges)
- getDeltasForDelete(registry) {
- // set all incoming edges to 'null' (if they aren't already):
- const [afterTgt, newDeltas] = this.getIncomingEdgeDependenciesForDelete(registry);
- console.log({ afterTgt });
- // set all outgoing edges to 'null':
- const afterSrc = [...this.outgoingDeltas.values()].map(u => {
- return registry.newEdgeUpdate(u.overwrite(), null, [], this.getReads(u.overwrites.label).slice());
- });
- // console.log("deleting", this.creation.id, {afterTgt, afterSrc});
- const nodeDeletion = registry.newNodeDeletion(this.creation, afterSrc, afterTgt);
- return [...newDeltas, ...afterSrc, nodeDeletion];
- }
- }
- class ValueState extends Common {
- constructor(value) {
- super();
- this.type = "value";
- this.shown = false; // does a (visible) node currently exist for this value?
- this.value = value;
- }
- isNode(uuid) {
- return false;
- }
- isValue(value) {
- return this.value === value;
- }
- asTarget() {
- return this.value;
- }
- createLinkTo(sourceId, label, listener) {
- listener.createLinkToValue(sourceId, label, this.value);
- }
- addIncoming(label, delta, srcState, listener) {
- super.addIncoming(label, delta, srcState, listener);
- this._showOrHide(listener);
- }
- noLongerIncoming(overwritten, overwriter, listener) {
- super.noLongerIncoming(overwritten, overwriter, listener);
- this._showOrHide(listener);
- }
- // only called when undoing a Delta
- unAddIncoming(delta, listener) {
- super.unAddIncoming(delta, listener);
- this._showOrHide(listener);
- }
- // only called when undoing a Delta
- incomingAgain(label, unOverwritten, srcState, unOverwriter, listener) {
- super.incomingAgain(label, unOverwritten, srcState, unOverwriter, listener);
- this._showOrHide(listener);
- }
- // Value nodes are "always already there", but they are only rendered when they are currently the target of an edge. This function determines whether a value node should be rendered, and creates/deletes the corresponding node using the 'listener'.
- _showOrHide(listener) {
- const willShow = this.currentlyIncoming.length > 0;
- if (!this.shown && willShow) {
- listener.createValue(this);
- }
- else if (this.shown && !willShow) {
- listener.deleteValue(this.value);
- }
- this.shown = willShow;
- }
- getDeltaForSetEdge(registry, label, target, reads = []) {
- // A value cannot be the source of an edge, so we return no deltas.
- throw new Error("Assertion failed: A value cannot be the source of an edge.");
- }
- getDeltasForDelete(registry) {
- const [edgeUnsettings, _] = this.getIncomingEdgeDependenciesForDelete(registry);
- return edgeUnsettings;
- }
- }
- // Executes (primitive) deltas, and updates the graph state accordingly.
- // External representations (e.g., d3) can be kept in sync through GraphStateListener.
- class GraphState {
- constructor() {
- this.nodes = new Map();
- this.values = new Map();
- this.deltasSinceCheckpoint = [];
- // Deltas that are part of current state
- this.deltas = new Set();
- }
- // Stores a snapshot of the current graph state. Kind of like Git stash.
- pushState() {
- this.deltasSinceCheckpoint.push([]);
- }
- // Restores a previously stored graph state snapshot
- popState() {
- const toUnexec = this.deltasSinceCheckpoint.at(-1);
- if (toUnexec === undefined) {
- throw new Error("GraphState: cannot popState(), must pushState() first");
- }
- const result = toUnexec.slice(); // clone this array
- toUnexec.reduceRight((_, d) => { this.unexec(d); return null; }, null);
- this.deltasSinceCheckpoint.pop();
- return result;
- }
- exec(delta, listener = DUMMY) {
- var _a;
- if (delta instanceof delta_2.NodeCreation) {
- this.execNodeCreation(delta, listener);
- }
- else if (delta instanceof delta_2.NodeDeletion) {
- this.execNodeDeletion(delta, listener);
- }
- else if (delta instanceof delta_2.EdgeUpdate) {
- this.execEdgeUpdate(delta, listener);
- }
- else if (delta instanceof delta_1.Transaction) {
- delta.deltas.forEach(d => this.exec(d, listener));
- }
- else {
- throw new Error("Assertion failed: Unexpected delta type");
- }
- (_a = this.deltasSinceCheckpoint.at(-1)) === null || _a === void 0 ? void 0 : _a.push(delta);
- this.deltas.add(delta);
- }
- unexec(delta, listener = DUMMY) {
- this.deltas.delete(delta);
- if (delta instanceof delta_2.NodeCreation) {
- this.unexecNodeCreation(delta, listener);
- }
- else if (delta instanceof delta_2.NodeDeletion) {
- this.unexecNodeDeletion(delta, listener);
- }
- else if (delta instanceof delta_2.EdgeUpdate) {
- this.unexecEdgeUpdate(delta, listener);
- }
- else if (delta instanceof delta_1.Transaction) {
- delta.deltas.reduceRight((_, d) => { this.unexec(d, listener); return null; }, null);
- }
- else {
- throw new Error("Assertion failed: Unexpected delta type");
- }
- const checkpoint = this.deltasSinceCheckpoint.at(-1);
- if (checkpoint !== undefined) {
- const lastDelta = checkpoint.pop();
- if (lastDelta !== delta) {
- throw new Error("GraphState: asymmetrical call to unexec");
- }
- }
- }
- _getEdgeTargetState(target) {
- if (target instanceof delta_2.TargetNode) {
- return this.nodes.get(target.value.id); // may return undefined
- }
- else if (target instanceof delta_2.TargetValue && target.value !== null) {
- return this.getValueState(target.value);
- }
- }
- getValueState(value) {
- let vs = this.values.get(value);
- if (vs === undefined) {
- vs = new ValueState(value);
- this.values.set(value, vs);
- }
- return vs;
- }
- execNodeCreation(delta, listener) {
- // console.log("execNodeCreation", delta)
- const nodeState = new NodeState(delta);
- this.nodes.set(delta.id, nodeState);
- listener.createNode(nodeState);
- }
- unexecNodeCreation(delta, listener) {
- // console.log("unexecNodeCreation", delta)
- this.nodes.delete(delta.id);
- listener.deleteNode(delta.id);
- }
- execNodeDeletion(delta, listener) {
- // console.log("execNodeDeletion", delta)
- const id = delta.node.id;
- const nodeState = this.nodes.get(id);
- if (nodeState === undefined) {
- throw new Error("Assertion failed: deleted node does not exist");
- }
- nodeState.isDeleted = true;
- listener.deleteNode(id);
- }
- unexecNodeDeletion(delta, listener) {
- // restore outgoing links
- const id = delta.node.id;
- const nodeState = this.nodes.get(id);
- if (nodeState === undefined) {
- throw new Error("Assertion failed: deleted node does not exist");
- }
- nodeState.isDeleted = false;
- listener.createNode(nodeState);
- }
- execEdgeUpdate(delta, listener) {
- const edge = delta.overwrites;
- const label = edge.label;
- const sourceId = edge.source.id;
- const sourceState = this.nodes.get(sourceId);
- if (sourceState === undefined) {
- throw new Error(`Assertion failed: Must have sourceState -> cannot execute EdgeUpdate. (${delta.description})`);
- }
- // Remove edge from old target
- if (edge instanceof delta_2.NewEdge) {
- // Nothing was overwritten
- }
- else if (edge instanceof delta_2.ExistingEdge) {
- const overwrittenUpdate = edge.delta;
- const oldTarget = overwrittenUpdate.target;
- const oldTargetState = this._getEdgeTargetState(oldTarget);
- if (oldTargetState === undefined) {
- if (oldTarget.value !== null) {
- console.log("TODO: Check: Possibly an assertion error here.");
- }
- }
- else {
- oldTargetState.noLongerIncoming(overwrittenUpdate, delta, listener);
- listener.deleteLink(sourceId, label);
- }
- }
- // Add edge to new target
- const newTarget = delta.target;
- const newTargetState = this._getEdgeTargetState(newTarget);
- if (newTargetState === undefined) {
- if (newTarget.value !== null) {
- console.log("TODO: Check: Possibly an assertion error here.");
- }
- sourceState.outgoing.delete(label);
- }
- else {
- newTargetState.addIncoming(label, delta, sourceState, listener);
- newTargetState.createLinkTo(sourceId, label, listener);
- sourceState.outgoing.set(label, newTargetState);
- }
- sourceState.outgoingDeltas.set(label, delta);
- // Add reads
- for (const r of delta.reads) {
- const readsNode = this.nodes.get(r.source.id);
- const reads = readsNode.outgoingReads.get(r.label) || (() => {
- const newReads = [];
- readsNode.outgoingReads.set(r.label, newReads);
- return newReads;
- })();
- reads.push(delta);
- }
- }
- unexecEdgeUpdate(delta, listener) {
- const edge = delta.overwrites;
- const label = edge.label;
- const sourceId = edge.source.id;
- const sourceState = this.nodes.get(sourceId);
- if (sourceState === undefined) {
- throw new Error("Assertion failed: Must have sourceState -> cannot un-execute EdgeUpdate.");
- }
- // Remove reads
- for (const r of delta.reads) {
- const readsNode = this.nodes.get(r.source.id);
- const reads = readsNode.outgoingReads.get(r.label) || (() => {
- const newReads = [];
- readsNode.outgoingReads.set(r.label, newReads);
- return newReads;
- })();
- removeFromArray(reads, elem => elem === delta);
- }
- // Remove edge from new target
- const newTarget = delta.target;
- const newTargetState = this._getEdgeTargetState(newTarget);
- if (newTargetState === undefined) {
- if (newTarget.value !== null) {
- console.log("TODO: Check: Possibly an assertion error here.");
- }
- }
- else {
- newTargetState.unAddIncoming(delta, listener);
- listener.deleteLink(sourceId, label);
- }
- // Add edge to old target
- if (edge instanceof delta_2.NewEdge) {
- // Nothing was overwritten
- sourceState.outgoingDeltas.delete(label);
- sourceState.outgoing.delete(label);
- }
- else if (edge instanceof delta_2.ExistingEdge) {
- const overwrittenUpdate = edge.delta;
- const oldTarget = overwrittenUpdate.target;
- const oldTargetState = this._getEdgeTargetState(oldTarget);
- if (oldTargetState === undefined) {
- if (oldTarget.value !== null) {
- console.log("TODO: Check: Possibly an assertion error here.");
- }
- sourceState.outgoing.delete(label);
- }
- else {
- oldTargetState.incomingAgain(label, overwrittenUpdate, sourceState, delta, listener);
- oldTargetState.createLinkTo(sourceId, label, listener);
- sourceState.outgoing.set(label, oldTargetState);
- }
- sourceState.outgoingDeltas.set(label, overwrittenUpdate);
- }
- }
- }
- exports.GraphState = GraphState;
- //# sourceMappingURL=graph_state.js.map
|