ソースを参照

mol-state: updated transformer model

David Sehnal 6 年 前
コミット
408bf48a8a
3 ファイル変更54 行追加46 行削除
  1. 22 13
      src/mol-state/state.ts
  2. 20 13
      src/mol-state/transformer.ts
  3. 12 20
      src/perf-tests/state.ts

+ 22 - 13
src/mol-state/state.ts

@@ -115,22 +115,32 @@ export namespace State {
         }
     }
 
-    async function updateNode(oldTree: TransformTree, tree: TransformTree, objects: Objects, root: Transform.Ref) {
-        const transform = tree.getValue(root)!;
-        const parent = findParent(tree, objects, root, transform.transformer.definition.from);
+    async function updateNode(oldTree: TransformTree, tree: TransformTree, objects: Objects, currentRef: Transform.Ref) {
+        const transform = tree.getValue(currentRef)!;
+        const parent = findParent(tree, objects, currentRef, transform.transformer.definition.from);
         console.log('parent', parent ? parent.ref : 'undefined')
         if (!oldTree.nodes.has(transform.ref) || !objects.has(transform.ref)) {
             console.log('creating...', transform.transformer.id, oldTree.nodes.has(transform.ref), objects.has(transform.ref));
             const obj = await createObject(transform.transformer, parent, transform.params);
             obj.ref = transform.ref;
-            objects.set(root, { obj, state: StateObject.StateType.Ok, version: transform.version });
+            objects.set(currentRef, { obj, state: StateObject.StateType.Ok, version: transform.version });
         } else {
             console.log('updating...', transform.transformer.id);
             const current = objects.get(transform.ref)!.obj;
             const oldParams = oldTree.getValue(transform.ref)!.params;
-            await updateObject(transform.transformer, parent, current, oldParams, transform.params);
-            const obj = objects.get(root)!;
-            obj.version = transform.version;
+            switch (await updateObject(transform.transformer, parent, current, oldParams, transform.params)) {
+                case Transformer.UpdateResult.Recreate: {
+                    const obj = await createObject(transform.transformer, parent, transform.params);
+                    obj.ref = transform.ref;
+                    objects.set(currentRef, { obj, state: StateObject.StateType.Ok, version: transform.version });
+                    break;
+                }
+                case Transformer.UpdateResult.Updated: {
+                    const obj = objects.get(currentRef)!;
+                    obj.version = transform.version;
+                    break;
+                }
+            }
         }
     }
 
@@ -139,15 +149,14 @@ export namespace State {
         return t as A;
     }
 
-    function createObject(transformer: Transformer, parent: StateObject, params: any) {
-        return runTask(transformer.definition.apply(parent, params, 0 as any));
+    function createObject(transformer: Transformer, a: StateObject, params: any) {
+        return runTask(transformer.definition.apply({ a, params }));
     }
 
-    async function updateObject(transformer: Transformer, parent: StateObject, obj: StateObject, oldParams: any, params: any) {
+    async function updateObject(transformer: Transformer, a: StateObject, b: StateObject, oldParams: any, newParams: any) {
         if (!transformer.definition.update) {
-            // TODO
-            throw 'nyi';
+            return Transformer.UpdateResult.Recreate;
         }
-        return transformer.definition.update!(parent, oldParams, obj, params, 0 as any);
+        return runTask(transformer.definition.update({ a, oldParams, b, newParams }));
     }
 }

+ 20 - 13
src/mol-state/transformer.ts

@@ -11,6 +11,7 @@ import { Transform } from './tree/transform';
 
 export interface Transformer<A extends StateObject = StateObject, B extends StateObject = StateObject, P = unknown> {
     apply(params?: P, props?: Partial<Transform.Props>): Transform<A, B, P>,
+    readonly namespace: string,
     readonly id: Transformer.Id,
     readonly definition: Transformer.Definition<A, B, P>
 }
@@ -21,9 +22,22 @@ export namespace Transformer {
     export type To<T extends Transformer<any, any, any>> = T extends Transformer<any, infer B, any> ? B : unknown;
     export type ControlsFor<Props> = { [P in keyof Props]?: any }
 
+    export interface ApplyParams<A extends StateObject = StateObject, P = unknown> {
+        a: A,
+        params: P
+    }
+
+    export interface UpdateParams<A extends StateObject = StateObject, B extends StateObject = StateObject, P = unknown> {
+        a: A,
+        b: B,
+        oldParams: P,
+        newParams: P
+    }
+
+    export enum UpdateResult { Unchanged, Updated, Recreate }
+
     export interface Definition<A extends StateObject = StateObject, B extends StateObject = StateObject, P = unknown> {
         readonly name: string,
-        readonly namespace?: string,
         readonly from: { type: StateObject.Type }[],
         readonly to: { type: StateObject.Type }[],
 
@@ -31,16 +45,14 @@ export namespace Transformer {
          * Apply the actual transformation. It must be pure (i.e. with no side effects).
          * Returns a task that produces the result of the result directly.
          */
-        apply(a: A, params: P, context: TransformContext): Task<B> | B,
+        apply(params: ApplyParams<A, P>): Task<B> | B,
 
         /**
          * Attempts to update the entity in a non-destructive way.
          * For example changing a color scheme of a visual does not require computing new geometry.
          * Return/resolve to undefined if the update is not possible.
-         *
-         * The ability to resolve the task to undefined is present for "async updates" (i.e. containing an ajax call).
          */
-        update?(a: A, oldParams: P, b: B, newParams: P, context: TransformContext): Task<B | undefined> | B | undefined,
+        update?(params: UpdateParams<A, B, P>): Task<UpdateResult> | UpdateResult,
 
         /** Check the parameters and return a list of errors if the are not valid. */
         defaultParams?(a: A, context: TransformContext): P,
@@ -66,12 +78,6 @@ export namespace Transformer {
 
     const registry = new Map<Id, Transformer>();
 
-    function typeToString(a: { type: StateObject.Type }[]) {
-        if (!a.length) return '()';
-        if (a.length === 1) return a[0].type.kind;
-        return `(${a.map(t => t.type.kind).join(' | ')})`;
-    }
-
     export function get(id: string): Transformer {
         const t = registry.get(id as Id);
         if (!t) {
@@ -81,8 +87,8 @@ export namespace Transformer {
     }
 
     export function create<A extends StateObject, B extends StateObject, P>(namespace: string, definition: Definition<A, B, P>) {
-        const { from, to, name } = definition;
-        const id = `${namespace}.${name} :: ${typeToString(from)} -> ${typeToString(to)}` as Id;
+        const { name } = definition;
+        const id = `${namespace}.${name}` as Id;
 
         if (registry.has(id)) {
             throw new Error(`A transform with id '${name}' is already registered. Please pick a unique identifier for your transforms and/or register them only once. This is to ensure that transforms can be serialized and replayed.`);
@@ -90,6 +96,7 @@ export namespace Transformer {
 
         const t: Transformer<A, B, P> = {
             apply(params, props) { return Transform.create<A, B, P>(t as any, params, props); },
+            namespace,
             id,
             definition
         };

+ 12 - 20
src/perf-tests/state.ts

@@ -18,19 +18,17 @@ export class Square extends _obj<{ a: number }>('square', { name: 'Square', clas
 export class Circle extends _obj<{ r: number }>('circle', { name: 'Circle', class: 'shape' }) { }
 export class Area extends _obj<{ volume: number }>('volume', { name: 'Volume', class: 'prop' }) { }
 
-const root = new Root({ label: 'Root' }, {});
-
 export const CreateSquare = _transform<Root, Square, { a: number }>({
     name: 'create-square',
     from: [Root],
     to: [Square],
-    apply(a, p) {
+    apply({ params: p }) {
         return new Square({ label: `Square a=${p.a}` }, p);
     },
-    update(a, _, b, p) {
+    update({ b, newParams: p }) {
         b.props.label = `Square a=${p.a}`
         b.data.a = p.a;
-        return b;
+        return Transformer.UpdateResult.Updated;
     }
 });
 
@@ -38,13 +36,13 @@ export const CreateCircle = _transform<Root, Circle, { r: number }>({
     name: 'create-circle',
     from: [Root],
     to: [Square],
-    apply(a, p) {
+    apply({ params: p }) {
         return new Circle({ label: `Circle r=${p.r}` }, p);
     },
-    update(a, _, b, p) {
+    update({ b, newParams: p }) {
         b.props.label = `Circle r=${p.r}`
         b.data.r = p.r;
-        return b;
+        return Transformer.UpdateResult.Updated;
     }
 });
 
@@ -52,30 +50,24 @@ export const CaclArea = _transform<Square | Circle, Area, {}>({
     name: 'calc-area',
     from: [Square, Circle],
     to: [Area],
-    apply(a) {
+    apply({ a }) {
         if (a instanceof Square) return new Area({ label: 'Area' }, { volume: a.data.a * a.data.a });
         else if (a instanceof Circle) return new Area({ label: 'Area' }, { volume: a.data.r * a.data.r * Math.PI });
         throw new Error('Unknown object type.');
     },
-    update(a, _, b) {
+    update({ a, b }) {
         if (a instanceof Square) b.data.volume = a.data.a * a.data.a;
         else if (a instanceof Circle) b.data.volume = a.data.r * a.data.r * Math.PI;
         else throw new Error('Unknown object type.');
-        return b;
+        return Transformer.UpdateResult.Updated;
     }
 });
 
-async function runTask<A>(t: A | Task<A>): Promise<A> {
+export async function runTask<A>(t: A | Task<A>): Promise<A> {
     if ((t as any).run) return await (t as Task<A>).run();
     return t as A;
 }
 
-export async function test() {
-    const sq = await runTask(CreateSquare.definition.apply(root, { a: 10 }, 0 as any));
-    const area = await runTask(CaclArea.definition.apply(sq, {}, 0 as any));
-    console.log(area);
-}
-
 export async function testState() {
     const state = State.create();
 
@@ -117,8 +109,8 @@ testState();
 export function printTTree(tree: TransformTree) {
     let lines: string[] = [];
     function print(offset: string, ref: any) {
-        let t = tree.nodes.get(ref)!;
-        let tr = t.value;
+        const t = tree.nodes.get(ref)!;
+        const tr = t.value;
 
         const name = tr.transformer.id;
         lines.push(`${offset}|_ (${ref}) ${name} ${tr.params ? JSON.stringify(tr.params) : ''}, v${t.value.version}`);