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