Bladeren bron

mol-state: fixed update queuing

David Sehnal 6 jaren geleden
bovenliggende
commit
dfd1588cbb

+ 4 - 4
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, {});

+ 3 - 3
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);

+ 65 - 22
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;

+ 2 - 3
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
+}

+ 6 - 4
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);