graph_state.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. import {Delta} from "./delta";
  2. import {
  3. NodeCreation,
  4. NodeDeletion,
  5. EdgeCreation,
  6. EdgeUpdate,
  7. EdgeTargetType,
  8. SetsTarget,
  9. PrimitiveRegistry,
  10. } from "./primitive_delta";
  11. import {CompositeDelta} from "./composite_delta";
  12. import {PrimitiveValue} from "./types";
  13. // This interface is used to de-couple the graph state (in our case, a data structure interpreted by the d3 library, and by the 'Graph' react component), from GraphState.
  14. export interface GraphStateListener {
  15. createNode(ns: NodeState);
  16. createValue(vs: ValueState);
  17. deleteNode(id: PrimitiveValue);
  18. deleteValue(value: PrimitiveValue);
  19. createLinkToNode(sourceId: PrimitiveValue, label: string, targetId: PrimitiveValue);
  20. createLinkToValue(sourceId: PrimitiveValue, label: string, targetValue: PrimitiveValue);
  21. deleteLink(sourceId: PrimitiveValue, label: string);
  22. }
  23. // A 'proxy' GraphStateListener that multicasts graph state operations to a bunch of GraphStateListeners.
  24. export class FanOutListener implements GraphStateListener {
  25. readonly listeners: GraphStateListener[];
  26. constructor(listeners: GraphStateListener[]) {
  27. this.listeners = listeners;
  28. }
  29. createNode(ns: NodeState) {
  30. this.listeners.forEach(m => m.createNode(ns));
  31. }
  32. createValue(vs: ValueState) {
  33. this.listeners.forEach(m => m.createValue(vs));
  34. }
  35. deleteNode(id: PrimitiveValue) {
  36. this.listeners.forEach(m => m.deleteNode(id));
  37. }
  38. deleteValue(value: PrimitiveValue) {
  39. this.listeners.forEach(m => m.deleteValue(value));
  40. }
  41. createLinkToNode(sourceId: PrimitiveValue, label: string, targetId: PrimitiveValue) {
  42. this.listeners.forEach(m => m.createLinkToNode(sourceId, label, targetId));
  43. }
  44. createLinkToValue(sourceId: PrimitiveValue, label: string, targetValue: PrimitiveValue) {
  45. this.listeners.forEach(m => m.createLinkToValue(sourceId, label, targetValue));
  46. }
  47. deleteLink(sourceId: PrimitiveValue, label: string) {
  48. this.listeners.forEach(m => m.deleteLink(sourceId, label));
  49. }
  50. }
  51. type IncomingEdgeDelta = EdgeCreation|EdgeUpdate|NodeDeletion;
  52. export class DummyListener implements GraphStateListener {
  53. createNode(ns: NodeState) {}
  54. createValue(vs: ValueState) {}
  55. deleteNode(id: PrimitiveValue) {}
  56. deleteValue(value: PrimitiveValue) {}
  57. createLinkToNode(sourceId: PrimitiveValue, label: string, targetId: PrimitiveValue) {}
  58. createLinkToValue(sourceId: PrimitiveValue, label: string, targetValue: PrimitiveValue) {}
  59. deleteLink(sourceId: PrimitiveValue, label: string) {}
  60. }
  61. const DUMMY = new DummyListener();
  62. abstract class Common {
  63. // If there once was an incoming edge on this node (or value), this contains the Delta that (un)set the edge to point to this node (or value)
  64. readonly incoming: Array<IncomingEdgeDelta> = [];
  65. // For every currently incoming edge, the pair (label, source)
  66. readonly incomingStates: Array<[string, NodeState]> = [];
  67. addIncoming(delta: IncomingEdgeDelta, listener: GraphStateListener) {
  68. this.incoming.push(delta);
  69. }
  70. replaceIncoming(prevDelta: IncomingEdgeDelta, newDelta: IncomingEdgeDelta, listener: GraphStateListener) {
  71. this.incoming.splice(this.incoming.indexOf(prevDelta), 1, newDelta);
  72. }
  73. // only called when undoing a Delta
  74. removeIncoming(delta: IncomingEdgeDelta, listener: GraphStateListener) {
  75. this.incoming.splice(this.incoming.indexOf(delta), 1);
  76. }
  77. getIncomingEdges(): [string, INodeState][] {
  78. return this.incomingStates;
  79. }
  80. // pure
  81. abstract isNode(uuid: PrimitiveValue): boolean;
  82. abstract isValue(value: PrimitiveValue): boolean;
  83. // abstract isTargetOf(setsTarget: SetsTarget): boolean;
  84. abstract asTarget(): EdgeTargetType;
  85. // will modify 'listener'
  86. abstract createLinkTo(sourceId: PrimitiveValue, label: string, listener: GraphStateListener);
  87. // pure
  88. abstract getDeltasForSetEdge(registry: PrimitiveRegistry, label: string, target: EdgeTargetType): (EdgeCreation|EdgeUpdate)[];
  89. abstract getDeltasForDelete(registry: PrimitiveRegistry): (EdgeUpdate|NodeDeletion)[];
  90. // pure
  91. getIncomingEdgeDependenciesForDelete(registry: PrimitiveRegistry): [EdgeUpdate[], (EdgeUpdate|NodeDeletion)[]] {
  92. const edgeUnsettings: EdgeUpdate[] = [];
  93. const incomingEdgeDependencies = this.incoming.map((incomingEdge) => {
  94. // We also depend on incoming edges that were deleted (because their source node was deleted):
  95. if (incomingEdge instanceof NodeDeletion) {
  96. return incomingEdge;
  97. }
  98. else if (incomingEdge.target.getTarget() === this.asTarget()) {
  99. // Must set the value of every incoming edge to 'null' (with an EdgeUpdate):
  100. const edgeUnsetting = registry.newEdgeUpdate(incomingEdge, null);
  101. edgeUnsettings.push(edgeUnsetting);
  102. return edgeUnsetting;
  103. }
  104. else {
  105. // Edge is already pointing somewhere else: just include the operation as a dependency for the deletion.
  106. if (!(incomingEdge instanceof EdgeUpdate)) {
  107. throw new Error("Assertion failed: incomingEdge must be EdgeUpdate here.")
  108. }
  109. return incomingEdge;
  110. }
  111. });
  112. // incomingEdgeDependencies are dependencies of the (future) deletion of this node.
  113. // edgeUnsettings is the subset (of incomingEdgeDependencies) of NEW deltas.
  114. return [edgeUnsettings, incomingEdgeDependencies];
  115. }
  116. }
  117. // These interfaces allow us to pass around NodeState and ValueState objects while not exposing the stuff that is supposed to remain internal wrt. this module.
  118. interface ICommon {
  119. // array of (label, NodeState) pairs
  120. getIncomingEdges(): [string, INodeState][];
  121. getDeltasForSetEdge(registry: PrimitiveRegistry, label: string, target: EdgeTargetType): (EdgeCreation|EdgeUpdate)[];
  122. getDeltasForDelete(registry: PrimitiveRegistry): (EdgeUpdate|NodeDeletion)[];
  123. asTarget(): EdgeTargetType;
  124. isNode(uuid: PrimitiveValue): boolean;
  125. isValue(value: PrimitiveValue): boolean;
  126. }
  127. export interface IValueState extends ICommon {
  128. readonly type: "value";
  129. readonly value: PrimitiveValue;
  130. }
  131. export interface INodeState extends ICommon {
  132. readonly type: "node";
  133. // mapping of label to NodeState
  134. getOutgoingEdges(): Map<string, IValueState | INodeState>;
  135. readonly creation: NodeCreation;
  136. }
  137. // 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).
  138. // 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.
  139. class NodeState extends Common implements INodeState {
  140. readonly type = "node";
  141. readonly creation: NodeCreation;
  142. // For every *currently* outgoing edge, the Delta that set this edge to its current value
  143. readonly outgoing: Map<string, EdgeCreation|EdgeUpdate> = new Map();
  144. readonly outgoingStates: Map<string, IValueState | INodeState> = new Map();
  145. constructor(creation: NodeCreation) {
  146. super();
  147. this.creation = creation;
  148. }
  149. isNode(uuid: PrimitiveValue): boolean {
  150. return this.creation.id.value == uuid;
  151. }
  152. isValue(value: PrimitiveValue): boolean {
  153. return false;
  154. }
  155. asTarget(): EdgeTargetType {
  156. return this.creation;
  157. }
  158. createLinkTo(sourceId: PrimitiveValue, label: string, listener: GraphStateListener) {
  159. listener.createLinkToNode(sourceId, label, this.creation.id.value);
  160. }
  161. getOutgoingEdges(): Map<string, IValueState | INodeState> {
  162. return this.outgoingStates;
  163. }
  164. // Has no side effects - instead returns the deltas that capture the creation or update of the given outgoing edge
  165. getDeltasForSetEdge(registry: PrimitiveRegistry, label: string, target: EdgeTargetType): (EdgeCreation|EdgeUpdate)[] {
  166. const previousEdgeUpdate = this.outgoing.get(label);
  167. if (previousEdgeUpdate !== undefined) {
  168. return [registry.newEdgeUpdate(previousEdgeUpdate, target)];
  169. }
  170. else {
  171. return [registry.newEdgeCreation(this.creation, label, target)];
  172. }
  173. }
  174. // Has no side effects - instead returns the deltas that capture the deletion of this node (and its incoming+outgoing edges)
  175. getDeltasForDelete(registry: PrimitiveRegistry): (EdgeUpdate|NodeDeletion)[] {
  176. const [edgeUnsettings, incomingEdgeDependencies] = this.getIncomingEdgeDependenciesForDelete(registry);
  177. const outgoingEdgeDependencies = [...this.outgoing.values()];
  178. const nodeDeletion = registry.newNodeDeletion(this.creation, outgoingEdgeDependencies, incomingEdgeDependencies);
  179. return [...edgeUnsettings, nodeDeletion];
  180. }
  181. }
  182. class ValueState extends Common implements IValueState {
  183. readonly type = "value";
  184. shown: boolean = false; // does a (visible) node currently exist for this value?
  185. readonly value: PrimitiveValue;
  186. constructor(value: PrimitiveValue) {
  187. super();
  188. this.value = value;
  189. }
  190. isNode(uuid: PrimitiveValue) {
  191. return false;
  192. }
  193. isValue(value: PrimitiveValue) {
  194. return this.value === value;
  195. }
  196. asTarget(): EdgeTargetType {
  197. return this.value;
  198. }
  199. createLinkTo(sourceId: PrimitiveValue, label: string, listener: GraphStateListener) {
  200. listener.createLinkToValue(sourceId, label, this.value);
  201. }
  202. addIncoming(delta: IncomingEdgeDelta, listener: GraphStateListener) {
  203. super.addIncoming(delta, listener);
  204. this._showOrHide(listener);
  205. }
  206. replaceIncoming(prevDelta: IncomingEdgeDelta, newDelta: IncomingEdgeDelta, listener: GraphStateListener) {
  207. super.replaceIncoming(prevDelta, newDelta, listener);
  208. this._showOrHide(listener);
  209. }
  210. removeIncoming(delta: IncomingEdgeDelta, listener: GraphStateListener) {
  211. super.removeIncoming(delta, listener);
  212. this._showOrHide(listener);
  213. }
  214. // 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'.
  215. private _showOrHide(listener: GraphStateListener) {
  216. const willShow = this.incoming.some(delta => {
  217. // Does 'delta' require a value node to be displayed?
  218. if (delta instanceof EdgeCreation) {
  219. return true;
  220. }
  221. else if (delta instanceof EdgeUpdate) {
  222. return delta.target.getTarget() === this.value;
  223. }
  224. else if (delta instanceof NodeDeletion) {
  225. return false;
  226. }
  227. });
  228. if (!this.shown && willShow) {
  229. listener.createValue(this);
  230. }
  231. else if (this.shown && !willShow) {
  232. listener.deleteValue(this.value);
  233. }
  234. this.shown = willShow;
  235. }
  236. getDeltasForSetEdge(registry: PrimitiveRegistry, label: string, target: EdgeTargetType): (EdgeCreation|EdgeUpdate)[] {
  237. // A value cannot be the source of an edge, so we return no deltas.
  238. throw new Error("Assertion failed: A value cannot be the source of an edge.")
  239. }
  240. getDeltasForDelete(registry: PrimitiveRegistry): (EdgeUpdate|NodeDeletion)[] {
  241. const [edgeUnsettings, _] = this.getIncomingEdgeDependenciesForDelete(registry);
  242. return edgeUnsettings;
  243. }
  244. }
  245. // Executes (primitive) deltas, and updates the graph state accordingly (through GraphStateListener)
  246. // Decouples execution of deltas from any specific graph state representation (e.g. d3).
  247. export class GraphState {
  248. readonly nodes: Map<PrimitiveValue, NodeState> = new Map();
  249. readonly values: Map<PrimitiveValue, ValueState> = new Map();
  250. // private readonly listener: GraphStateListener;
  251. // constructor(listener: GraphStateListener = DUMMY) {
  252. // this.listener = listener;
  253. // }
  254. exec(delta: Delta, listener: GraphStateListener = DUMMY) {
  255. if (delta instanceof CompositeDelta) {
  256. delta.deltas.forEach(d => this.exec(d, listener));
  257. }
  258. else if (delta instanceof NodeCreation) {
  259. this.execNodeCreation(delta, listener);
  260. }
  261. else if (delta instanceof NodeDeletion) {
  262. this.execNodeDeletion(delta, listener);
  263. }
  264. else if (delta instanceof EdgeCreation) {
  265. this.execEdgeCreation(delta, listener);
  266. }
  267. else if (delta instanceof EdgeUpdate) {
  268. this.execEdgeUpdate(delta, listener);
  269. }
  270. else {
  271. throw new Error("Assertion failed: Unexpected delta type");
  272. }
  273. }
  274. unexec(delta: Delta, listener: GraphStateListener = DUMMY) {
  275. if (delta instanceof CompositeDelta) {
  276. // must un-exec them in reverse order:
  277. delta.deltas.reduceRight((_, currentDelta) => {this.unexec(currentDelta, listener); return null;}, null);
  278. }
  279. else if (delta instanceof NodeCreation) {
  280. this.unexecNodeCreation(delta, listener);
  281. }
  282. else if (delta instanceof NodeDeletion) {
  283. this.unexecNodeDeletion(delta, listener);
  284. }
  285. else if (delta instanceof EdgeCreation) {
  286. this.unexecEdgeCreation(delta, listener);
  287. }
  288. else if (delta instanceof EdgeUpdate) {
  289. this.unexecEdgeUpdate(delta, listener);
  290. }
  291. else {
  292. throw new Error("Assertion failed: Unexpected delta type");
  293. }
  294. }
  295. private _getEdgeTargetState(target: EdgeTargetType): NodeState | ValueState | undefined {
  296. if (target instanceof NodeCreation) {
  297. return this.nodes.get(target.id.value); // may return undefined
  298. }
  299. else if (target !== null) { // target was a PrimitiveValue
  300. return this.getValueState(target);
  301. }
  302. }
  303. getValueState(value: PrimitiveValue): ValueState {
  304. let vs = this.values.get(value);
  305. if (vs === undefined) {
  306. vs = new ValueState(value);
  307. this.values.set(value, vs);
  308. }
  309. return vs;
  310. }
  311. execNodeCreation(delta: NodeCreation, listener: GraphStateListener) {
  312. // console.log("execNodeCreation", delta)
  313. const nodeState = new NodeState(delta);
  314. this.nodes.set(delta.id.value, nodeState);
  315. listener.createNode(nodeState);
  316. }
  317. unexecNodeCreation(delta: NodeCreation, listener: GraphStateListener) {
  318. // console.log("unexecNodeCreation", delta)
  319. this.nodes.delete(delta.id.value);
  320. listener.deleteNode(delta.id.value);
  321. }
  322. execNodeDeletion(delta: NodeDeletion, listener: GraphStateListener) {
  323. // console.log("execNodeDeletion", delta)
  324. const id = delta.creation.id.value;
  325. const nodeState = this.nodes.get(id);
  326. if (nodeState === undefined) {
  327. throw new Error("Assertion failed: deleted node does not exist")
  328. }
  329. // For every outgoing edge of deleted node, replace in the target node the incoming edge operation by the deletion:
  330. for (const outgoingEdgeOperation of nodeState.outgoing.values()) {
  331. const target = outgoingEdgeOperation.target.getTarget();
  332. const targetState = this._getEdgeTargetState(target);
  333. if (targetState !== undefined) {
  334. targetState.replaceIncoming(outgoingEdgeOperation, delta, listener);
  335. const outgoingEdgeCreation = outgoingEdgeOperation.getCreation();
  336. const sourceId = outgoingEdgeCreation.source.id.value;
  337. const label = outgoingEdgeCreation.label;
  338. listener.deleteLink(sourceId, label);
  339. }
  340. }
  341. listener.deleteNode(id);
  342. }
  343. unexecNodeDeletion(delta: NodeDeletion, listener: GraphStateListener) {
  344. // restore outgoing links
  345. const id = delta.creation.id.value;
  346. const nodeState = this.nodes.get(id);
  347. if (nodeState === undefined) {
  348. throw new Error("Assertion failed: deleted node does not exist")
  349. }
  350. listener.createNode(nodeState);
  351. // For every outgoing edge of deleted node, restore in the target node the incoming edge operation by whatever was there before
  352. for (const outgoingEdgeOperation of nodeState.outgoing.values()) {
  353. const target = outgoingEdgeOperation.target.getTarget();
  354. const targetState = this._getEdgeTargetState(target);
  355. if (targetState !== undefined) {
  356. targetState.replaceIncoming(delta, outgoingEdgeOperation, listener);
  357. const outgoingEdgeCreation = outgoingEdgeOperation.getCreation();
  358. const sourceId = outgoingEdgeCreation.source.id.value;
  359. const label = outgoingEdgeCreation.label;
  360. targetState.createLinkTo(sourceId, label, listener);
  361. }
  362. }
  363. }
  364. execEdgeCreation(delta: EdgeCreation, listener: GraphStateListener) {
  365. // console.log("execEdgeCreation", delta)
  366. const sourceId = delta.source.id.value;
  367. const target = delta.target.getTarget();
  368. if (target === null) {
  369. throw new Error("Assertion failed: EdgeCreation never sets edge target to null.");
  370. }
  371. const targetState = this._getEdgeTargetState(target);
  372. const label = delta.label;
  373. const sourceState = this.nodes.get(sourceId);
  374. if (sourceState === undefined) {
  375. throw new Error("Assertion failed: Source node is non-existing.")
  376. }
  377. if (targetState === undefined) {
  378. throw new Error("Assertion failed: Target node is non-existing.");
  379. }
  380. sourceState.outgoing.set(label, delta);
  381. sourceState.outgoingStates.set(label, targetState);
  382. targetState.addIncoming(delta, listener);
  383. targetState.incomingStates.push([label, sourceState]);
  384. targetState.createLinkTo(sourceId, label, listener);
  385. }
  386. unexecEdgeCreation(delta: EdgeCreation, listener: GraphStateListener) {
  387. const sourceId = delta.source.id.value;
  388. const target = delta.target.getTarget();
  389. if (target === null) {
  390. throw new Error("Assertion failed: EdgeCreation never sets edge target to null.");
  391. }
  392. const label = delta.label;
  393. const sourceState = this.nodes.get(sourceId);
  394. const targetState = this._getEdgeTargetState(target);
  395. if (sourceState === undefined) {
  396. throw new Error("Assertion failed: Source node is non-existing.")
  397. }
  398. if (targetState === undefined) {
  399. throw new Error("Assertion failed: Target node is non-existing.");
  400. }
  401. sourceState.outgoing.delete(label);
  402. sourceState.outgoingStates.delete(label);
  403. targetState.removeIncoming(delta, listener);
  404. targetState.incomingStates.splice(targetState.incomingStates.findIndex(([l,s]) => l===label && s===sourceState), 1);
  405. listener.deleteLink(sourceId, label);
  406. }
  407. execEdgeUpdate(delta: EdgeUpdate, listener: GraphStateListener) {
  408. // console.log("execEdgeUpdate", delta)
  409. // Delete link to old target
  410. const edgeCreation = delta.getCreation();
  411. const sourceId = edgeCreation.source.id.value;
  412. const label = edgeCreation.label;
  413. const overwrittenEdgeOperation = delta.overwrites;
  414. const sourceState = this.nodes.get(sourceId);
  415. if (sourceState === undefined) {
  416. throw new Error("Assertion failed: Must have sourceState.");
  417. }
  418. // Delete link to old target
  419. const oldTarget = overwrittenEdgeOperation.target.getTarget();
  420. if (oldTarget !== null) {
  421. // The old target was a node
  422. const oldTargetState = this._getEdgeTargetState(oldTarget);
  423. // Delete from old target's incoming edges:
  424. if (oldTargetState !== undefined) {
  425. oldTargetState.replaceIncoming(overwrittenEdgeOperation, delta, listener);
  426. oldTargetState.incomingStates.splice(oldTargetState.incomingStates.findIndex(([l,s]) => l===label && s===sourceState), 1);
  427. listener.deleteLink(sourceId, label);
  428. }
  429. }
  430. // Create link to new target (if there is a target)
  431. const newTarget = delta.target.getTarget();
  432. if (newTarget !== null) {
  433. // The new target is a node
  434. const newTargetState = this._getEdgeTargetState(newTarget);
  435. // Add to new target's incoming edges
  436. if (newTargetState !== undefined) {
  437. if (newTarget !== oldTarget) { // if newTarget === oldTarget, the 'delta' is already part of 'incoming'
  438. newTargetState.addIncoming(delta, listener);
  439. }
  440. newTargetState.incomingStates.push([label, sourceState]);
  441. newTargetState.createLinkTo(sourceId, label, listener);
  442. sourceState.outgoingStates.set(label, newTargetState);
  443. }
  444. } else {
  445. sourceState.outgoingStates.delete(label);
  446. }
  447. sourceState.outgoing.set(label, delta);
  448. }
  449. unexecEdgeUpdate(delta: EdgeUpdate, listener: GraphStateListener) {
  450. // console.log("execEdgeUpdate", delta)
  451. // Delete link to old target
  452. const edgeCreation = delta.getCreation();
  453. const sourceId = edgeCreation.source.id.value;
  454. const label = edgeCreation.label;
  455. const overwrittenEdgeOperation = delta.overwrites;
  456. const sourceState = this.nodes.get(sourceId);
  457. if (sourceState === undefined) {
  458. throw new Error("Assertion failed: Must have sourceState.");
  459. }
  460. // Delete link to new target (if there is a target)
  461. const newTarget = delta.target.getTarget();
  462. if (newTarget !== null) {
  463. // The new target is a node
  464. const newTargetState = this._getEdgeTargetState(newTarget);
  465. // Add to new target's incoming edges
  466. if (newTargetState !== undefined) {
  467. newTargetState.removeIncoming(delta, listener);
  468. newTargetState.incomingStates.splice(newTargetState.incomingStates.findIndex(([l,s]) => l===label && s===sourceState), 1);
  469. listener.deleteLink(sourceId, label);
  470. }
  471. }
  472. // Restore link to old target
  473. const oldTarget = overwrittenEdgeOperation.target.getTarget();
  474. if (oldTarget !== null) {
  475. // The old target was a node
  476. const oldTargetState = this._getEdgeTargetState(oldTarget);
  477. // Delete from old target's incoming edges:
  478. if (oldTargetState !== undefined) {
  479. oldTargetState.replaceIncoming(delta, overwrittenEdgeOperation, listener);
  480. oldTargetState.incomingStates.push([label, sourceState]);
  481. oldTargetState.createLinkTo(sourceId, label, listener);
  482. sourceState.outgoingStates.set(label, oldTargetState);
  483. }
  484. } else {
  485. sourceState.outgoingStates.delete(label);
  486. }
  487. sourceState.outgoing.set(label, overwrittenEdgeOperation);
  488. }
  489. }