From b6b02929fa0af8b4df37446b903efb43e0d37a3f Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Wed, 6 Mar 2019 20:23:02 +0100
Subject: [PATCH] mol-plugin: cache queries

---
 src/mol-plugin/state/transforms/model.ts | 43 ++++++++++++++++++++----
 src/mol-state/object.ts                  |  4 ++-
 src/mol-state/state.ts                   | 39 ++++++++++-----------
 src/mol-state/transformer.ts             |  2 +-
 4 files changed, 59 insertions(+), 29 deletions(-)

diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts
index 7e1a21e3d..fcb8235cc 100644
--- a/src/mol-plugin/state/transforms/model.ts
+++ b/src/mol-plugin/state/transforms/model.ts
@@ -9,13 +9,13 @@ import { parsePDB } from 'mol-io/reader/pdb/parser';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif';
 import { trajectoryFromPDB } from 'mol-model-formats/structure/pdb';
-import { Model, ModelSymmetry, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureSymmetry } from 'mol-model/structure';
+import { Model, ModelSymmetry, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureSymmetry, QueryFn } from 'mol-model/structure';
 import { Assembly } from 'mol-model/structure/model/properties/symmetry';
 import { PluginContext } from 'mol-plugin/context';
 import { MolScriptBuilder } from 'mol-script/language/builder';
 import Expression from 'mol-script/language/expression';
 import { compile } from 'mol-script/runtime/query/compiler';
-import { StateObject } from 'mol-state';
+import { StateObject, StateTransformer } from 'mol-state';
 import { RuntimeContext, Task } from 'mol-task';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { stringToWords } from 'mol-util/string';
