transient.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Map as ImmutableMap, OrderedSet } from 'immutable';
  7. import { StateTransform } from '../transform';
  8. import { StateTree } from './immutable';
  9. import { shallowEqual } from 'mol-util/object';
  10. export { TransientTree }
  11. class TransientTree implements StateTree {
  12. transforms = this.tree.transforms as ImmutableMap<StateTransform.Ref, StateTransform>;
  13. children = this.tree.children as ImmutableMap<StateTransform.Ref, OrderedSet<StateTransform.Ref>>;
  14. private changedNodes = false;
  15. private changedChildren = false;
  16. private _childMutations: Map<StateTransform.Ref, OrderedSet<StateTransform.Ref>> | undefined = void 0;
  17. private _stateUpdates: Set<StateTransform.Ref> | undefined = void 0;
  18. private get childMutations() {
  19. if (this._childMutations) return this._childMutations;
  20. this._childMutations = new Map();
  21. return this._childMutations;
  22. }
  23. private changeNodes() {
  24. if (this.changedNodes) return;
  25. this.changedNodes = true;
  26. this.transforms = this.transforms.asMutable();
  27. }
  28. private changeChildren() {
  29. if (this.changedChildren) return;
  30. this.changedChildren = true;
  31. this.children = this.children.asMutable();
  32. }
  33. get root() { return this.transforms.get(StateTransform.RootRef)! }
  34. asTransient() {
  35. return this.asImmutable().asTransient();
  36. }
  37. private addChild(parent: StateTransform.Ref, child: StateTransform.Ref) {
  38. this.changeChildren();
  39. if (this.childMutations.has(parent)) {
  40. this.childMutations.get(parent)!.add(child);
  41. } else {
  42. const set = (this.children.get(parent) as OrderedSet<StateTransform.Ref>).asMutable();
  43. set.add(child);
  44. this.children.set(parent, set);
  45. this.childMutations.set(parent, set);
  46. }
  47. }
  48. private removeChild(parent: StateTransform.Ref, child: StateTransform.Ref) {
  49. this.changeChildren();
  50. if (this.childMutations.has(parent)) {
  51. this.childMutations.get(parent)!.remove(child);
  52. } else {
  53. const set = (this.children.get(parent) as OrderedSet<StateTransform.Ref>).asMutable();
  54. set.remove(child);
  55. this.children.set(parent, set);
  56. this.childMutations.set(parent, set);
  57. }
  58. }
  59. private clearRoot() {
  60. const parent = StateTransform.RootRef;
  61. if (this.children.get(parent).size === 0) return;
  62. this.changeChildren();
  63. const set = OrderedSet<StateTransform.Ref>();
  64. this.children.set(parent, set);
  65. this.childMutations.set(parent, set);
  66. }
  67. changeParent(ref: StateTransform.Ref, newParent: StateTransform.Ref) {
  68. ensurePresent(this.transforms, ref);
  69. const old = this.transforms.get(ref);
  70. this.removeChild(old.parent, ref);
  71. this.addChild(newParent, ref);
  72. this.changeNodes();
  73. this.transforms.set(ref, StateTransform.withParent(old, newParent));
  74. }
  75. add(transform: StateTransform) {
  76. const ref = transform.ref;
  77. if (this.transforms.has(transform.ref)) {
  78. const node = this.transforms.get(transform.ref);
  79. if (node.parent !== transform.parent) alreadyPresent(transform.ref);
  80. }
  81. const children = this.children.get(transform.parent);
  82. if (!children) parentNotPresent(transform.parent);
  83. if (!children.has(transform.ref)) {
  84. this.addChild(transform.parent, transform.ref);
  85. }
  86. if (!this.children.has(transform.ref)) {
  87. if (!this.changedChildren) {
  88. this.changedChildren = true;
  89. this.children = this.children.asMutable();
  90. }
  91. this.children.set(transform.ref, OrderedSet());
  92. }
  93. this.changeNodes();
  94. this.transforms.set(ref, transform);
  95. return this;
  96. }
  97. /** Calls Transform.definition.params.areEqual if available, otherwise uses shallowEqual to check if the params changed */
  98. setParams(ref: StateTransform.Ref, params: any) {
  99. ensurePresent(this.transforms, ref);
  100. const transform = this.transforms.get(ref)!;
  101. // TODO: should this be here?
  102. if (shallowEqual(transform.params, params)) {
  103. return false;
  104. }
  105. if (!this.changedNodes) {
  106. this.changedNodes = true;
  107. this.transforms = this.transforms.asMutable();
  108. }
  109. this.transforms.set(transform.ref, StateTransform.withParams(transform, params));
  110. return true;
  111. }
  112. assignState(ref: StateTransform.Ref, state?: Partial<StateTransform.State>) {
  113. ensurePresent(this.transforms, ref);
  114. const old = this.transforms.get(ref);
  115. if (this._stateUpdates && this._stateUpdates.has(ref)) {
  116. StateTransform.assignState(old.state, state);
  117. return old;
  118. } else {
  119. if (!this._stateUpdates) this._stateUpdates = new Set();
  120. this._stateUpdates.add(old.ref);
  121. this.changeNodes();
  122. const updated = StateTransform.withState(old, state);
  123. this.transforms.set(ref, updated);
  124. return updated;
  125. }
  126. }
  127. remove(ref: StateTransform.Ref): StateTransform[] {
  128. const node = this.transforms.get(ref);
  129. if (!node) return [];
  130. const st = StateTree.subtreePostOrder(this, node);
  131. if (ref === StateTransform.RootRef) {
  132. st.pop();
  133. if (st.length === 0) return st;
  134. this.clearRoot();
  135. } else {
  136. if (st.length === 0) return st;
  137. this.removeChild(node.parent, node.ref);
  138. }
  139. this.changeNodes();
  140. this.changeChildren();
  141. for (const n of st) {
  142. this.transforms.delete(n.ref);
  143. this.children.delete(n.ref);
  144. if (this._childMutations) this._childMutations.delete(n.ref);
  145. }
  146. return st;
  147. }
  148. asImmutable() {
  149. if (!this.changedNodes && !this.changedChildren && !this._childMutations) return this.tree;
  150. if (this._childMutations) this._childMutations.forEach(fixChildMutations, this.children);
  151. return StateTree.create(
  152. this.changedNodes ? this.transforms.asImmutable() : this.transforms,
  153. this.changedChildren ? this.children.asImmutable() : this.children);
  154. }
  155. constructor(private tree: StateTree) {
  156. }
  157. }
  158. function fixChildMutations(this: ImmutableMap<StateTransform.Ref, OrderedSet<StateTransform.Ref>>, m: OrderedSet<StateTransform.Ref>, k: StateTransform.Ref) { this.set(k, m.asImmutable()); }
  159. function alreadyPresent(ref: StateTransform.Ref) {
  160. throw new Error(`Transform '${ref}' is already present in the tree.`);
  161. }
  162. function parentNotPresent(ref: StateTransform.Ref) {
  163. throw new Error(`Parent '${ref}' must be present in the tree.`);
  164. }
  165. function ensurePresent(nodes: StateTree.Transforms, ref: StateTransform.Ref) {
  166. if (!nodes.has(ref)) {
  167. throw new Error(`Node '${ref}' is not present in the tree.`);
  168. }
  169. }