state.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  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 { StateObject, StateObjectCell } from './object';
  7. import { StateTree } from './tree';
  8. import { StateTransform } from './transform';
  9. import { StateTransformer } from './transformer';
  10. import { RuntimeContext, Task } from 'mol-task';
  11. import { StateSelection } from './state/selection';
  12. import { RxEventHelper } from 'mol-util/rx-event-helper';
  13. import { StateBuilder } from './state/builder';
  14. import { StateAction } from './action';
  15. import { StateActionManager } from './action/manager';
  16. import { TransientTree } from './tree/transient';
  17. import { LogEntry } from 'mol-util/log-entry';
  18. import { now, formatTimespan } from 'mol-util/now';
  19. import { ParamDefinition } from 'mol-util/param-definition';
  20. import { StateTreeSpine } from './tree/spine';
  21. export { State }
  22. class State {
  23. private _tree: TransientTree;
  24. protected errorFree = true;
  25. private ev = RxEventHelper.create();
  26. readonly globalContext: unknown = void 0;
  27. readonly events = {
  28. cell: {
  29. stateUpdated: this.ev<State.ObjectEvent & { cellState: StateObjectCell.State }>(),
  30. created: this.ev<State.ObjectEvent & { cell: StateObjectCell }>(),
  31. removed: this.ev<State.ObjectEvent & { parent: StateTransform.Ref }>(),
  32. },
  33. object: {
  34. updated: this.ev<State.ObjectEvent & { action: 'in-place' | 'recreate', obj: StateObject, oldObj?: StateObject }>(),
  35. created: this.ev<State.ObjectEvent & { obj: StateObject }>(),
  36. removed: this.ev<State.ObjectEvent & { obj?: StateObject }>()
  37. },
  38. log: this.ev<LogEntry>(),
  39. changed: this.ev<void>(),
  40. isUpdating: this.ev<boolean>()
  41. };
  42. readonly behaviors = {
  43. currentObject: this.ev.behavior<State.ObjectEvent>({ state: this, ref: StateTransform.RootRef })
  44. };
  45. readonly actions = new StateActionManager();
  46. get tree(): StateTree { return this._tree; }
  47. get transforms() { return (this._tree as StateTree).transforms; }
  48. get cellStates() { return (this._tree as StateTree).cellStates; }
  49. get current() { return this.behaviors.currentObject.value.ref; }
  50. build() { return new StateBuilder.Root(this._tree); }
  51. readonly cells: State.Cells = new Map();
  52. private spine = new StateTreeSpine.Impl(this.cells);
  53. getSnapshot(): State.Snapshot {
  54. return { tree: StateTree.toJSON(this._tree) };
  55. }
  56. setSnapshot(snapshot: State.Snapshot) {
  57. const tree = StateTree.fromJSON(snapshot.tree);
  58. return this.updateTree(tree);
  59. }
  60. setCurrent(ref: StateTransform.Ref) {
  61. this.behaviors.currentObject.next({ state: this, ref });
  62. }
  63. updateCellState(ref: StateTransform.Ref, stateOrProvider: ((old: StateObjectCell.State) => Partial<StateObjectCell.State>) | Partial<StateObjectCell.State>) {
  64. const update = typeof stateOrProvider === 'function'
  65. ? stateOrProvider(this.tree.cellStates.get(ref))
  66. : stateOrProvider;
  67. if (this._tree.updateCellState(ref, update)) {
  68. this.events.cell.stateUpdated.next({ state: this, ref, cellState: this.tree.cellStates.get(ref) });
  69. }
  70. }
  71. dispose() {
  72. this.ev.dispose();
  73. }
  74. /**
  75. * Select Cells using the provided selector.
  76. * @example state.query(StateSelection.Generators.byRef('test').ancestorOfType([type]))
  77. * @example state.query('test')
  78. */
  79. select(selector: StateSelection.Selector) {
  80. return StateSelection.select(selector, this)
  81. }
  82. /**
  83. * Select Cells by building a query generated on the fly.
  84. * @example state.select(q => q.byRef('test').subtree())
  85. */
  86. selectQ(selector: (q: typeof StateSelection.Generators) => StateSelection.Selector) {
  87. if (typeof selector === 'string') return StateSelection.select(selector, this);
  88. return StateSelection.select(selector(StateSelection.Generators), this)
  89. }
  90. /**
  91. * Creates a Task that applies the specified StateAction (i.e. must use run* on the result)
  92. * If no ref is specified, apply to root.
  93. */
  94. applyAction<A extends StateAction>(action: A, params: StateAction.Params<A>, ref: StateTransform.Ref = StateTransform.RootRef): Task<void> {
  95. return Task.create('Apply Action', ctx => {
  96. const cell = this.cells.get(ref);
  97. if (!cell) throw new Error(`'${ref}' does not exist.`);
  98. if (cell.status !== 'ok') throw new Error(`Action cannot be applied to a cell with status '${cell.status}'`);
  99. return runTask(action.definition.run({ ref, cell, a: cell.obj!, params, state: this }, this.globalContext), ctx);
  100. });
  101. }
  102. /**
  103. * Reconcialites the existing state tree with the new version.
  104. *
  105. * If the tree is StateBuilder.To<T>, the corresponding StateObject is returned by the task.
  106. * @param tree Tree instance or a tree builder instance
  107. * @param doNotReportTiming Indicates whether to log timing of the individual transforms
  108. */
  109. updateTree<T extends StateObject>(tree: StateBuilder.To<T>, options?: Partial<State.UpdateOptions>): Task<T>
  110. updateTree(tree: StateTree | StateBuilder, options?: Partial<State.UpdateOptions>): Task<void>
  111. updateTree(tree: StateTree | StateBuilder, options?: Partial<State.UpdateOptions>): Task<any> {
  112. return Task.create('Update Tree', async taskCtx => {
  113. this.events.isUpdating.next(true);
  114. let updated = false;
  115. const ctx = this.updateTreeAndCreateCtx(tree, taskCtx, options);
  116. try {
  117. updated = await update(ctx);
  118. if (StateBuilder.isTo(tree)) {
  119. const cell = this.select(tree.ref)[0];
  120. return cell && cell.obj;
  121. }
  122. } finally {
  123. this.spine.setSurrent();
  124. if (updated) this.events.changed.next();
  125. this.events.isUpdating.next(false);
  126. for (const ref of ctx.stateChanges) {
  127. this.events.cell.stateUpdated.next({ state: this, ref, cellState: this.tree.cellStates.get(ref) });
  128. }
  129. }
  130. });
  131. }
  132. private updateTreeAndCreateCtx(tree: StateTree | StateBuilder, taskCtx: RuntimeContext, options: Partial<State.UpdateOptions> | undefined) {
  133. const _tree = (StateBuilder.is(tree) ? tree.getTree() : tree).asTransient();
  134. const oldTree = this._tree;
  135. this._tree = _tree;
  136. const ctx: UpdateContext = {
  137. parent: this,
  138. editInfo: StateBuilder.is(tree) ? tree.editInfo : void 0,
  139. errorFree: this.errorFree,
  140. taskCtx,
  141. oldTree,
  142. tree: _tree,
  143. cells: this.cells as Map<StateTransform.Ref, StateObjectCell>,
  144. spine: this.spine,
  145. results: [],
  146. stateChanges: [],
  147. options: { ...StateUpdateDefaultOptions, ...options },
  148. changed: false,
  149. hadError: false,
  150. newCurrent: void 0
  151. };
  152. this.errorFree = true;
  153. return ctx;
  154. }
  155. constructor(rootObject: StateObject, params?: { globalContext?: unknown, rootProps?: StateTransform.Props }) {
  156. this._tree = StateTree.createEmpty(StateTransform.createRoot(params && params.rootProps)).asTransient();
  157. const tree = this._tree;
  158. const root = tree.root;
  159. (this.cells as Map<StateTransform.Ref, StateObjectCell>).set(root.ref, {
  160. transform: root,
  161. sourceRef: void 0,
  162. obj: rootObject,
  163. status: 'ok',
  164. errorText: void 0,
  165. params: {
  166. definition: {},
  167. values: {}
  168. },
  169. cache: { }
  170. });
  171. this.globalContext = params && params.globalContext;
  172. }
  173. }
  174. namespace State {
  175. export type Cells = ReadonlyMap<StateTransform.Ref, StateObjectCell>
  176. export type Tree = StateTree
  177. export type Builder = StateBuilder
  178. export interface ObjectEvent {
  179. state: State,
  180. ref: Ref
  181. }
  182. export interface Snapshot {
  183. readonly tree: StateTree.Serialized
  184. }
  185. export interface UpdateOptions {
  186. doNotLogTiming: boolean,
  187. doNotUpdateCurrent: boolean
  188. }
  189. export function create(rootObject: StateObject, params?: { globalContext?: unknown, rootProps?: StateTransform.Props }) {
  190. return new State(rootObject, params);
  191. }
  192. }
  193. const StateUpdateDefaultOptions: State.UpdateOptions = {
  194. doNotLogTiming: false,
  195. doNotUpdateCurrent: false
  196. };
  197. type Ref = StateTransform.Ref
  198. interface UpdateContext {
  199. parent: State,
  200. editInfo: StateBuilder.EditInfo | undefined
  201. errorFree: boolean,
  202. taskCtx: RuntimeContext,
  203. oldTree: StateTree,
  204. tree: TransientTree,
  205. cells: Map<StateTransform.Ref, StateObjectCell>,
  206. spine: StateTreeSpine.Impl,
  207. results: UpdateNodeResult[],
  208. stateChanges: StateTransform.Ref[],
  209. // suppress timing messages
  210. options: State.UpdateOptions,
  211. changed: boolean,
  212. hadError: boolean,
  213. newCurrent?: Ref
  214. }
  215. async function update(ctx: UpdateContext) {
  216. // if only a single node was added/updated, we can skip potentially expensive diffing
  217. const fastTrack = !!(ctx.errorFree && ctx.editInfo && ctx.editInfo.count === 1 && ctx.editInfo.lastUpdate && ctx.editInfo.sourceTree === ctx.oldTree);
  218. let deletes: StateTransform.Ref[], deletedObjects: (StateObject | undefined)[] = [], roots: StateTransform.Ref[];
  219. if (fastTrack) {
  220. deletes = [];
  221. roots = [ctx.editInfo!.lastUpdate!];
  222. } else {
  223. // find all nodes that will definitely be deleted.
  224. // this is done in "post order", meaning that leaves will be deleted first.
  225. deletes = findDeletes(ctx);
  226. const current = ctx.parent.current;
  227. let hasCurrent = false;
  228. for (const d of deletes) {
  229. if (d === current) {
  230. hasCurrent = true;
  231. break;
  232. }
  233. }
  234. if (hasCurrent) {
  235. const newCurrent = findNewCurrent(ctx.oldTree, current, deletes, ctx.cells);
  236. ctx.parent.setCurrent(newCurrent);
  237. }
  238. for (const d of deletes) {
  239. const obj = ctx.cells.has(d) ? ctx.cells.get(d)!.obj : void 0;
  240. ctx.cells.delete(d);
  241. deletedObjects.push(obj);
  242. }
  243. // Find roots where transform version changed or where nodes will be added.
  244. roots = findUpdateRoots(ctx.cells, ctx.tree);
  245. }
  246. let newCellStates: StateTree.CellStates;
  247. if (!ctx.editInfo) {
  248. newCellStates = ctx.tree.cellStatesSnapshot();
  249. syncOldStates(ctx);
  250. }
  251. // Init empty cells where not present
  252. // this is done in "pre order", meaning that "parents" will be created 1st.
  253. const addedCells = initCells(ctx, roots);
  254. // Notify additions of new cells.
  255. for (const cell of addedCells) {
  256. ctx.parent.events.cell.created.next({ state: ctx.parent, ref: cell.transform.ref, cell });
  257. }
  258. for (let i = 0; i < deletes.length; i++) {
  259. const d = deletes[i];
  260. const parent = ctx.oldTree.transforms.get(d).parent;
  261. ctx.parent.events.object.removed.next({ state: ctx.parent, ref: d, obj: deletedObjects[i] });
  262. ctx.parent.events.cell.removed.next({ state: ctx.parent, ref: d, parent: parent });
  263. }
  264. if (deletedObjects.length) deletedObjects = [];
  265. // Set status of cells that will be updated to 'pending'.
  266. initCellStatus(ctx, roots);
  267. // Sequentially update all the subtrees.
  268. for (const root of roots) {
  269. await updateSubtree(ctx, root);
  270. }
  271. // Sync cell states
  272. if (!ctx.editInfo) {
  273. syncNewStates(ctx, newCellStates!);
  274. }
  275. let newCurrent: StateTransform.Ref | undefined = ctx.newCurrent;
  276. // Raise object updated events
  277. for (const update of ctx.results) {
  278. if (update.action === 'created') {
  279. ctx.parent.events.object.created.next({ state: ctx.parent, ref: update.ref, obj: update.obj! });
  280. if (!ctx.newCurrent) {
  281. const transform = ctx.tree.transforms.get(update.ref);
  282. if (!(transform.props && transform.props.isGhost) && update.obj !== StateObject.Null) newCurrent = update.ref;
  283. }
  284. } else if (update.action === 'updated') {
  285. ctx.parent.events.object.updated.next({ state: ctx.parent, ref: update.ref, action: 'in-place', obj: update.obj });
  286. } else if (update.action === 'replaced') {
  287. ctx.parent.events.object.updated.next({ state: ctx.parent, ref: update.ref, action: 'recreate', obj: update.obj, oldObj: update.oldObj });
  288. }
  289. }
  290. if (newCurrent) {
  291. if (!ctx.options.doNotUpdateCurrent) ctx.parent.setCurrent(newCurrent);
  292. } else {
  293. // check if old current or its parent hasn't become null
  294. const current = ctx.parent.current;
  295. const currentCell = ctx.cells.get(current);
  296. if (currentCell && (currentCell.obj === StateObject.Null
  297. || (currentCell.status === 'error' && currentCell.errorText === ParentNullErrorText))) {
  298. newCurrent = findNewCurrent(ctx.oldTree, current, [], ctx.cells);
  299. ctx.parent.setCurrent(newCurrent);
  300. }
  301. }
  302. return deletes.length > 0 || roots.length > 0 || ctx.changed;
  303. }
  304. function findUpdateRoots(cells: Map<StateTransform.Ref, StateObjectCell>, tree: StateTree) {
  305. const findState = { roots: [] as Ref[], cells };
  306. StateTree.doPreOrder(tree, tree.root, findState, findUpdateRootsVisitor);
  307. return findState.roots;
  308. }
  309. function findUpdateRootsVisitor(n: StateTransform, _: any, s: { roots: Ref[], cells: Map<Ref, StateObjectCell> }) {
  310. const cell = s.cells.get(n.ref);
  311. if (!cell || cell.transform.version !== n.version || cell.status === 'error') {
  312. s.roots.push(n.ref);
  313. return false;
  314. }
  315. // nothing below a Null object can be an update root
  316. if (cell && cell.obj === StateObject.Null) return false;
  317. return true;
  318. }
  319. type FindDeletesCtx = { newTree: StateTree, cells: State.Cells, deletes: Ref[] }
  320. function checkDeleteVisitor(n: StateTransform, _: any, ctx: FindDeletesCtx) {
  321. if (!ctx.newTree.transforms.has(n.ref) && ctx.cells.has(n.ref)) ctx.deletes.push(n.ref);
  322. }
  323. function findDeletes(ctx: UpdateContext): Ref[] {
  324. const deleteCtx: FindDeletesCtx = { newTree: ctx.tree, cells: ctx.cells, deletes: [] };
  325. StateTree.doPostOrder(ctx.oldTree, ctx.oldTree.root, deleteCtx, checkDeleteVisitor);
  326. return deleteCtx.deletes;
  327. }
  328. function syncOldStatesVisitor(n: StateTransform, tree: StateTree, oldState: StateTree.CellStates) {
  329. if (oldState.has(n.ref)) {
  330. (tree as TransientTree).updateCellState(n.ref, oldState.get(n.ref));
  331. }
  332. }
  333. function syncOldStates(ctx: UpdateContext) {
  334. StateTree.doPreOrder(ctx.tree, ctx.tree.root, ctx.oldTree.cellStates, syncOldStatesVisitor);
  335. }
  336. function syncNewStatesVisitor(n: StateTransform, tree: StateTree, ctx: { newState: StateTree.CellStates, changes: StateTransform.Ref[] }) {
  337. if (ctx.newState.has(n.ref)) {
  338. const changed = (tree as TransientTree).updateCellState(n.ref, ctx.newState.get(n.ref));
  339. if (changed) {
  340. ctx.changes.push(n.ref);
  341. }
  342. }
  343. }
  344. function syncNewStates(ctx: UpdateContext, newState: StateTree.CellStates) {
  345. StateTree.doPreOrder(ctx.tree, ctx.tree.root, { newState, changes: ctx.stateChanges }, syncNewStatesVisitor);
  346. }
  347. function setCellStatus(ctx: UpdateContext, ref: Ref, status: StateObjectCell.Status, errorText?: string) {
  348. const cell = ctx.cells.get(ref)!;
  349. const changed = cell.status !== status;
  350. cell.status = status;
  351. cell.errorText = errorText;
  352. if (changed) ctx.parent.events.cell.stateUpdated.next({ state: ctx.parent, ref, cellState: ctx.tree.cellStates.get(ref) });
  353. }
  354. function initCellStatusVisitor(t: StateTransform, _: any, ctx: UpdateContext) {
  355. ctx.cells.get(t.ref)!.transform = t;
  356. setCellStatus(ctx, t.ref, 'pending');
  357. }
  358. function initCellStatus(ctx: UpdateContext, roots: Ref[]) {
  359. for (const root of roots) {
  360. StateTree.doPreOrder(ctx.tree, ctx.tree.transforms.get(root), ctx, initCellStatusVisitor);
  361. }
  362. }
  363. type InitCellsCtx = { ctx: UpdateContext, added: StateObjectCell[] }
  364. function initCellsVisitor(transform: StateTransform, _: any, { ctx, added }: InitCellsCtx) {
  365. if (ctx.cells.has(transform.ref)) {
  366. return;
  367. }
  368. const cell: StateObjectCell = {
  369. transform,
  370. sourceRef: void 0,
  371. status: 'pending',
  372. errorText: void 0,
  373. params: void 0,
  374. cache: void 0
  375. };
  376. ctx.cells.set(transform.ref, cell);
  377. added.push(cell);
  378. }
  379. function initCells(ctx: UpdateContext, roots: Ref[]) {
  380. const initCtx: InitCellsCtx = { ctx, added: [] };
  381. for (const root of roots) {
  382. StateTree.doPreOrder(ctx.tree, ctx.tree.transforms.get(root), initCtx, initCellsVisitor);
  383. }
  384. return initCtx.added;
  385. }
  386. function findNewCurrent(tree: StateTree, start: Ref, deletes: Ref[], cells: Map<Ref, StateObjectCell>) {
  387. const deleteSet = new Set(deletes);
  388. return _findNewCurrent(tree, start, deleteSet, cells);
  389. }
  390. function _findNewCurrent(tree: StateTree, ref: Ref, deletes: Set<Ref>, cells: Map<Ref, StateObjectCell>): Ref {
  391. if (ref === StateTransform.RootRef) return ref;
  392. const node = tree.transforms.get(ref)!;
  393. const siblings = tree.children.get(node.parent)!.values();
  394. let prevCandidate: Ref | undefined = void 0, seenRef = false;
  395. while (true) {
  396. const s = siblings.next();
  397. if (s.done) break;
  398. if (deletes.has(s.value)) continue;
  399. const cell = cells.get(s.value);
  400. if (!cell || cell.status === 'error' || cell.obj === StateObject.Null) {
  401. continue;
  402. }
  403. const t = tree.transforms.get(s.value);
  404. if (t.props && t.props.isGhost) continue;
  405. if (s.value === ref) {
  406. seenRef = true;
  407. if (!deletes.has(ref)) prevCandidate = ref;
  408. continue;
  409. }
  410. if (seenRef) return t.ref;
  411. prevCandidate = t.ref;
  412. }
  413. if (prevCandidate) return prevCandidate;
  414. return _findNewCurrent(tree, node.parent, deletes, cells);
  415. }
  416. /** Set status and error text of the cell. Remove all existing objects in the subtree. */
  417. function doError(ctx: UpdateContext, ref: Ref, errorText: string | undefined, silent: boolean) {
  418. if (!silent) {
  419. ctx.hadError = true;
  420. (ctx.parent as any as { errorFree: boolean }).errorFree = false;
  421. }
  422. const cell = ctx.cells.get(ref)!;
  423. if (errorText) {
  424. setCellStatus(ctx, ref, 'error', errorText);
  425. if (!silent) ctx.parent.events.log.next({ type: 'error', timestamp: new Date(), message: errorText });
  426. } else {
  427. cell.params = void 0;
  428. }
  429. if (cell.obj) {
  430. const obj = cell.obj;
  431. cell.obj = void 0;
  432. cell.cache = void 0;
  433. ctx.parent.events.object.removed.next({ state: ctx.parent, ref, obj });
  434. }
  435. // remove the objects in the child nodes if they exist
  436. const children = ctx.tree.children.get(ref).values();
  437. while (true) {
  438. const next = children.next();
  439. if (next.done) return;
  440. doError(ctx, next.value, void 0, silent);
  441. }
  442. }
  443. type UpdateNodeResult =
  444. | { ref: Ref, action: 'created', obj: StateObject }
  445. | { ref: Ref, action: 'updated', obj: StateObject }
  446. | { ref: Ref, action: 'replaced', oldObj?: StateObject, obj: StateObject }
  447. | { action: 'none' }
  448. const ParentNullErrorText = 'Parent is null';
  449. async function updateSubtree(ctx: UpdateContext, root: Ref) {
  450. setCellStatus(ctx, root, 'processing');
  451. let isNull = false;
  452. try {
  453. const start = now();
  454. const update = await updateNode(ctx, root);
  455. const time = now() - start;
  456. if (update.action !== 'none') ctx.changed = true;
  457. setCellStatus(ctx, root, 'ok');
  458. ctx.results.push(update);
  459. if (update.action === 'created') {
  460. isNull = update.obj === StateObject.Null;
  461. if (!isNull && !ctx.options.doNotLogTiming) ctx.parent.events.log.next(LogEntry.info(`Created ${update.obj.label} in ${formatTimespan(time)}.`));
  462. } else if (update.action === 'updated') {
  463. isNull = update.obj === StateObject.Null;
  464. if (!isNull && !ctx.options.doNotLogTiming) ctx.parent.events.log.next(LogEntry.info(`Updated ${update.obj.label} in ${formatTimespan(time)}.`));
  465. } else if (update.action === 'replaced') {
  466. isNull = update.obj === StateObject.Null;
  467. if (!isNull && !ctx.options.doNotLogTiming) ctx.parent.events.log.next(LogEntry.info(`Updated ${update.obj.label} in ${formatTimespan(time)}.`));
  468. }
  469. } catch (e) {
  470. ctx.changed = true;
  471. if (!ctx.hadError) ctx.newCurrent = root;
  472. doError(ctx, root, '' + e, false);
  473. console.error(e);
  474. return;
  475. }
  476. const children = ctx.tree.children.get(root).values();
  477. while (true) {
  478. const next = children.next();
  479. if (next.done) return;
  480. if (isNull) doError(ctx, next.value, void 0, true);
  481. else await updateSubtree(ctx, next.value);
  482. }
  483. }
  484. function resolveParams(ctx: UpdateContext, transform: StateTransform, src: StateObject) {
  485. const prms = transform.transformer.definition.params;
  486. const definition = prms ? prms(src, ctx.parent.globalContext) : {};
  487. const values = transform.params ? transform.params : ParamDefinition.getDefaultValues(definition);
  488. return { definition, values };
  489. }
  490. async function updateNode(ctx: UpdateContext, currentRef: Ref): Promise<UpdateNodeResult> {
  491. const { oldTree, tree } = ctx;
  492. const current = ctx.cells.get(currentRef)!;
  493. const transform = current.transform;
  494. // special case for Root
  495. if (current.transform.ref === StateTransform.RootRef) {
  496. return { action: 'none' };
  497. }
  498. let parentCell = transform.transformer.definition.from.length === 0
  499. ? ctx.cells.get(current.transform.parent)
  500. : StateSelection.findAncestorOfType(tree, ctx.cells, currentRef, transform.transformer.definition.from);
  501. if (!parentCell) {
  502. throw new Error(`No suitable parent found for '${currentRef}'`);
  503. }
  504. ctx.spine.setSurrent(current);
  505. const parent = parentCell.obj!;
  506. current.sourceRef = parentCell.transform.ref;
  507. const params = resolveParams(ctx, transform, parent);
  508. if (!oldTree.transforms.has(currentRef) || !current.params) {
  509. current.params = params;
  510. const obj = await createObject(ctx, current, transform.transformer, parent, params.values);
  511. updateTag(obj, transform);
  512. current.obj = obj;
  513. return { ref: currentRef, action: 'created', obj };
  514. } else {
  515. const oldParams = current.params.values;
  516. const newParams = params.values;
  517. current.params = params;
  518. const updateKind = !!current.obj && current.obj !== StateObject.Null
  519. ? await updateObject(ctx, current, transform.transformer, parent, current.obj!, oldParams, newParams)
  520. : StateTransformer.UpdateResult.Recreate;
  521. switch (updateKind) {
  522. case StateTransformer.UpdateResult.Recreate: {
  523. const oldObj = current.obj;
  524. const newObj = await createObject(ctx, current, transform.transformer, parent, newParams);
  525. updateTag(newObj, transform);
  526. current.obj = newObj;
  527. return { ref: currentRef, action: 'replaced', oldObj, obj: newObj };
  528. }
  529. case StateTransformer.UpdateResult.Updated:
  530. updateTag(current.obj, transform);
  531. return { ref: currentRef, action: 'updated', obj: current.obj! };
  532. case StateTransformer.UpdateResult.Null: {
  533. current.obj = StateObject.Null;
  534. return { ref: currentRef, action: 'updated', obj: current.obj! };
  535. }
  536. default:
  537. return { action: 'none' };
  538. }
  539. }
  540. }
  541. function updateTag(obj: StateObject | undefined, transform: StateTransform) {
  542. if (!obj || obj === StateObject.Null) return;
  543. (obj.tag as string | undefined) = transform.props.tag;
  544. }
  545. function runTask<T>(t: T | Task<T>, ctx: RuntimeContext) {
  546. if (typeof (t as any).runInContext === 'function') return (t as Task<T>).runInContext(ctx);
  547. return t as T;
  548. }
  549. function createObject(ctx: UpdateContext, cell: StateObjectCell, transformer: StateTransformer, a: StateObject, params: any) {
  550. if (!cell.cache) cell.cache = Object.create(null);
  551. return runTask(transformer.definition.apply({ a, params, cache: cell.cache, spine: ctx.spine }, ctx.parent.globalContext), ctx.taskCtx);
  552. }
  553. async function updateObject(ctx: UpdateContext, cell: StateObjectCell, transformer: StateTransformer, a: StateObject, b: StateObject, oldParams: any, newParams: any) {
  554. if (!transformer.definition.update) {
  555. return StateTransformer.UpdateResult.Recreate;
  556. }
  557. if (!cell.cache) cell.cache = Object.create(null);
  558. return runTask(transformer.definition.update({ a, oldParams, b, newParams, cache: cell.cache, spine: ctx.spine }, ctx.parent.globalContext), ctx.taskCtx);
  559. }