graph_state.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.GraphState = exports.DummyListener = exports.FanOutListener = void 0;
  4. const delta_1 = require("./delta");
  5. const delta_2 = require("./delta");
  6. // A 'proxy' GraphStateListener that multicasts graph state operations to a bunch of GraphStateListeners.
  7. class FanOutListener {
  8. constructor(listeners) {
  9. this.listeners = listeners;
  10. }
  11. createNode(ns) {
  12. this.listeners.forEach(m => m.createNode(ns));
  13. }
  14. createValue(vs) {
  15. this.listeners.forEach(m => m.createValue(vs));
  16. }
  17. deleteNode(id) {
  18. this.listeners.forEach(m => m.deleteNode(id));
  19. }
  20. deleteValue(value) {
  21. this.listeners.forEach(m => m.deleteValue(value));
  22. }
  23. createLinkToNode(sourceId, label, targetId) {
  24. this.listeners.forEach(m => m.createLinkToNode(sourceId, label, targetId));
  25. }
  26. createLinkToValue(sourceId, label, targetValue) {
  27. this.listeners.forEach(m => m.createLinkToValue(sourceId, label, targetValue));
  28. }
  29. deleteLink(sourceId, label) {
  30. this.listeners.forEach(m => m.deleteLink(sourceId, label));
  31. }
  32. }
  33. exports.FanOutListener = FanOutListener;
  34. class DummyListener {
  35. createNode(ns) { }
  36. createValue(vs) { }
  37. deleteNode(id) { }
  38. deleteValue(value) { }
  39. createLinkToNode(sourceId, label, targetId) { }
  40. createLinkToValue(sourceId, label, targetValue) { }
  41. deleteLink(sourceId, label) { }
  42. }
  43. exports.DummyListener = DummyListener;
  44. const DUMMY = new DummyListener();
  45. // Helper
  46. function removeFromArray(arr, cb) {
  47. arr.splice(arr.findIndex(cb), 1);
  48. }
  49. class Common {
  50. constructor() {
  51. // For every currently incoming edge, the label of the edge, the delta that set the edge, and the source of the edge.
  52. this.currentlyIncoming = [];
  53. // For every previously incoming edge, the delta that set the edge to point somewhere else.
  54. this.previouslyIncoming = new Set();
  55. }
  56. addIncoming(label, delta, srcState, listener) {
  57. this.currentlyIncoming.push([label, delta, srcState]);
  58. }
  59. noLongerIncoming(overwritten, overwriter, listener) {
  60. removeFromArray(this.currentlyIncoming, ([_, d]) => d === overwritten);
  61. this.previouslyIncoming.add(overwriter);
  62. }
  63. // only called when undoing a Delta
  64. unAddIncoming(delta, listener) {
  65. removeFromArray(this.currentlyIncoming, ([_, d]) => d === delta);
  66. }
  67. // only called when undoing a Delta
  68. incomingAgain(label, unOverwritten, srcState, unOverwriter, listener) {
  69. this.currentlyIncoming.push([label, unOverwritten, srcState]);
  70. this.previouslyIncoming.delete(unOverwriter);
  71. }
  72. getIncomingEdges() {
  73. return this.currentlyIncoming.map(([label, _, srcState]) => [label, srcState]);
  74. }
  75. getReads(label) {
  76. return [];
  77. }
  78. // idempotent - may create some new deltas but does not execute them
  79. getIncomingEdgeDependenciesForDelete(registry) {
  80. const newDeltas = this.currentlyIncoming.map(([label, u, ns]) => {
  81. return registry.newEdgeUpdate(u.overwrite(), null, [], ns.getReads(label).slice());
  82. });
  83. return [[...this.previouslyIncoming].concat(newDeltas), newDeltas];
  84. }
  85. }
  86. // 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).
  87. // 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.
  88. class NodeState extends Common {
  89. constructor(creation) {
  90. super();
  91. this.type = "node";
  92. // For every outgoing edge, the Delta that set this edge to its current value
  93. this.outgoingDeltas = new Map();
  94. // For every outgoing edge, the set of deltas that read the value of this edge.
  95. // A new EdgeUpdate of this edge must depend ("after") on all these reads.
  96. // Careful: the values (which are arrays) are updated in-place as GraphState (un)executes deltas!
  97. this.outgoingReads = new Map();
  98. // All currently outgoing edges. Edges that were set to null will not be part of this mapping.
  99. this.outgoing = new Map();
  100. this.isDeleted = false; // has the node been deleted?
  101. this.creation = creation;
  102. }
  103. isNode(uuid) {
  104. return this.creation.id == uuid;
  105. }
  106. isValue(value) {
  107. return false;
  108. }
  109. asTarget() {
  110. return this.creation;
  111. }
  112. createLinkTo(sourceId, label, listener) {
  113. listener.createLinkToNode(sourceId, label, this.creation.id);
  114. }
  115. getReads(label) {
  116. return this.outgoingReads.get(label) || [];
  117. }
  118. // Has no side effects - instead returns the deltas that capture the creation or update of the given outgoing edge
  119. getDeltaForSetEdge(registry, label, target, reads = []) {
  120. const previousEdgeUpdate = this.outgoingDeltas.get(label);
  121. if (previousEdgeUpdate === undefined) {
  122. return registry.newEdgeUpdate(this.creation.createOutgoingEdge(label), target, reads, []);
  123. }
  124. else {
  125. return registry.newEdgeUpdate(previousEdgeUpdate.overwrite(), target, reads, this.getReads(label).slice());
  126. }
  127. }
  128. // Has no side effects - instead returns the deltas that capture the deletion of this node (and its incoming+outgoing edges)
  129. getDeltasForDelete(registry) {
  130. // set all incoming edges to 'null' (if they aren't already):
  131. const [afterTgt, newDeltas] = this.getIncomingEdgeDependenciesForDelete(registry);
  132. console.log({ afterTgt });
  133. // set all outgoing edges to 'null':
  134. const afterSrc = [...this.outgoingDeltas.values()].map(u => {
  135. return registry.newEdgeUpdate(u.overwrite(), null, [], this.getReads(u.overwrites.label).slice());
  136. });
  137. // console.log("deleting", this.creation.id, {afterTgt, afterSrc});
  138. const nodeDeletion = registry.newNodeDeletion(this.creation, afterSrc, afterTgt);
  139. return [...newDeltas, ...afterSrc, nodeDeletion];
  140. }
  141. }
  142. class ValueState extends Common {
  143. constructor(value) {
  144. super();
  145. this.type = "value";
  146. this.shown = false; // does a (visible) node currently exist for this value?
  147. this.value = value;
  148. }
  149. isNode(uuid) {
  150. return false;
  151. }
  152. isValue(value) {
  153. return this.value === value;
  154. }
  155. asTarget() {
  156. return this.value;
  157. }
  158. createLinkTo(sourceId, label, listener) {
  159. listener.createLinkToValue(sourceId, label, this.value);
  160. }
  161. addIncoming(label, delta, srcState, listener) {
  162. super.addIncoming(label, delta, srcState, listener);
  163. this._showOrHide(listener);
  164. }
  165. noLongerIncoming(overwritten, overwriter, listener) {
  166. super.noLongerIncoming(overwritten, overwriter, listener);
  167. this._showOrHide(listener);
  168. }
  169. // only called when undoing a Delta
  170. unAddIncoming(delta, listener) {
  171. super.unAddIncoming(delta, listener);
  172. this._showOrHide(listener);
  173. }
  174. // only called when undoing a Delta
  175. incomingAgain(label, unOverwritten, srcState, unOverwriter, listener) {
  176. super.incomingAgain(label, unOverwritten, srcState, unOverwriter, listener);
  177. this._showOrHide(listener);
  178. }
  179. // 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'.
  180. _showOrHide(listener) {
  181. const willShow = this.currentlyIncoming.length > 0;
  182. if (!this.shown && willShow) {
  183. listener.createValue(this);
  184. }
  185. else if (this.shown && !willShow) {
  186. listener.deleteValue(this.value);
  187. }
  188. this.shown = willShow;
  189. }
  190. getDeltaForSetEdge(registry, label, target, reads = []) {
  191. // A value cannot be the source of an edge, so we return no deltas.
  192. throw new Error("Assertion failed: A value cannot be the source of an edge.");
  193. }
  194. getDeltasForDelete(registry) {
  195. const [edgeUnsettings, _] = this.getIncomingEdgeDependenciesForDelete(registry);
  196. return edgeUnsettings;
  197. }
  198. }
  199. // Executes (primitive) deltas, and updates the graph state accordingly.
  200. // External representations (e.g., d3) can be kept in sync through GraphStateListener.
  201. class GraphState {
  202. constructor() {
  203. this.nodes = new Map();
  204. this.values = new Map();
  205. this.deltasSinceCheckpoint = [];
  206. // Deltas that are part of current state
  207. this.deltas = new Set();
  208. }
  209. // Stores a snapshot of the current graph state. Kind of like Git stash.
  210. pushState() {
  211. this.deltasSinceCheckpoint.push([]);
  212. }
  213. // Restores a previously stored graph state snapshot
  214. popState() {
  215. const toUnexec = this.deltasSinceCheckpoint.at(-1);
  216. if (toUnexec === undefined) {
  217. throw new Error("GraphState: cannot popState(), must pushState() first");
  218. }
  219. const result = toUnexec.slice(); // clone this array
  220. toUnexec.reduceRight((_, d) => { this.unexec(d); return null; }, null);
  221. this.deltasSinceCheckpoint.pop();
  222. return result;
  223. }
  224. exec(delta, listener = DUMMY) {
  225. var _a;
  226. if (delta instanceof delta_2.NodeCreation) {
  227. this.execNodeCreation(delta, listener);
  228. }
  229. else if (delta instanceof delta_2.NodeDeletion) {
  230. this.execNodeDeletion(delta, listener);
  231. }
  232. else if (delta instanceof delta_2.EdgeUpdate) {
  233. this.execEdgeUpdate(delta, listener);
  234. }
  235. else if (delta instanceof delta_1.Transaction) {
  236. delta.deltas.forEach(d => this.exec(d, listener));
  237. }
  238. else {
  239. throw new Error("Assertion failed: Unexpected delta type");
  240. }
  241. (_a = this.deltasSinceCheckpoint.at(-1)) === null || _a === void 0 ? void 0 : _a.push(delta);
  242. this.deltas.add(delta);
  243. }
  244. unexec(delta, listener = DUMMY) {
  245. this.deltas.delete(delta);
  246. if (delta instanceof delta_2.NodeCreation) {
  247. this.unexecNodeCreation(delta, listener);
  248. }
  249. else if (delta instanceof delta_2.NodeDeletion) {
  250. this.unexecNodeDeletion(delta, listener);
  251. }
  252. else if (delta instanceof delta_2.EdgeUpdate) {
  253. this.unexecEdgeUpdate(delta, listener);
  254. }
  255. else if (delta instanceof delta_1.Transaction) {
  256. delta.deltas.reduceRight((_, d) => { this.unexec(d, listener); return null; }, null);
  257. }
  258. else {
  259. throw new Error("Assertion failed: Unexpected delta type");
  260. }
  261. const checkpoint = this.deltasSinceCheckpoint.at(-1);
  262. if (checkpoint !== undefined) {
  263. const lastDelta = checkpoint.pop();
  264. if (lastDelta !== delta) {
  265. throw new Error("GraphState: asymmetrical call to unexec");
  266. }
  267. }
  268. }
  269. _getEdgeTargetState(target) {
  270. if (target instanceof delta_2.TargetNode) {
  271. return this.nodes.get(target.value.id); // may return undefined
  272. }
  273. else if (target instanceof delta_2.TargetValue && target.value !== null) {
  274. return this.getValueState(target.value);
  275. }
  276. }
  277. getValueState(value) {
  278. let vs = this.values.get(value);
  279. if (vs === undefined) {
  280. vs = new ValueState(value);
  281. this.values.set(value, vs);
  282. }
  283. return vs;
  284. }
  285. execNodeCreation(delta, listener) {
  286. // console.log("execNodeCreation", delta)
  287. const nodeState = new NodeState(delta);
  288. this.nodes.set(delta.id, nodeState);
  289. listener.createNode(nodeState);
  290. }
  291. unexecNodeCreation(delta, listener) {
  292. // console.log("unexecNodeCreation", delta)
  293. this.nodes.delete(delta.id);
  294. listener.deleteNode(delta.id);
  295. }
  296. execNodeDeletion(delta, listener) {
  297. // console.log("execNodeDeletion", delta)
  298. const id = delta.node.id;
  299. const nodeState = this.nodes.get(id);
  300. if (nodeState === undefined) {
  301. throw new Error("Assertion failed: deleted node does not exist");
  302. }
  303. nodeState.isDeleted = true;
  304. listener.deleteNode(id);
  305. }
  306. unexecNodeDeletion(delta, listener) {
  307. // restore outgoing links
  308. const id = delta.node.id;
  309. const nodeState = this.nodes.get(id);
  310. if (nodeState === undefined) {
  311. throw new Error("Assertion failed: deleted node does not exist");
  312. }
  313. nodeState.isDeleted = false;
  314. listener.createNode(nodeState);
  315. }
  316. execEdgeUpdate(delta, listener) {
  317. const edge = delta.overwrites;
  318. const label = edge.label;
  319. const sourceId = edge.source.id;
  320. const sourceState = this.nodes.get(sourceId);
  321. if (sourceState === undefined) {
  322. throw new Error(`Assertion failed: Must have sourceState -> cannot execute EdgeUpdate. (${delta.description})`);
  323. }
  324. // Remove edge from old target
  325. if (edge instanceof delta_2.NewEdge) {
  326. // Nothing was overwritten
  327. }
  328. else if (edge instanceof delta_2.ExistingEdge) {
  329. const overwrittenUpdate = edge.delta;
  330. const oldTarget = overwrittenUpdate.target;
  331. const oldTargetState = this._getEdgeTargetState(oldTarget);
  332. if (oldTargetState === undefined) {
  333. if (oldTarget.value !== null) {
  334. console.log("TODO: Check: Possibly an assertion error here.");
  335. }
  336. }
  337. else {
  338. oldTargetState.noLongerIncoming(overwrittenUpdate, delta, listener);
  339. listener.deleteLink(sourceId, label);
  340. }
  341. }
  342. // Add edge to new target
  343. const newTarget = delta.target;
  344. const newTargetState = this._getEdgeTargetState(newTarget);
  345. if (newTargetState === undefined) {
  346. if (newTarget.value !== null) {
  347. console.log("TODO: Check: Possibly an assertion error here.");
  348. }
  349. sourceState.outgoing.delete(label);
  350. }
  351. else {
  352. newTargetState.addIncoming(label, delta, sourceState, listener);
  353. newTargetState.createLinkTo(sourceId, label, listener);
  354. sourceState.outgoing.set(label, newTargetState);
  355. }
  356. sourceState.outgoingDeltas.set(label, delta);
  357. // Add reads
  358. for (const r of delta.reads) {
  359. const readsNode = this.nodes.get(r.source.id);
  360. const reads = readsNode.outgoingReads.get(r.label) || (() => {
  361. const newReads = [];
  362. readsNode.outgoingReads.set(r.label, newReads);
  363. return newReads;
  364. })();
  365. reads.push(delta);
  366. }
  367. }
  368. unexecEdgeUpdate(delta, listener) {
  369. const edge = delta.overwrites;
  370. const label = edge.label;
  371. const sourceId = edge.source.id;
  372. const sourceState = this.nodes.get(sourceId);
  373. if (sourceState === undefined) {
  374. throw new Error("Assertion failed: Must have sourceState -> cannot un-execute EdgeUpdate.");
  375. }
  376. // Remove reads
  377. for (const r of delta.reads) {
  378. const readsNode = this.nodes.get(r.source.id);
  379. const reads = readsNode.outgoingReads.get(r.label) || (() => {
  380. const newReads = [];
  381. readsNode.outgoingReads.set(r.label, newReads);
  382. return newReads;
  383. })();
  384. removeFromArray(reads, elem => elem === delta);
  385. }
  386. // Remove edge from new target
  387. const newTarget = delta.target;
  388. const newTargetState = this._getEdgeTargetState(newTarget);
  389. if (newTargetState === undefined) {
  390. if (newTarget.value !== null) {
  391. console.log("TODO: Check: Possibly an assertion error here.");
  392. }
  393. }
  394. else {
  395. newTargetState.unAddIncoming(delta, listener);
  396. listener.deleteLink(sourceId, label);
  397. }
  398. // Add edge to old target
  399. if (edge instanceof delta_2.NewEdge) {
  400. // Nothing was overwritten
  401. sourceState.outgoingDeltas.delete(label);
  402. sourceState.outgoing.delete(label);
  403. }
  404. else if (edge instanceof delta_2.ExistingEdge) {
  405. const overwrittenUpdate = edge.delta;
  406. const oldTarget = overwrittenUpdate.target;
  407. const oldTargetState = this._getEdgeTargetState(oldTarget);
  408. if (oldTargetState === undefined) {
  409. if (oldTarget.value !== null) {
  410. console.log("TODO: Check: Possibly an assertion error here.");
  411. }
  412. sourceState.outgoing.delete(label);
  413. }
  414. else {
  415. oldTargetState.incomingAgain(label, overwrittenUpdate, sourceState, delta, listener);
  416. oldTargetState.createLinkTo(sourceId, label, listener);
  417. sourceState.outgoing.set(label, oldTargetState);
  418. }
  419. sourceState.outgoingDeltas.set(label, overwrittenUpdate);
  420. }
  421. }
  422. }
  423. exports.GraphState = GraphState;
  424. //# sourceMappingURL=graph_state.js.map