diff --git a/src/apps/viewer/extensions/jolecule.ts b/src/apps/viewer/extensions/jolecule.ts index 839ad61ff5262b63a51a89a0f811ee8aca1e91f3..cdf0026b39a4201f802fe01f18bc736c2ca0c71e 100644 --- a/src/apps/viewer/extensions/jolecule.ts +++ b/src/apps/viewer/extensions/jolecule.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { StateTree, StateBuilder, StateAction } from 'mol-state'; +import { StateTree, StateBuilder, StateAction, State } from 'mol-state'; import { StateTransforms } from 'mol-plugin/state/transforms'; import { createModelTree, complexRepresentation } from 'mol-plugin/state/actions/structure'; import { PluginContext } from 'mol-plugin/context'; @@ -34,7 +34,7 @@ export const CreateJoleculeState = StateAction.build({ await PluginCommands.State.RemoveObject.dispatch(plugin, { state, ref }); plugin.state.snapshots.clear(); - const template = createTemplate(plugin, state.tree, id); + const template = createTemplate(plugin, state, id); const snapshots = data.map((e, idx) => buildSnapshot(plugin, template, { e, idx, len: data.length })); for (const s of snapshots) { plugin.state.snapshots.add(s); @@ -55,8 +55,8 @@ interface JoleculeSnapshot { text: string } -function createTemplate(plugin: PluginContext, tree: StateTree, id: string) { - const b = new StateBuilder.Root(tree); +function createTemplate(plugin: PluginContext, state: State, id: string) { + const b = new StateBuilder.Root(state.tree); const data = b.toRoot().apply(StateTransforms.Data.Download, { url: `https://www.ebi.ac.uk/pdbe/static/entry/${id}_updated.cif` }, { props: { isGhost: true }}); const model = createModelTree(data, 'cif'); const structure = model.apply(StateTransforms.Model.StructureFromModel, {}); diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 6c70a40717a880523f4a33b7fbcefd20ade02aa0..66563660ade1dd87bd95a428fd867f2d20618baa 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -58,7 +58,7 @@ class State { get cellStates() { return (this._tree as StateTree).cellStates; } get current() { return this.behaviors.currentObject.value.ref; } - build() { return new StateBuilder.Root(this._tree); } + build() { return new StateBuilder.Root(this.tree, this); } readonly cells: State.Cells = new Map(); private spine = new StateTreeSpine.Impl(this.cells); @@ -134,8 +134,8 @@ class State { updateTree(tree: StateTree | StateBuilder, options?: Partial<State.UpdateOptions>): Task<any> { const params: UpdateParams = { tree, options }; return Task.create('Update Tree', async taskCtx => { - const ok = await this.updateQueue.enqueue(params); - if (!ok) return; + const removed = await this.updateQueue.enqueue(params); + if (!removed) return; try { const ret = await this._updateTree(taskCtx, params); diff --git a/src/mol-state/state/builder.ts b/src/mol-state/state/builder.ts index 46ea7bdaf41fe4d9ae88aecce2c04b50b86b5d4e..6e9e48a10a680457d27f4efa86e602a0450f032f 100644 --- a/src/mol-state/state/builder.ts +++ b/src/mol-state/state/builder.ts @@ -26,8 +26,42 @@ namespace StateBuilder { } interface BuildState { + state: State | undefined, tree: TransientTree, - editInfo: EditInfo + editInfo: EditInfo, + actions: Action[] + } + + type Action = + | { kind: 'add', transform: StateTransform } + | { kind: 'update', ref: string, params: any } + | { kind: 'delete', ref: string } + | { kind: 'insert', ref: string, transform: StateTransform, initialCellState?: Partial<StateObjectCell.State> } + + function buildTree(state: BuildState) { + if (!state.state || state.state.tree === state.editInfo.sourceTree) { + return state.tree.asImmutable(); + } + + // The tree has changed in the meantime, we need to reapply the changes! + const tree = state.state.tree.asTransient(); + for (const a of state.actions) { + switch (a.kind) { + case 'add': tree.add(a.transform); break; + case 'update': tree.setParams(a.ref, a.params); break; + case 'delete': tree.remove(a.ref); break; + case 'insert': { + const children = tree.children.get(a.ref).toArray(); + tree.add(a.transform, a.initialCellState); + for (const c of children) { + tree.changeParent(c, a.transform.ref); + } + break; + } + } + } + state.editInfo.sourceTree = state.tree; + return tree.asImmutable(); } export function is(obj: any): obj is StateBuilder { @@ -52,10 +86,11 @@ namespace StateBuilder { delete(ref: StateTransform.Ref) { this.editInfo.count++; this.state.tree.remove(ref); + this.state.actions.push({ kind: 'delete', ref }); return this; } - getTree(): StateTree { return this.state.tree.asImmutable(); } - constructor(tree: StateTree) { this.state = { tree: tree.asTransient(), editInfo: { sourceTree: tree, count: 0, lastUpdate: void 0 } } } + getTree(): StateTree { return buildTree(this.state); } //this.state.tree.asImmutable(); } + constructor(tree: StateTree, state?: State) { this.state = { state, tree: tree.asTransient(), actions: [], editInfo: { sourceTree: tree, count: 0, lastUpdate: void 0 } } } } export class To<A extends StateObject, T extends StateTransformer = StateTransformer> implements StateBuilder { @@ -72,6 +107,9 @@ namespace StateBuilder { this.state.tree.add(t, initialCellState); this.editInfo.count++; this.editInfo.lastUpdate = t.ref; + + this.state.actions.push({ kind: 'add', transform: t }); + return new To(this.state, t.ref, this.root); } @@ -100,30 +138,33 @@ namespace StateBuilder { this.editInfo.count++; this.editInfo.lastUpdate = t.ref; + + this.state.actions.push({ kind: 'insert', ref: this.ref, transform: t, initialCellState }); + return new To(this.state, t.ref, this.root); } - /** - * Updates a transform in an instantiated tree, passing the transform's source into the providers - * - * This only works if the transform source is NOT updated by the builder. Use at own discression. - */ - updateInState<T extends StateTransformer<any, A, any>>(transformer: T, state: State, provider: (old: StateTransformer.Params<T>, a: StateTransformer.From<T>) => StateTransformer.Params<T>): Root { - const old = this.state.tree.transforms.get(this.ref)!; - const cell = state.cells.get(this.ref); - if (!cell || !cell.sourceRef) throw new Error('Source cell is not present in the tree.'); - const parent = state.cells.get(cell.sourceRef); - if (!parent || !parent.obj) throw new Error('Parent cell is not present or computed.'); + // /** + // * Updates a transform in an instantiated tree, passing the transform's source into the providers + // * + // * This only works if the transform source is NOT updated by the builder. Use at own discression. + // */ + // updateInState<T extends StateTransformer<any, A, any>>(transformer: T, state: State, provider: (old: StateTransformer.Params<T>, a: StateTransformer.From<T>) => StateTransformer.Params<T>): Root { + // const old = this.state.tree.transforms.get(this.ref)!; + // const cell = state.cells.get(this.ref); + // if (!cell || !cell.sourceRef) throw new Error('Source cell is not present in the tree.'); + // const parent = state.cells.get(cell.sourceRef); + // if (!parent || !parent.obj) throw new Error('Parent cell is not present or computed.'); - const params = provider(old.params as any, parent.obj as any); + // const params = provider(old.params as any, parent.obj as any); - if (this.state.tree.setParams(this.ref, params)) { - this.editInfo.count++; - this.editInfo.lastUpdate = this.ref; - } + // if (this.state.tree.setParams(this.ref, params)) { + // this.editInfo.count++; + // this.editInfo.lastUpdate = this.ref; + // } - return this.root; - } + // return this.root; + // } update<T extends StateTransformer<any, A, any>>(transformer: T, params: (old: StateTransformer.Params<T>) => StateTransformer.Params<T>): Root update(params: StateTransformer.Params<T> | ((old: StateTransformer.Params<T>) => StateTransformer.Params<T>)): Root @@ -143,6 +184,8 @@ namespace StateBuilder { this.editInfo.lastUpdate = this.ref; } + this.state.actions.push({ kind: 'update', ref: this.ref, params }); + return this.root; } @@ -150,7 +193,7 @@ namespace StateBuilder { toRoot<A extends StateObject>() { return this.root.toRoot<A>(); } delete(ref: StateTransform.Ref) { return this.root.delete(ref); } - getTree(): StateTree { return this.state.tree.asImmutable(); } + getTree(): StateTree { return buildTree(this.state); } //this.state.tree.asImmutable(); } constructor(private state: BuildState, ref: StateTransform.Ref, private root: Root) { this.ref = ref; diff --git a/src/mol-util/array.ts b/src/mol-util/array.ts index ac3b10a8132ef7d885a482606b2488fb169e46c1..078ab5319b4db6bdd612c7655390a8e4a0e2225f 100644 --- a/src/mol-util/array.ts +++ b/src/mol-util/array.ts @@ -69,9 +69,8 @@ export function arrayRemoveInPlace<T>(xs: T[], x: T) { if (!found) return false; i++; for (; i < l; i++) { - xs[i] = xs[i - 1]; + xs[i - 1] = xs[i]; } xs.pop(); return true; -} -(window as any).arrayRem = arrayRemoveInPlace \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-util/async-queue.ts b/src/mol-util/async-queue.ts index a2ef601c3f6ab3cd54d436a1e446b2373626538a..5e01ff69f4e434b6821330fa134040a03520ae2f 100644 --- a/src/mol-util/async-queue.ts +++ b/src/mol-util/async-queue.ts @@ -9,7 +9,7 @@ import { Subject } from 'rxjs'; export class AsyncQueue<T> { private queue: T[] = []; - private signal = new Subject<{ v: T, removed: boolean }>(); + private signal = new Subject<{ v: T, stillPresent: boolean }>(); enqueue(v: T) { this.queue.push(v); @@ -19,19 +19,21 @@ export class AsyncQueue<T> { handled(v: T) { arrayRemoveInPlace(this.queue, v); - if (this.queue.length > 0) this.signal.next({ v: this.queue[0], removed: false }); + if (this.queue.length > 0) { + this.signal.next({ v: this.queue[0], stillPresent: true }); + } } remove(v: T) { const rem = arrayRemoveInPlace(this.queue, v); if (rem) - this.signal.next({ v, removed: true }) + this.signal.next({ v, stillPresent: false }) return rem; } private waitFor(t: T): Promise<boolean> { return new Promise(res => { - const sub = this.signal.subscribe(({ v, removed }) => { + const sub = this.signal.subscribe(({ v, stillPresent: removed }) => { if (v === t) { sub.unsubscribe(); res(removed);