Skip to content
Snippets Groups Projects
Commit 408bf48a authored by David Sehnal's avatar David Sehnal
Browse files

mol-state: updated transformer model

parent 5ad983e9
No related branches found
No related tags found
No related merge requests found
......@@ -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 }));
}
}
......@@ -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
};
......
......@@ -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}`);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment