graph_state.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. // NodeJS libraries
  2. import {inspect} from "util";
  3. type PrimitiveType = string | number | boolean;
  4. // This class is here to distinguish UUIDs from ordinary strings
  5. export class UUID {
  6. uuid: PrimitiveType;
  7. constructor(uuid: PrimitiveType) {
  8. this.uuid = uuid;
  9. }
  10. [inspect.custom](depth: number, options: object) {
  11. return "UUID{" + inspect(this.uuid, options) + "}"
  12. }
  13. }
  14. type NodeId = PrimitiveType | UUID;
  15. function nodeIdsEqual(a: NodeId, b: NodeId) {
  16. if (a === b) return true;
  17. if (a instanceof UUID && b instanceof UUID) {
  18. return a.uuid === b.uuid;
  19. }
  20. }
  21. // In- and outgoing edges of a node.
  22. // This is the only place where edges are recorded.
  23. // Every edge corresponds to one entry in the source's 'outgoing', and one entry in the target's 'incoming'.
  24. class Node {
  25. outgoing: Map<string, NodeId>;
  26. incoming: Array<{label: string, srcId: NodeId}>;
  27. constructor() {
  28. this.outgoing = new Map();
  29. this.incoming = [];
  30. }
  31. }
  32. // Helper class. Stores all nodes.
  33. class NodeMap {
  34. // ordinary nodes: they are created and deleted
  35. nodes: Map<PrimitiveType, Node>;
  36. // value nodes: we pretend that they always already exist
  37. values: Map<PrimitiveType, Node>;
  38. constructor() {
  39. this.nodes = new Map();
  40. this.values = new Map();
  41. }
  42. getOptional(id: NodeId): Node | undefined {
  43. if (id instanceof UUID) {
  44. // ordinary node: can only get it if it actually exists,
  45. // i.e., it has already been created and has not yet been deleted.
  46. return this.nodes.get(id.uuid); // may return undefined
  47. }
  48. // value node: implicitly create it if it doesn't exist yet,
  49. // pretending that it's "always already there"
  50. const v = this.values.get(id);
  51. if (v !== undefined) {
  52. return v;
  53. }
  54. // auto-construct non-existing value node
  55. const node = new Node();
  56. this.values.set(id, node);
  57. return node;
  58. }
  59. // same as get, but raises error when not found
  60. get(id: NodeId): Node {
  61. const node = this.getOptional(id);
  62. if (node === undefined) {
  63. throw Error("node not found");
  64. }
  65. return node;
  66. }
  67. // create a new ordinary node
  68. create(id: UUID): Node {
  69. const node = new Node();
  70. this.nodes.set(id.uuid, node);
  71. return node;
  72. }
  73. // delete an ordinary node
  74. // Idempotent.
  75. delete(id: UUID) {
  76. this.nodes.delete(id.uuid);
  77. }
  78. }
  79. type UUIDCallbackType = () => UUID;
  80. export class GraphState {
  81. nodes: NodeMap;
  82. uuidCallback: UUIDCallbackType;
  83. constructor(uuidCallback: UUIDCallbackType) {
  84. this.nodes = new NodeMap();
  85. this.uuidCallback = uuidCallback;
  86. }
  87. // Create a new node.
  88. createNode(): UUID {
  89. const uuid = this.uuidCallback();
  90. this.nodes.create(uuid);
  91. return uuid;
  92. }
  93. // Delete node and delete all of its outgoing + incoming edges.
  94. // Does nothing when given uuid does not exist.
  95. // Idempotent.
  96. deleteNode(uuid: UUID) {
  97. const node = this.nodes.getOptional(uuid);
  98. if (node !== undefined) {
  99. // delete outgoing edges
  100. for (const [label, targetId] of node.outgoing.entries()) {
  101. const targetNode = this.nodes.get(targetId);
  102. // remove edge from targetNode.incoming
  103. const i = this.lookupIncoming(targetNode, label, uuid);
  104. targetNode.incoming.splice(i, 1);
  105. }
  106. // delete incoming edges
  107. for (const {label, srcId} of node.incoming) {
  108. const srcNode = this.nodes.get(srcId);
  109. // remove edge from srcNode.outgoing
  110. srcNode.outgoing.delete(label);
  111. }
  112. // delete node itself
  113. this.nodes.delete(uuid);
  114. }
  115. }
  116. // Create or update a node's outgoing edge to point to a node
  117. // Idempotent.
  118. setEdge(srcId: NodeId, label: string, targetId: NodeId) {
  119. // gotta remove the existing edge first, if it exists
  120. this.deleteEdge(srcId, label);
  121. const srcNode = this.nodes.get(srcId);
  122. srcNode.outgoing.set(label, targetId);
  123. const targetNode = this.nodes.get(targetId);
  124. targetNode.incoming.push({label, srcId});
  125. }
  126. // Delete an edge.
  127. // Idempotent.
  128. deleteEdge(srcId: NodeId, label: string) {
  129. const srcNode = this.nodes.get(srcId);
  130. const existingTargetId = srcNode.outgoing.get(label);
  131. if (existingTargetId !== undefined) {
  132. // remove the respective entry in the existingTargetNode's 'incoming' array:
  133. const existingTargetNode = this.nodes.get(existingTargetId);
  134. const i = this.lookupIncoming(existingTargetNode, label, srcId);
  135. existingTargetNode.incoming.splice(i, 1); // remove from array
  136. }
  137. srcNode.outgoing.delete(label);
  138. }
  139. // In a node's array of incoming edges, search for {label, srcId}, and return the position in the array.
  140. private lookupIncoming(node: Node, label: string, srcId: NodeId): number {
  141. for (const [i, {label: l, srcId: s}] of node.incoming.entries()) {
  142. if (l === label && nodeIdsEqual(s, srcId)) {
  143. return i;
  144. }
  145. }
  146. throw new Error("Not found!");
  147. }
  148. // Read a node's outgoing edge with given label.
  149. // If no such edge exists, returns undefined.
  150. readEdge(srcId: NodeId, label: string): NodeId | undefined {
  151. const srcNode = this.nodes.getOptional(srcId);
  152. if (srcNode !== undefined) {
  153. return srcNode.outgoing.get(label);
  154. }
  155. }
  156. // Get all the edges in the graph. Slow. For debugging purposes.
  157. getEdges(): Array<[NodeId, NodeId, string]> {
  158. const result: Array<[NodeId, NodeId, string]> = [];
  159. // get all outgoing edges of ordinary nodes
  160. for (const [srcId, srcNode] of this.nodes.nodes.entries()) {
  161. for (const [label, targetId] of srcNode.outgoing) {
  162. result.push([new UUID(srcId), targetId, label]);
  163. }
  164. }
  165. // get all outgoing edges of value nodes
  166. for (const [srcId, srcNode] of this.nodes.values.entries()) {
  167. for (const [label, targetId] of srcNode.outgoing) {
  168. result.push([srcId, targetId, label]);
  169. }
  170. }
  171. return result;
  172. }
  173. }