immutable.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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 { TransientTree } from './transient';
  9. export { StateTree };
  10. /**
  11. * An immutable tree where each node requires a unique reference.
  12. * Represented as an immutable map.
  13. */
  14. interface StateTree {
  15. readonly root: StateTransform,
  16. readonly transforms: StateTree.Transforms,
  17. readonly children: StateTree.Children,
  18. /** Refs to all nodes that depend on the given key */
  19. readonly dependencies: StateTree.Dependencies,
  20. asTransient(): TransientTree
  21. }
  22. namespace StateTree {
  23. type Ref = StateTransform.Ref
  24. export interface ChildSet {
  25. readonly size: number,
  26. readonly values: OrderedSet<Ref>['values'],
  27. has(ref: Ref): boolean,
  28. readonly forEach: OrderedSet<Ref>['forEach'],
  29. readonly map: OrderedSet<Ref>['map'],
  30. toArray(): Ref[],
  31. first(): Ref,
  32. asMutable(): MutableChildSet
  33. }
  34. export interface MutableChildSet extends ChildSet {
  35. add(ref: Ref): MutableChildSet,
  36. remove(ref: Ref): MutableChildSet,
  37. asImmutable(): ChildSet
  38. }
  39. interface _Map<T> {
  40. readonly size: number,
  41. has(ref: Ref): boolean,
  42. get(ref: Ref): T,
  43. asImmutable(): _Map<T>,
  44. asMutable(): MutableMap<T>
  45. }
  46. export interface MutableMap<T> extends _Map<T> {
  47. set(ref: Ref, value: T): MutableMap<T>,
  48. delete(ref: Ref): MutableMap<T>
  49. }
  50. export interface Transforms extends _Map<StateTransform> {}
  51. export interface Children extends _Map<ChildSet> { }
  52. export interface Dependencies extends _Map<ChildSet> { }
  53. export interface MutableTransforms extends MutableMap<StateTransform> {}
  54. export interface MutableChildren extends MutableMap<MutableChildSet> { }
  55. export interface MutableDependencies extends MutableMap<MutableChildSet> { }
  56. class Impl implements StateTree {
  57. get root() { return this.transforms.get(StateTransform.RootRef)!; }
  58. asTransient(): TransientTree {
  59. return new TransientTree(this);
  60. }
  61. constructor(public transforms: Transforms, public children: Children, public dependencies: Dependencies) {
  62. }
  63. }
  64. /**
  65. * Create an instance of an immutable tree.
  66. */
  67. export function createEmpty(customRoot?: StateTransform): StateTree {
  68. const root = customRoot || StateTransform.createRoot();
  69. return create(
  70. ImmutableMap([[root.ref, root] as [Ref, StateTransform]]) as Transforms,
  71. ImmutableMap([[root.ref, OrderedSet()] as [Ref, ChildSet]]) as Children,
  72. ImmutableMap() as Dependencies);
  73. }
  74. export function create(nodes: Transforms, children: Children, dependencies: Dependencies): StateTree {
  75. return new Impl(nodes, children, dependencies);
  76. }
  77. type VisitorCtx = { tree: StateTree, state: any, f: (node: StateTransform, tree: StateTree, state: any) => boolean | undefined | void };
  78. function _postOrderFunc(this: VisitorCtx, c: Ref | undefined) { _doPostOrder(this, this.tree.transforms.get(c!)!); }
  79. function _doPostOrder(ctx: VisitorCtx, root: StateTransform) {
  80. const children = ctx.tree.children.get(root.ref);
  81. if (children && children.size) {
  82. children.forEach(_postOrderFunc, ctx);
  83. }
  84. ctx.f(root, ctx.tree, ctx.state);
  85. }
  86. /**
  87. * Visit all nodes in a subtree in "post order", meaning leafs get visited first.
  88. */
  89. export function doPostOrder<S>(tree: StateTree, root: StateTransform, state: S, f: (node: StateTransform, tree: StateTree, state: S) => boolean | undefined | void): S {
  90. const ctx: VisitorCtx = { tree, state, f };
  91. _doPostOrder(ctx, root);
  92. return ctx.state;
  93. }
  94. function _preOrderFunc(this: VisitorCtx, c: Ref | undefined) { _doPreOrder(this, this.tree.transforms.get(c!)!); }
  95. function _doPreOrder(ctx: VisitorCtx, root: StateTransform) {
  96. const ret = ctx.f(root, ctx.tree, ctx.state);
  97. if (typeof ret === 'boolean' && !ret) return;
  98. const children = ctx.tree.children.get(root.ref);
  99. if (children && children.size) {
  100. children.forEach(_preOrderFunc, ctx);
  101. }
  102. }
  103. /**
  104. * Visit all nodes in a subtree in "pre order", meaning leafs get visited last.
  105. * If the visitor function returns false, the visiting for that branch is interrupted.
  106. */
  107. export function doPreOrder<S>(tree: StateTree, root: StateTransform, state: S, f: (node: StateTransform, tree: StateTree, state: S) => boolean | undefined | void): S {
  108. const ctx: VisitorCtx = { tree, state, f };
  109. _doPreOrder(ctx, root);
  110. return ctx.state;
  111. }
  112. function _subtree(n: StateTransform, _: any, subtree: StateTransform[]) { subtree.push(n); }
  113. /**
  114. * Get all nodes in a subtree, leafs come first.
  115. */
  116. export function subtreePostOrder(tree: StateTree, root: StateTransform) {
  117. return doPostOrder<StateTransform[]>(tree, root, [], _subtree);
  118. }
  119. function _visitNodeToJson(node: StateTransform, tree: StateTree, ctx: StateTransform.Serialized[]) {
  120. // const children: Ref[] = [];
  121. // tree.children.get(node.ref).forEach(_visitChildToJson as any, children);
  122. ctx.push(StateTransform.toJSON(node));
  123. }
  124. export interface Serialized {
  125. /** Transforms serialized in pre-order */
  126. transforms: StateTransform.Serialized[]
  127. }
  128. export function toJSON(tree: StateTree): Serialized {
  129. const transforms: StateTransform.Serialized[] = [];
  130. doPreOrder(tree, tree.root, transforms, _visitNodeToJson);
  131. return { transforms };
  132. }
  133. export function fromJSON(data: Serialized): StateTree {
  134. const nodes = ImmutableMap<Ref, StateTransform>().asMutable();
  135. const children = ImmutableMap<Ref, OrderedSet<Ref>>().asMutable();
  136. const dependencies = ImmutableMap<Ref, OrderedSet<Ref>>().asMutable();
  137. for (const t of data.transforms) {
  138. const transform = StateTransform.fromJSON(t);
  139. nodes.set(transform.ref, transform);
  140. if (!children.has(transform.ref)) {
  141. children.set(transform.ref, OrderedSet<Ref>().asMutable());
  142. }
  143. if (transform.ref !== transform.parent) children.get(transform.parent)!.add(transform.ref);
  144. }
  145. const dependent = new Set<Ref>();
  146. for (const t of data.transforms) {
  147. const ref = t.ref;
  148. children.set(ref, children.get(ref)!.asImmutable());
  149. if (!t.dependsOn) continue;
  150. for (const d of t.dependsOn) {
  151. dependent.add(d);
  152. if (!dependencies.has(d)) {
  153. dependencies.set(d, OrderedSet<Ref>([ref]).asMutable());
  154. } else {
  155. dependencies.get(d)!.add(ref);
  156. }
  157. }
  158. }
  159. dependent.forEach(d => {
  160. dependencies.set(d, dependencies.get(d)!.asImmutable());
  161. });
  162. return create(nodes.asImmutable() as Transforms, children.asImmutable() as Children, dependencies.asImmutable() as Dependencies);
  163. }
  164. export function dump(tree: StateTree) {
  165. console.log({
  166. tr: (tree.transforms as ImmutableMap<any, any>).keySeq().toArray(),
  167. tr1: (tree.transforms as ImmutableMap<any, any>).valueSeq().toArray().map(t => t.ref),
  168. ch: (tree.children as ImmutableMap<any, any>).keySeq().toArray()
  169. });
  170. }
  171. function _subtreeHasRef(tree: StateTree, root: StateTransform.Ref, ref: StateTransform.Ref) {
  172. if (root === ref) return true;
  173. const children = tree.children.get(root);
  174. const it = children.values();
  175. while (true) {
  176. const next = it.next();
  177. if (next.done) return false;
  178. if (_subtreeHasRef(tree, next.value, ref)) return true;
  179. }
  180. }
  181. /** Check if the subtree with the given root has the provided ref */
  182. export function subtreeHasRef(tree: StateTree, root: StateTransform.Ref, ref: StateTransform.Ref): boolean {
  183. if (!tree.transforms.has(root) || !tree.transforms.has(ref)) return false;
  184. return _subtreeHasRef(tree, root, ref);
  185. }
  186. export function getDecoratorRoot(tree: StateTree, ref: StateTransform.Ref): StateTransform.Ref {
  187. const children = tree.children.get(ref);
  188. if (children.size !== 1) return ref;
  189. const child = tree.transforms.get(children.first());
  190. if (child.transformer.definition.isDecorator) return getDecoratorRoot(tree, child.ref);
  191. return ref;
  192. }
  193. }