From 8eaac7e3f981c673ddf49538456852e1f4780e4b Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Fri, 9 Nov 2018 18:25:11 +0100
Subject: [PATCH] mol-state: selection & removed separate context

---
 src/mol-plugin/context.ts              | 14 ++---
 src/mol-plugin/ui/controls.tsx         |  6 ++-
 src/mol-plugin/ui/state-tree.tsx       |  2 +-
 src/mol-state/context.ts               | 46 ----------------
 src/mol-state/index.ts                 |  4 +-
 src/mol-state/state.ts                 | 75 ++++++++++++++++++--------
 src/mol-state/{ => state}/selection.ts | 45 ++++++++--------
 src/perf-tests/state.ts                | 15 +++---
 8 files changed, 95 insertions(+), 112 deletions(-)
 delete mode 100644 src/mol-state/context.ts
 rename src/mol-state/{ => state}/selection.ts (83%)

diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts
index b59d2610f..f45839d22 100644
--- a/src/mol-plugin/context.ts
+++ b/src/mol-plugin/context.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { StateTree, StateSelection, Transformer, Transform } from 'mol-state';
+import { StateTree, Transformer, Transform } from 'mol-state';
 import { Canvas3D } from 'mol-canvas3d/canvas3d';
 import { StateTransforms } from './state/transforms';
 import { PluginStateObject as PSO } from './state/base';
