Browse Source

mol-state and mol-plugin refactoring

David Sehnal 6 years ago
parent
commit
fd3aade53d

+ 12 - 16
src/mol-plugin/context.ts

@@ -4,32 +4,28 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { State, StateTree, StateSelection, Transformer } from 'mol-state';
+import { StateTree, StateSelection, Transformer } from 'mol-state';
 import Canvas3D from 'mol-canvas3d/canvas3d';
 import { StateTransforms } from './state/transforms';
 import { PluginStateObjects as SO } from './state/objects';
 import { RxEventHelper } from 'mol-util/rx-event-helper';
+import { PluginState } from './state';
 
 export class PluginContext {
     private disposed = false;
-    private _events = new RxEventHelper();
+    private ev = RxEventHelper.create();
 
-    state = {
-        data: State.create(new SO.Root({ label: 'Root' }, { })),
-        // behaviour: State,
-        // plugin: State
-    };
+    readonly state = new PluginState();
 
-    // TODO: better events
-    events = {
-        stateUpdated: this._events.create<undefined>()
+    readonly events = {
+        stateUpdated: this.ev<undefined>()
     };
 
-    canvas3d: Canvas3D;
+    readonly canvas3d: Canvas3D;
 
     initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) {
         try {
-            this.canvas3d = Canvas3D.create(canvas, container);
+            (this.canvas3d as Canvas3D) = Canvas3D.create(canvas, container);
             this.canvas3d.animate();
             console.log('canvas3d created');
             return true;
@@ -42,7 +38,8 @@ export class PluginContext {
     dispose() {
         if (this.disposed) return;
         this.canvas3d.dispose();
-        this._events.dispose();
+        this.ev.dispose();
+        this.state.dispose();
         this.disposed = true;
     }
 
@@ -60,9 +57,8 @@ export class PluginContext {
     }
 
     async _test_updateStateData(tree: StateTree) {
-        const newState = await State.update(this.state.data, tree).run(p => console.log(p), 250);
-        this.state.data = newState;
-        console.log(newState);
+        await this.state.data.update(tree).run(p => console.log(p), 250);
+        console.log(this.state.data);
         this.events.stateUpdated.next();
     }
 

+ 34 - 0
src/mol-plugin/state.ts

@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { State } from 'mol-state';
+import { PluginStateObjects as SO } from './state/objects';
+
+export { PluginState }
+
+class PluginState {
+    readonly data = State.create(new SO.Root({ label: 'Root' }, { }));
+
+    getSnapshot(): PluginState.Snapshot {
+        throw 'nyi';
+    }
+
+    setSnapshot(snapshot: PluginState.Snapshot) {
+        throw 'nyi';
+    }
+
+    setDataSnapshot(snapshot: State.Snapshot) {
+        throw 'nyi';
+    }
+
+    dispose() {
+        this.data.dispose();
+    }
+}
+
+namespace PluginState {
+    export interface Snapshot { }
+}

+ 26 - 34
src/mol-state/context.ts

@@ -4,45 +4,37 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Subject } from 'rxjs'
 import { StateObject } from './object';
 import { Transform } from './transform';
+import { RxEventHelper } from 'mol-util/rx-event-helper';
 
-interface StateContext {
-    events: {
+export { StateContext }
+
+class StateContext {
+    private ev = RxEventHelper.create();
+
+    events = {
         object: {
-            stateChanged: Subject<{ ref: Transform.Ref }>,
-            propsChanged: Subject<{ ref: Transform.Ref, newProps: unknown }>,
+            stateChanged: this.ev<{ ref: Transform.Ref }>(),
+            propsChanged: this.ev<{ ref: Transform.Ref, newProps: unknown }>(),
 
-            updated: Subject<{ ref: Transform.Ref, obj?: StateObject }>,
-            replaced: Subject<{ ref: Transform.Ref, oldObj?: StateObject, newObj?: StateObject }>,
-            created: Subject<{ ref: Transform.Ref, obj: StateObject }>,
-            removed: Subject<{ ref: Transform.Ref, obj?: StateObject }>,
+            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 }>(),
         },
-        warn: Subject<string>
-    },
-    globalContext: unknown,
-    defaultObjectProps: unknown
-}
-
-namespace StateContext {
-    export function create(params: { globalContext: unknown, defaultObjectProps: unknown }): StateContext {
-        return {
-            events: {
-                object: {
-                    stateChanged: new Subject(),
-                    propsChanged: new Subject(),
-                    updated: new Subject(),
-                    replaced: new Subject(),
-                    created: new Subject(),
-                    removed: new Subject()
-                },
-                warn: new Subject()
-            },
-            globalContext: params.globalContext,
-            defaultObjectProps: params.defaultObjectProps
-        }
+        warn: this.ev<string>()
+    };
+
+    readonly globalContext: unknown;
+    readonly defaultObjectProps: unknown;
+
+    dispose() {
+        this.ev.dispose();
     }
-}
 
-export { StateContext }
+    constructor(params: { globalContext: unknown, defaultObjectProps: unknown }) {
+        this.globalContext = params.globalContext;
+        this.defaultObjectProps = params.defaultObjectProps;
+    }
+}

+ 69 - 50
src/mol-state/state.ts

@@ -13,23 +13,50 @@ import { StateContext } from './context';
 import { UUID } from 'mol-util';
 import { RuntimeContext, Task } from 'mol-task';
 
-export interface State {
-    tree: StateTree,
-    objects: State.Objects,
-    context: StateContext
-}
+export { State }
 
-export namespace State {
-    export type Ref = Transform.Ref
-    export type Objects = Map<Ref, StateObject.Node>
+class State {
+    private _tree: StateTree = StateTree.create();
+    get tree() { return this._tree; }
 
-    export function create(rootObject: StateObject, params?: { globalContext?: unknown, defaultObjectProps: unknown }) {
-        const tree = StateTree.create();
-        const objects: Objects = new Map();
+    readonly objects: State.Objects = new Map();
+    readonly context: StateContext;
+
+    getSnapshot(): State.Snapshot {
+        throw 'nyi';
+    }
+
+    setSnapshot(snapshot: State.Snapshot): void {
+        throw 'nyi';
+    }
+
+    dispose() {
+        this.context.dispose();
+    }
+
+    update(tree: StateTree): Task<void> {
+        return Task.create('Update Tree', taskCtx => {
+            const oldTree = this._tree;
+            this._tree = tree;
+
+            const ctx: UpdateContext = {
+                stateCtx: this.context,
+                taskCtx,
+                oldTree,
+                tree: tree,
+                objects: this.objects
+            };
+            // TODO: have "cancelled" error? Or would this be handled automatically?
+            return update(ctx);
+        });
+    }
+
+    constructor(rootObject: StateObject, params?: { globalContext?: unknown, defaultObjectProps: unknown }) {
+        const tree = this._tree;
         const root = tree.getValue(tree.rootRef)!;
         const defaultObjectProps = (params && params.defaultObjectProps) || { }
 
-        objects.set(tree.rootRef, {
+        this.objects.set(tree.rootRef, {
             ref: tree.rootRef,
             obj: rootObject,
             state: StateObject.StateType.Ok,
@@ -37,30 +64,37 @@ export namespace State {
             props: { ...defaultObjectProps }
         });
 
-        return {
-            tree,
-            objects,
-            context: StateContext.create({
-                globalContext: params && params.globalContext,
-                defaultObjectProps
-            })
-        };
+        this.context = new StateContext({
+            globalContext: params && params.globalContext,
+            defaultObjectProps
+        });
     }
+}
 
-    export function update(state: State, tree: StateTree): Task<State> {
-        return Task.create('Update Tree', taskCtx => {
-            const ctx: UpdateContext = {
-                stateCtx: state.context,
-                taskCtx,
-                oldTree: state.tree,
-                tree: tree,
-                objects: state.objects
-            };
-            return _update(ctx);
-        })
+namespace State {    
+    export type Objects = Map<Transform.Ref, StateObject.Node>
+
+    export interface Snapshot {
+        readonly tree: StateTree,
+        readonly props: { [key: string]: unknown }
     }
 
-    async function _update(ctx: UpdateContext): Promise<State> {
+    export function create(rootObject: StateObject, params?: { globalContext?: unknown, defaultObjectProps: unknown }) {
+        return new State(rootObject, params);
+    }
+}
+
+    type Ref = Transform.Ref
+
+    interface UpdateContext {
+        stateCtx: StateContext,
+        taskCtx: RuntimeContext,
+        oldTree: StateTree,
+        tree: StateTree,
+        objects: State.Objects
+    }
+
+    async function update(ctx: UpdateContext) {
         const roots = findUpdateRoots(ctx.objects, ctx.tree);
         const deletes = findDeletes(ctx);
         for (const d of deletes) {
@@ -73,23 +107,9 @@ export namespace State {
         for (const root of roots) {
             await updateSubtree(ctx, root);
         }
-
-        return {
-            tree: ctx.tree,
-            objects: ctx.objects,
-            context: ctx.stateCtx
-        };
-    }
-
-    interface UpdateContext {
-        stateCtx: StateContext,
-        taskCtx: RuntimeContext,
-        oldTree: StateTree,
-        tree: StateTree,
-        objects: Objects
     }
 
-    function findUpdateRoots(objects: Objects, tree: StateTree) {
+    function findUpdateRoots(objects: State.Objects, tree: StateTree) {
         const findState = {
             roots: [] as Ref[],
             objects
@@ -165,7 +185,7 @@ export namespace State {
         }
     }
 
-    function findAncestor(tree: StateTree, objects: Objects, root: Ref, types: { type: StateObject.Type }[]): StateObject {
+    function findAncestor(tree: StateTree, objects: State.Objects, root: Ref, types: { type: StateObject.Type }[]): StateObject {
         let current = tree.nodes.get(root)!;
         while (true) {
             current = tree.nodes.get(current.parent)!;
@@ -260,5 +280,4 @@ export namespace State {
             return Transformer.UpdateResult.Recreate;
         }
         return runTask(transformer.definition.update({ a, oldParams, b, newParams }, ctx.stateCtx.globalContext), ctx.taskCtx);
-    }
-}
+    }

+ 21 - 1
src/mol-util/rx-event-helper.ts

@@ -6,14 +6,34 @@
 
 import { Subject } from 'rxjs';
 
-export class RxEventHelper {
+export { RxEventHelper }
+
+interface RxEventHelper {
+    <T>(): Subject<T>,
+    dispose(): void
+}
+
+namespace RxEventHelper {
+    export function create(): RxEventHelper {
+        const helper = new _RxEventHelper();
+        const ret: RxEventHelper = (<T>() => helper.create<T>()) as RxEventHelper;
+        ret.dispose = () => helper.dispose();
+        return ret;
+    }
+}
+
+class _RxEventHelper {
     private _eventList: Subject<any>[] = [];
+    private _disposed = false;
+
     create<T>() {
         const s = new Subject<T>();
         this._eventList.push(s);
         return s;
     }
     dispose() {
+        if (this._disposed) return;
         for (const e of this._eventList) e.complete();
+        this._disposed = true;
     }
 }

+ 5 - 5
src/perf-tests/state.ts

@@ -90,9 +90,9 @@ export async function testState() {
     printTTree(tree1);
     printTTree(tree2);
 
-    const state1 = await State.update(state, tree1).run();
+    await state.update(tree1).run();
     console.log('----------------');
-    console.log(util.inspect(state1.objects, true, 3, true));
+    console.log(util.inspect(state.objects, true, 3, true));
 
     console.log('----------------');
     const jsonString = JSON.stringify(StateTree.toJSON(tree2), null, 2);
@@ -103,13 +103,13 @@ export async function testState() {
     printTTree(treeFromJson);
 
     console.log('----------------');
-    const state2 = await State.update(state1, treeFromJson).run();
-    console.log(util.inspect(state2.objects, true, 3, true));
+    await state.update(treeFromJson).run();
+    console.log(util.inspect(state.objects, true, 3, true));
 
     console.log('----------------');
 
     const q = StateSelection.byRef('square').parent();
-    const sel = StateSelection.select(q, state2);
+    const sel = StateSelection.select(q, state);
     console.log(sel);
 }