@@ -261,14 +261,23 @@ const StructureSelection = PluginStateTransform.BuiltIn({
         label: PD.makeOptional(PD.Text('', { isHidden: true }))
     }
 })({
-    apply({ a, params }) {
-        // TODO: use cache, add "update"
+    apply({ a, params, cache }) {
         const compiled = compile<Sel>(params.query);
+        (cache as { compiled: QueryFn<Sel> }).compiled = compiled;
+
         const result = compiled(new QueryContext(a.data));
         const s = Sel.unionStructure(result);
         if (s.elementCount === 0) return StateObject.Null;
         const props = { label: `${params.label || 'Selection'}`, description: structureDesc(s) };
         return new SO.Molecule.Structure(s, props);
+    },
+    update: ({ a, b, oldParams, newParams, cache }) => {
+        if (oldParams.query !== newParams.query) return StateTransformer.UpdateResult.Recreate;
+
+        if (updateStructureFromQuery((cache as { compiled: QueryFn<Sel> }).compiled, a.data, b, newParams.label)) {
+            return StateTransformer.UpdateResult.Updated;
+        }
+        return StateTransformer.UpdateResult.Null;
     }
 });
 
@@ -283,19 +292,41 @@ const UserStructureSelection = PluginStateTransform.BuiltIn({
         label: PD.makeOptional(PD.Text(''))
     }
 })({
-    apply({ a, params }) {
-        // TODO: use cache, add "update"
+    apply({ a, params, cache }) {
         const parsed = parseMolScript(params.query.expression);
         if (parsed.length === 0) throw new Error('No query');
         const query = transpileMolScript(parsed[0]);
         const compiled = compile<Sel>(query);
+        (cache as { compiled: QueryFn<Sel> }).compiled = compiled;
         const result = compiled(new QueryContext(a.data));
         const s = Sel.unionStructure(result);
         const props = { label: `${params.label || 'Selection'}`, description: structureDesc(s) };
         return new SO.Molecule.Structure(s, props);
+    },
+    update: ({ a, b, oldParams, newParams, cache }) => {
+        if (oldParams.query.language !== newParams.query.language || oldParams.query.expression !== newParams.query.expression) {
+            return StateTransformer.UpdateResult.Recreate;
+        }
+
+        updateStructureFromQuery((cache as { compiled: QueryFn<Sel> }).compiled, a.data, b, newParams.label);
+        return StateTransformer.UpdateResult.Updated;
     }
 });
 
+function updateStructureFromQuery(query: QueryFn<Sel>, src: Structure, obj: SO.Molecule.Structure, label?: string) {
+    const result = query(new QueryContext(src));
+    const s = Sel.unionStructure(result);
+    if (s.elementCount === 0) {
+        return false;
+    }
+
+    obj.label = `${label || 'Selection'}`;
+    obj.description = structureDesc(s);
+    obj.data = s;
+    return true;
+}
+
+
 namespace StructureComplexElement {
     export type Types = 'atomic-sequence' | 'water' | 'atomic-het' | 'spheres'
 }
diff --git a/src/mol-state/object.ts b/src/mol-state/object.ts
index a57faf389..612964518 100644
--- a/src/mol-state/object.ts
+++ b/src/mol-state/object.ts
@@ -69,7 +69,9 @@ interface StateObjectCell<T = StateObject> {
     } | undefined;
 
     errorText?: string,
-    obj?: T
+    obj?: T,
+
+    cache: unknown | undefined
 }
 
 namespace StateObjectCell {
diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts
index 0269c30c1..95fa15c5d 100644
--- a/src/mol-state/state.ts
+++ b/src/mol-state/state.ts
@@ -25,7 +25,6 @@ class State {
     private _tree: TransientTree;
 
     protected errorFree = true;
-    private transformCache = new Map<StateTransform.Ref, unknown>();
 
     private ev = RxEventHelper.create();
 
@@ -165,7 +164,6 @@ class State {
             oldTree,
             tree: _tree,
             cells: this.cells as Map<StateTransform.Ref, StateObjectCell>,
-            transformCache: this.transformCache,
 
             results: [],
             stateChanges: [],
@@ -196,7 +194,8 @@ class State {
             params: {
                 definition: {},
                 values: {}
-            }
+            },
+            cache: { }
         });
 
         this.globalContext = params && params.globalContext;
@@ -244,7 +243,6 @@ interface UpdateContext {
     oldTree: StateTree,
     tree: TransientTree,
     cells: Map<StateTransform.Ref, StateObjectCell>,
-    transformCache: Map<Ref, unknown>,
 
     results: UpdateNodeResult[],
     stateChanges: StateTransform.Ref[],
@@ -288,7 +286,6 @@ async function update(ctx: UpdateContext) {
         for (const d of deletes) {
             const obj = ctx.cells.has(d) ? ctx.cells.get(d)!.obj : void 0;
             ctx.cells.delete(d);
-            ctx.transformCache.delete(d);
             deletedObjects.push(obj);
         }
 
@@ -443,7 +440,8 @@ function initCellsVisitor(transform: StateTransform, _: any, { ctx, added }: Ini
         sourceRef: void 0,
         status: 'pending',
         errorText: void 0,
-        params: void 0
+        params: void 0,
+        cache: void 0
     };
     ctx.cells.set(transform.ref, cell);
     added.push(cell);
@@ -516,8 +514,8 @@ function doError(ctx: UpdateContext, ref: Ref, errorText: string | undefined, si
     if (cell.obj) {
         const obj = cell.obj;
         cell.obj = void 0;
+        cell.cache = void 0;
         ctx.parent.events.object.removed.next({ state: ctx.parent, ref, obj });
-        ctx.transformCache.delete(ref);
     }
 
     // remove the objects in the child nodes if they exist
@@ -608,7 +606,7 @@ async function updateNode(ctx: UpdateContext, currentRef: Ref): Promise<UpdateNo
 
     if (!oldTree.transforms.has(currentRef) || !current.params) {
         current.params = params;
-        const obj = await createObject(ctx, currentRef, transform.transformer, parent, params.values);
+        const obj = await createObject(ctx, current, transform.transformer, parent, params.values);
         updateTag(obj, transform);
         current.obj = obj;
 
@@ -619,13 +617,13 @@ async function updateNode(ctx: UpdateContext, currentRef: Ref): Promise<UpdateNo
         current.params = params;
 
         const updateKind = !!current.obj && current.obj !== StateObject.Null
-            ? await updateObject(ctx, currentRef, transform.transformer, parent, current.obj!, oldParams, newParams)
+            ? await updateObject(ctx, current, transform.transformer, parent, current.obj!, oldParams, newParams)
             : StateTransformer.UpdateResult.Recreate;
 
         switch (updateKind) {
             case StateTransformer.UpdateResult.Recreate: {
                 const oldObj = current.obj;
-                const newObj = await createObject(ctx, currentRef, transform.transformer, parent, newParams);
+                const newObj = await createObject(ctx, current, transform.transformer, parent, newParams);
                 updateTag(newObj, transform);
                 current.obj = newObj;
                 return { ref: currentRef, action: 'replaced', oldObj, obj: newObj };
@@ -633,6 +631,10 @@ async function updateNode(ctx: UpdateContext, currentRef: Ref): Promise<UpdateNo
             case StateTransformer.UpdateResult.Updated:
                 updateTag(current.obj, transform);
                 return { ref: currentRef, action: 'updated', obj: current.obj! };
+            case StateTransformer.UpdateResult.Null: {
+                current.obj = StateObject.Null;
+                return { ref: currentRef, action: 'updated', obj: current.obj! };
+            }
             default:
                 return { action: 'none' };
         }
@@ -649,20 +651,15 @@ function runTask<T>(t: T | Task<T>, ctx: RuntimeContext) {
     return t as T;
 }
 
-function createObject(ctx: UpdateContext, ref: Ref, transformer: StateTransformer, a: StateObject, params: any) {
-    const cache = Object.create(null);
-    ctx.transformCache.set(ref, cache);
-    return runTask(transformer.definition.apply({ a, params, cache }, ctx.parent.globalContext), ctx.taskCtx);
+function createObject(ctx: UpdateContext, cell: StateObjectCell, transformer: StateTransformer, a: StateObject, params: any) {
+    if (!cell.cache) cell.cache = Object.create(null);
+    return runTask(transformer.definition.apply({ a, params, cache: cell.cache }, ctx.parent.globalContext), ctx.taskCtx);
 }
 
-async function updateObject(ctx: UpdateContext, ref: Ref, transformer: StateTransformer, a: StateObject, b: StateObject, oldParams: any, newParams: any) {
+async function updateObject(ctx: UpdateContext, cell: StateObjectCell,  transformer: StateTransformer, a: StateObject, b: StateObject, oldParams: any, newParams: any) {
     if (!transformer.definition.update) {
         return StateTransformer.UpdateResult.Recreate;
     }
-    let cache = ctx.transformCache.get(ref);
-    if (!cache) {
-        cache = Object.create(null);
-        ctx.transformCache.set(ref, cache);
-    }
-    return runTask(transformer.definition.update({ a, oldParams, b, newParams, cache }, ctx.parent.globalContext), ctx.taskCtx);
+    if (!cell.cache) cell.cache = Object.create(null);
+    return runTask(transformer.definition.update({ a, oldParams, b, newParams, cache: cell.cache }, ctx.parent.globalContext), ctx.taskCtx);
 }
\ No newline at end of file
diff --git a/src/mol-state/transformer.ts b/src/mol-state/transformer.ts
index b6eaa8c91..61df2c900 100644
--- a/src/mol-state/transformer.ts
+++ b/src/mol-state/transformer.ts
@@ -55,7 +55,7 @@ namespace Transformer {
         newParams: P
     }
 
-    export enum UpdateResult { Unchanged, Updated, Recreate }
+    export enum UpdateResult { Unchanged, Updated, Recreate, Null }
 
     /** Specify default control descriptors for the parameters */
     // export type ParamsDefinition<A extends StateObject = StateObject, P = any> = (a: A, globalCtx: unknown) => { [K in keyof P]: PD.Any }
-- 
GitLab