@@ -28,15 +28,15 @@ export class PluginContext {
 
     readonly events = {
         state: {
-            data: this.state.data.context.events,
-            behavior: this.state.behavior.context.events
+            data: this.state.data.events,
+            behavior: this.state.behavior.events
         }
     };
 
     readonly behaviors = {
         state: {
-            data: this.state.data.context.behaviors,
-            behavior: this.state.behavior.context.behaviors
+            data: this.state.data.behaviors,
+            behavior: this.state.behavior.behaviors
         },
         canvas: {
             highlightLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any }>({ loci: EmptyLoci }),
@@ -151,7 +151,7 @@ export class PluginContext {
     }
 
     _test_centerView() {
-        const sel = StateSelection.select(StateSelection.root().subtree().ofType(SO.Molecule.Structure.type), this.state.data);
+        const sel = this.state.data.select(q => q.root.subtree().ofType(SO.Molecule.Structure.type));
         if (!sel.length) return;
 
         const center = (sel[0].obj! as SO.Molecule.Structure).data.boundary.sphere.center;
@@ -160,7 +160,7 @@ export class PluginContext {
     }
 
     _test_nextModel() {
-        const models = StateSelection.select('models', this.state.data)[0].obj as SO.Molecule.Models;
+        const models = this.state.data.select('models')[0].obj as SO.Molecule.Models;
         const idx = (this.state.data.tree.nodes.get('structure')!.params as Transformer.Params<typeof StateTransforms.Model.CreateStructureFromModel>).modelIndex;
         const newTree = StateTree.updateParams(this.state.data.tree, 'structure', { modelIndex: (idx + 1) % models.data.length });
         return this.state.updateData(newTree);
diff --git a/src/mol-plugin/ui/controls.tsx b/src/mol-plugin/ui/controls.tsx
index e9a92645e..f10434287 100644
--- a/src/mol-plugin/ui/controls.tsx
+++ b/src/mol-plugin/ui/controls.tsx
@@ -6,7 +6,7 @@
 
 import * as React from 'react';
 import { PluginContext } from '../context';
-import { Transform, Transformer, StateSelection } from 'mol-state';
+import { Transform, Transformer } from 'mol-state';
 import { ParametersComponent } from 'mol-app/component/parameters';
 
 export class Controls extends React.Component<{ plugin: PluginContext }, { id: string }> {
@@ -99,7 +99,9 @@ export class _test_UpdateTransform extends React.Component<{ plugin: PluginConte
         const def = this.getTransform().transformer.definition;
         if (!def.params || !def.params.controls) return void 0;
 
-        const src = StateSelection.ancestorOfType(this.props.nodeRef, def.from).select(this.props.plugin.state.data)[0];
+        const src = this.props.plugin.state.data.select(q => q.byRef(this.props.nodeRef).ancestorOfType(def.from))[0];
+
+        // StateSelection.ancestorOfType(this.props.nodeRef, def.from).select(this.props.plugin.state.data)[0];
 
         console.log(src, def.from);
 
diff --git a/src/mol-plugin/ui/state-tree.tsx b/src/mol-plugin/ui/state-tree.tsx
index 83daeb922..3dc89ed53 100644
--- a/src/mol-plugin/ui/state-tree.tsx
+++ b/src/mol-plugin/ui/state-tree.tsx
@@ -13,7 +13,7 @@ import { PluginCommands } from 'mol-plugin/command';
 export class StateTree extends React.Component<{ plugin: PluginContext, state: State }, { }> {
     componentDidMount() {
         // TODO: move to constructor?
-        this.props.state.context.events.updated.subscribe(() => this.forceUpdate());
+        this.props.state.events.updated.subscribe(() => this.forceUpdate());
     }
     render() {
         // const n = this.props.plugin.state.data.tree.nodes.get(this.props.plugin.state.data.tree.rootRef)!;
diff --git a/src/mol-state/context.ts b/src/mol-state/context.ts
deleted file mode 100644
index 7680623c0..000000000
--- a/src/mol-state/context.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { StateObject } from './object';
-import { Transform } from './transform';
-import { RxEventHelper } from 'mol-util/rx-event-helper';
-
-export { StateContext }
-
-class StateContext {
-    private ev = RxEventHelper.create();
-
-    readonly events = {
-        object: {
-            stateChanged: this.ev<{ ref: Transform.Ref }>(),
-            propsChanged: this.ev<{ ref: Transform.Ref, newProps: unknown }>(),
-
-            updated: this.ev<{ ref: Transform.Ref, obj?: StateObject }>(),
-            replaced: this.ev<{ ref: Transform.Ref, oldObj?: StateObject, newObj?: StateObject }>(),
-            created: this.ev<{ ref: Transform.Ref, obj: StateObject }>(),
-            removed: this.ev<{ ref: Transform.Ref, obj?: StateObject }>(),
-
-            currentChanged: this.ev<{ ref: Transform.Ref }>()
-        },
-        warn: this.ev<string>(),
-        updated: this.ev<void>()
-    };
-
-    readonly behaviors = {
-        currentObject: this.ev.behavior<{ ref: Transform.Ref }>(void 0 as any)
-    };
-
-    readonly globalContext: unknown;
-
-    dispose() {
-        this.ev.dispose();
-    }
-
-    constructor(params: { globalContext: unknown }) {
-        this.globalContext = params.globalContext;
-        this.behaviors.currentObject.next({ ref: Transform.RootRef });
-    }
-}
\ No newline at end of file
diff --git a/src/mol-state/index.ts b/src/mol-state/index.ts
index 89b5ea1a5..8ef37d2fd 100644
--- a/src/mol-state/index.ts
+++ b/src/mol-state/index.ts
@@ -8,6 +8,4 @@ export * from './object'
 export * from './state'
 export * from './transformer'
 export * from './tree'
-export * from './context'
-export * from './transform'
-export * from './selection'
\ No newline at end of file
+export * from './transform'
\ No newline at end of file
diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts
index f1b04ccb7..198737c9b 100644
--- a/src/mol-state/state.ts
+++ b/src/mol-state/state.ts
@@ -8,10 +8,10 @@ import { StateObject, StateObjectCell } from './object';
 import { StateTree } from './tree';
 import { Transform } from './transform';
 import { Transformer } from './transformer';
-import { StateContext } from './context';
 import { UUID } from 'mol-util';
 import { RuntimeContext, Task } from 'mol-task';
-import { StateSelection } from './selection';
+import { StateSelection } from './state/selection';
+import { RxEventHelper } from 'mol-util/rx-event-helper';
 
 export { State }
 
@@ -20,11 +20,35 @@ class State {
     private _current: Transform.Ref = this._tree.root.ref;
     private transformCache = new Map<Transform.Ref, unknown>();
 
+    private ev = RxEventHelper.create();
+
+    readonly globalContext: unknown = void 0;
+    readonly events = {
+        object: {
+            stateChanged: this.ev<{ ref: Transform.Ref }>(),
+            propsChanged: this.ev<{ ref: Transform.Ref, newProps: unknown }>(),
+
+            updated: this.ev<{ ref: Transform.Ref, obj?: StateObject }>(),
+            replaced: this.ev<{ ref: Transform.Ref, oldObj?: StateObject, newObj?: StateObject }>(),
+            created: this.ev<{ ref: Transform.Ref, obj: StateObject }>(),
+            removed: this.ev<{ ref: Transform.Ref, obj?: StateObject }>(),
+
+            currentChanged: this.ev<{ ref: Transform.Ref }>()
+        },
+        warn: this.ev<string>(),
+        updated: this.ev<void>()
+    };
+
+    readonly behaviors = {
+        currentObject: this.ev.behavior<{ ref: Transform.Ref }>({ ref: Transform.RootRef })
+    };
+
     get tree() { return this._tree; }
     get current() { return this._current; }
 
     readonly cells: State.Cells = new Map();
-    readonly context: StateContext;
+
+    // readonly context: StateContext;
 
     getSnapshot(): State.Snapshot {
         return { tree: StateTree.toJSON(this._tree) };
@@ -37,7 +61,7 @@ class State {
 
     setCurrent(ref: Transform.Ref) {
         this._current = ref;
-        this.context.behaviors.currentObject.next({ ref });
+        this.behaviors.currentObject.next({ ref });
     }
 
     updateCellState(ref: Transform.Ref, state?: Partial<StateObjectCell.State>) {
@@ -45,15 +69,21 @@ class State {
     }
 
     dispose() {
-        this.context.dispose();
+        this.ev.dispose();
     }
 
-    select(selector: StateSelection.Selector) {
-        return StateSelection.select(selector, this);
+    /**
+     * Select Cells by ref or a query generated on the fly.
+     * @example state.select('test')
+     * @example state.select(q => q.byRef('test').subtree())
+     */
+    select(selector: Transform.Ref | ((q: typeof StateSelection.Generators) => StateSelection.Selector)) {
+        if (typeof selector === 'string') return StateSelection.select(selector, this);
+        return StateSelection.select(selector(StateSelection.Generators), this)
     }
 
-    get selector() {
-        return StateSelection;
+    query(q: StateSelection.Query) {
+        return q(this);
     }
 
     update(tree: StateTree): Task<void> {
@@ -64,7 +94,7 @@ class State {
                 this._tree = tree;
 
                 const ctx: UpdateContext = {
-                    stateCtx: this.context,
+                    parent: this,
                     taskCtx,
                     oldTree,
                     tree,
@@ -74,7 +104,7 @@ class State {
                 // TODO: have "cancelled" error? Or would this be handled automatically?
                 await update(ctx);
             } finally {
-                this.context.events.updated.next();
+                this.events.updated.next();
             }
         });
     }
@@ -91,9 +121,7 @@ class State {
             state: { ...StateObjectCell.DefaultState }
         });
 
-        this.context = new StateContext({
-            globalContext: params && params.globalContext
-        });
+        this.globalContext = params && params.globalContext;
     }
 }
 
@@ -112,7 +140,8 @@ namespace State {
 type Ref = Transform.Ref
 
 interface UpdateContext {
-    stateCtx: StateContext,
+    parent: State,
+
     taskCtx: RuntimeContext,
     oldTree: StateTree,
     tree: StateTree,
@@ -127,7 +156,7 @@ async function update(ctx: UpdateContext) {
         const obj = ctx.cells.has(d) ? ctx.cells.get(d)!.obj : void 0;
         ctx.cells.delete(d);
         ctx.transformCache.delete(d);
-        ctx.stateCtx.events.object.removed.next({ ref: d, obj });
+        ctx.parent.events.object.removed.next({ ref: d, obj });
         // TODO: handle current object change
     }
 
@@ -169,7 +198,7 @@ function setObjectStatus(ctx: UpdateContext, ref: Ref, status: StateObjectCell.S
     const changed = obj.status !== status;
     obj.status = status;
     obj.errorText = errorText;
-    if (changed) ctx.stateCtx.events.object.stateChanged.next({ ref });
+    if (changed) ctx.parent.events.object.stateChanged.next({ ref });
 }
 
 function _initObjectStatusVisitor(t: Transform, _: any, ctx: UpdateContext) {
@@ -208,7 +237,7 @@ function doError(ctx: UpdateContext, ref: Ref, errorText: string) {
     setObjectStatus(ctx, ref, 'error', errorText);
     const wrap = ctx.cells.get(ref)!;
     if (wrap.obj) {
-        ctx.stateCtx.events.object.removed.next({ ref });
+        ctx.parent.events.object.removed.next({ ref });
         ctx.transformCache.delete(ref);
         wrap.obj = void 0;
     }
@@ -240,11 +269,11 @@ async function updateSubtree(ctx: UpdateContext, root: Ref) {
         const update = await updateNode(ctx, root);
         setObjectStatus(ctx, root, 'ok');
         if (update.action === 'created') {
-            ctx.stateCtx.events.object.created.next({ ref: root, obj: update.obj! });
+            ctx.parent.events.object.created.next({ ref: root, obj: update.obj! });
         } else if (update.action === 'updated') {
-            ctx.stateCtx.events.object.updated.next({ ref: root, obj: update.obj });
+            ctx.parent.events.object.updated.next({ ref: root, obj: update.obj });
         } else if (update.action === 'replaced') {
-            ctx.stateCtx.events.object.replaced.next({ ref: root, oldObj: update.oldObj, newObj: update.newObj });
+            ctx.parent.events.object.replaced.next({ ref: root, oldObj: update.oldObj, newObj: update.newObj });
         }
     } catch (e) {
         doError(ctx, root, '' + e);
@@ -305,7 +334,7 @@ function runTask<T>(t: T | Task<T>, ctx: RuntimeContext) {
 function createObject(ctx: UpdateContext, ref: Ref, transformer: Transformer, a: StateObject, params: any) {
     const cache = {};
     ctx.transformCache.set(ref, cache);
-    return runTask(transformer.definition.apply({ a, params, cache }, ctx.stateCtx.globalContext), ctx.taskCtx);
+    return runTask(transformer.definition.apply({ a, params, cache }, ctx.parent.globalContext), ctx.taskCtx);
 }
 
 async function updateObject(ctx: UpdateContext, ref: Ref, transformer: Transformer, a: StateObject, b: StateObject, oldParams: any, newParams: any) {
@@ -317,5 +346,5 @@ async function updateObject(ctx: UpdateContext, ref: Ref, transformer: Transform
         cache = {};
         ctx.transformCache.set(ref, cache);
     }
-    return runTask(transformer.definition.update({ a, oldParams, b, newParams, cache }, ctx.stateCtx.globalContext), ctx.taskCtx);
+    return runTask(transformer.definition.update({ a, oldParams, b, newParams, cache }, ctx.parent.globalContext), ctx.taskCtx);
 }
\ No newline at end of file
diff --git a/src/mol-state/selection.ts b/src/mol-state/state/selection.ts
similarity index 83%
rename from src/mol-state/selection.ts
rename to src/mol-state/state/selection.ts
index 47014c62f..9bbacebc3 100644
--- a/src/mol-state/selection.ts
+++ b/src/mol-state/state/selection.ts
@@ -4,10 +4,10 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { StateObject, StateObjectCell } from './object';
-import { State } from './state';
-import { StateTree } from './tree';
-import { Transform } from './transform';
+import { StateObject, StateObjectCell } from '../object';
+import { State } from '../state';
+import { StateTree } from '../tree';
+import { Transform } from '../transform';
 
 namespace StateSelection {
     export type Selector = Query | Builder | string | StateObjectCell;
@@ -19,12 +19,12 @@ namespace StateSelection {
     }
 
     export function compile(s: Selector): Query {
-        const selector = s ? s : root();
+        const selector = s ? s : Generators.root;
         let query: Query;
         if (isBuilder(selector)) query = (selector as any).compile();
-        else if (isObj(selector)) query = (byValue(selector) as any).compile();
+        else if (isObj(selector)) query = (Generators.byValue(selector) as any).compile();
         else if (isQuery(selector)) query = selector;
-        else query = (byRef(selector as string) as any).compile();
+        else query = (Generators.byRef(selector as string) as any).compile();
         return query;
     }
 
@@ -58,8 +58,8 @@ namespace StateSelection {
     }
 
     const BuilderPrototype: any = {
-        select(state: State) {
-            return select(this, state);
+        select(state?: State) {
+            return select(this, state || this.state);
         }
     };
 
@@ -71,23 +71,24 @@ namespace StateSelection {
         return Object.create(BuilderPrototype, { compile: { writable: false, configurable: false, value: compile } });
     }
 
-    export function root() { return build(() => (state: State) => [state.cells.get(state.tree.root.ref)!]) }
+    export namespace Generators {
+        export const root = build(() => (state: State) => [state.cells.get(state.tree.root.ref)!]);
 
+        export function byRef(...refs: Transform.Ref[]) {
+            return build(() => (state: State) => {
+                const ret: StateObjectCell[] = [];
+                for (const ref of refs) {
+                    const n = state.cells.get(ref);
+                    if (!n) continue;
+                    ret.push(n);
+                }
+                return ret;
+            });
+        }
 
-    export function byRef(...refs: Transform.Ref[]) {
-        return build(() => (state: State) => {
-            const ret: StateObjectCell[] = [];
-            for (const ref of refs) {
-                const n = state.cells.get(ref);
-                if (!n) continue;
-                ret.push(n);
-            }
-            return ret;
-        });
+        export function byValue(...objects: StateObjectCell[]) { return build(() => (state: State) => objects); }
     }
 
-    export function byValue(...objects: StateObjectCell[]) { return build(() => (state: State) => objects); }
-
     registerModifier('flatMap', flatMap);
     export function flatMap(b: Selector, f: (obj: StateObjectCell, state: State) => CellSeq) {
         const q = compile(b);
diff --git a/src/perf-tests/state.ts b/src/perf-tests/state.ts
index d141d85fa..052ac9952 100644
--- a/src/perf-tests/state.ts
+++ b/src/perf-tests/state.ts
@@ -1,4 +1,4 @@
-import { State, StateObject, StateTree, Transformer, StateSelection } from 'mol-state';
+import { State, StateObject, StateTree, Transformer } from 'mol-state';
 import { Task } from 'mol-task';
 import * as util from 'util';
 
@@ -65,11 +65,11 @@ export async function runTask<A>(t: A | Task<A>): Promise<A> {
 }
 
 function hookEvents(state: State) {
-    state.context.events.object.created.subscribe(e => console.log('created:', e.ref));
-    state.context.events.object.removed.subscribe(e => console.log('removed:', e.ref));
-    state.context.events.object.replaced.subscribe(e => console.log('replaced:', e.ref));
-    state.context.events.object.stateChanged.subscribe(e => console.log('stateChanged:', e.ref, state.cells.get(e.ref)!.status));
-    state.context.events.object.updated.subscribe(e => console.log('updated:', e.ref));
+    state.events.object.created.subscribe(e => console.log('created:', e.ref));
+    state.events.object.removed.subscribe(e => console.log('removed:', e.ref));
+    state.events.object.replaced.subscribe(e => console.log('replaced:', e.ref));
+    state.events.object.stateChanged.subscribe(e => console.log('stateChanged:', e.ref, state.cells.get(e.ref)!.status));
+    state.events.object.updated.subscribe(e => console.log('updated:', e.ref));
 }
 
 export async function testState() {
@@ -107,8 +107,7 @@ export async function testState() {
 
     console.log('----------------');
 
-    const q = StateSelection.byRef('square').parent();
-    const sel = StateSelection.select(q, state);
+    const sel = state.select('square');
     console.log(sel);
 }
 
-- 
GitLab