|
@@ -5,79 +5,97 @@
|
|
|
*/
|
|
|
|
|
|
import { StateObject } from './object';
|
|
|
-import { TransformTree } from './tree/tree';
|
|
|
+import { StateTree } from './tree';
|
|
|
import { Transform } from './tree/transform';
|
|
|
-import { Map as ImmutableMap } from 'immutable';
|
|
|
-// import { StateContext } from './context/context';
|
|
|
import { ImmutableTree } from './util/immutable-tree';
|
|
|
import { Transformer } from './transformer';
|
|
|
-import { Task } from 'mol-task';
|
|
|
+import { StateContext } from './context';
|
|
|
+import { UUID } from 'mol-util';
|
|
|
+import { RuntimeContext, Task } from 'mol-task';
|
|
|
|
|
|
-export interface State<ObjectProps = unknown> {
|
|
|
- definition: State.Definition<ObjectProps>,
|
|
|
- objects: State.Objects
|
|
|
+export interface State {
|
|
|
+ tree: StateTree,
|
|
|
+ objects: State.Objects,
|
|
|
+ context: StateContext
|
|
|
}
|
|
|
|
|
|
export namespace State {
|
|
|
- export type ObjectProps<P> = ImmutableMap<Transform.Ref, P>
|
|
|
- export type Objects = Map<Transform.Ref, StateObject.Wrapped>
|
|
|
+ export type Ref = Transform.Ref
|
|
|
+ export type Objects = Map<Ref, StateObject.Node>
|
|
|
|
|
|
- export interface Definition<P = unknown> {
|
|
|
- tree: TransformTree,
|
|
|
- // things like object visibility
|
|
|
- props: ObjectProps<P>
|
|
|
- }
|
|
|
-
|
|
|
- export function create(): State {
|
|
|
- const tree = TransformTree.create();
|
|
|
+ export function create(params?: { globalContext?: unknown, defaultObjectProps: unknown }) {
|
|
|
+ const tree = StateTree.create();
|
|
|
const objects: Objects = new Map();
|
|
|
const root = tree.getValue(tree.rootRef)!;
|
|
|
+ const defaultObjectProps = (params && params.defaultObjectProps) || { }
|
|
|
|
|
|
- objects.set(tree.rootRef, { obj: void 0 as any, state: StateObject.StateType.Ok, version: root.version });
|
|
|
+ objects.set(tree.rootRef, { obj: void 0 as any, state: StateObject.StateType.Ok, version: root.version, props: { ...defaultObjectProps } });
|
|
|
|
|
|
return {
|
|
|
- definition: {
|
|
|
- tree,
|
|
|
- props: ImmutableMap()
|
|
|
- },
|
|
|
- objects
|
|
|
+ tree,
|
|
|
+ objects,
|
|
|
+ context: StateContext.create({
|
|
|
+ globalContext: params && params.globalContext,
|
|
|
+ defaultObjectProps
|
|
|
+ })
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- export async function update<P>(state: State<P>, tree: TransformTree, props?: ObjectProps<P>): Promise<State<P>> {
|
|
|
- const roots = findUpdateRoots(state.objects, tree);
|
|
|
- const deletes = findDeletes(state.objects, tree);
|
|
|
+ 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);
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ async function _update(ctx: UpdateContext): Promise<State> {
|
|
|
+ const roots = findUpdateRoots(ctx.objects, ctx.tree);
|
|
|
+ const deletes = findDeletes(ctx);
|
|
|
for (const d of deletes) {
|
|
|
- state.objects.delete(d);
|
|
|
+ ctx.objects.delete(d);
|
|
|
+ ctx.stateCtx.events.object.removed.next({ ref: d });
|
|
|
}
|
|
|
|
|
|
- console.log('roots', roots);
|
|
|
+ initObjectState(ctx, roots);
|
|
|
+
|
|
|
for (const root of roots) {
|
|
|
- await updateSubtree(state.definition.tree, tree, state.objects, root);
|
|
|
+ await updateSubtree(ctx, root);
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
- definition: { tree, props: props || state.definition.props },
|
|
|
- objects: state.objects
|
|
|
+ tree: ctx.tree,
|
|
|
+ objects: ctx.objects,
|
|
|
+ context: ctx.stateCtx
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- function findUpdateRoots(objects: Objects, tree: TransformTree) {
|
|
|
- console.log(tree);
|
|
|
+ interface UpdateContext {
|
|
|
+ stateCtx: StateContext,
|
|
|
+ taskCtx: RuntimeContext,
|
|
|
+ oldTree: StateTree,
|
|
|
+ tree: StateTree,
|
|
|
+ objects: Objects
|
|
|
+ }
|
|
|
+
|
|
|
+ function findUpdateRoots(objects: Objects, tree: StateTree) {
|
|
|
const findState = {
|
|
|
- roots: [] as Transform.Ref[],
|
|
|
+ roots: [] as Ref[],
|
|
|
objects
|
|
|
};
|
|
|
|
|
|
ImmutableTree.doPreOrder(tree, tree.nodes.get(tree.rootRef)!, findState, (n, _, s) => {
|
|
|
if (!s.objects.has(n.ref)) {
|
|
|
- console.log('missing', n.ref);
|
|
|
s.roots.push(n.ref);
|
|
|
return false;
|
|
|
}
|
|
|
const o = s.objects.get(n.ref)!;
|
|
|
if (o.version !== n.value.version) {
|
|
|
- console.log('diff version', n.ref, n.value.version, o.version);
|
|
|
s.roots.push(n.ref);
|
|
|
return false;
|
|
|
}
|
|
@@ -88,66 +106,153 @@ export namespace State {
|
|
|
return findState.roots;
|
|
|
}
|
|
|
|
|
|
- function findDeletes(objects: Objects, tree: TransformTree): Transform.Ref[] {
|
|
|
- // TODO
|
|
|
- return [];
|
|
|
+ function findDeletes(ctx: UpdateContext): Ref[] {
|
|
|
+ // TODO: do this in some sort of "tree order"?
|
|
|
+ const deletes: Ref[] = [];
|
|
|
+ const keys = ctx.objects.keys();
|
|
|
+ while (true) {
|
|
|
+ const key = keys.next();
|
|
|
+ if (key.done) break;
|
|
|
+ if (!ctx.tree.nodes.has(key.value)) deletes.push(key.value);
|
|
|
+ }
|
|
|
+ return deletes;
|
|
|
+ }
|
|
|
+
|
|
|
+ function setObjectState(ctx: UpdateContext, ref: Ref, state: StateObject.StateType, errorText?: string) {
|
|
|
+ let changed = false;
|
|
|
+ if (ctx.objects.has(ref)) {
|
|
|
+ const obj = ctx.objects.get(ref)!;
|
|
|
+ changed = obj.state !== state;
|
|
|
+ obj.state = state;
|
|
|
+ obj.errorText = errorText;
|
|
|
+ } else {
|
|
|
+ const obj = { state, version: UUID.create(), errorText, props: { ...ctx.stateCtx.defaultObjectProps } };
|
|
|
+ ctx.objects.set(ref, obj);
|
|
|
+ changed = true;
|
|
|
+ }
|
|
|
+ if (changed) ctx.stateCtx.events.object.stateChanged.next({ ref });
|
|
|
}
|
|
|
|
|
|
- function findParent(tree: TransformTree, objects: Objects, root: Transform.Ref, types: { type: StateObject.Type }[]): StateObject {
|
|
|
+ function _initVisitor(t: ImmutableTree.Node<Transform>, _: any, ctx: UpdateContext) {
|
|
|
+ setObjectState(ctx, t.ref, StateObject.StateType.Pending);
|
|
|
+ }
|
|
|
+ /** Return "resolve set" */
|
|
|
+ function initObjectState(ctx: UpdateContext, roots: Ref[]) {
|
|
|
+ for (const root of roots) {
|
|
|
+ ImmutableTree.doPreOrder(ctx.tree, ctx.tree.nodes.get(root), ctx, _initVisitor);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function doError(ctx: UpdateContext, ref: Ref, errorText: string) {
|
|
|
+ setObjectState(ctx, ref, StateObject.StateType.Error, errorText);
|
|
|
+ const wrap = ctx.objects.get(ref)!;
|
|
|
+ if (wrap.obj) {
|
|
|
+ ctx.stateCtx.events.object.removed.next({ ref });
|
|
|
+ wrap.obj = void 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ const children = ctx.tree.nodes.get(ref)!.children.values();
|
|
|
+ while (true) {
|
|
|
+ const next = children.next();
|
|
|
+ if (next.done) return;
|
|
|
+ doError(ctx, next.value, 'Parent node contains error.');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function findParent(tree: StateTree, objects: Objects, root: Ref, types: { type: StateObject.Type }[]): StateObject {
|
|
|
let current = tree.nodes.get(root)!;
|
|
|
- console.log('finding', types.map(t => t.type.kind));
|
|
|
while (true) {
|
|
|
current = tree.nodes.get(current.parent)!;
|
|
|
- if (current.ref === tree.rootRef) return objects.get(tree.rootRef)!.obj;
|
|
|
- const obj = objects.get(current.ref)!.obj;
|
|
|
- console.log('current', obj.type.kind);
|
|
|
- for (const t of types) if (obj.type === t.type) return objects.get(current.ref)!.obj;
|
|
|
+ if (current.ref === tree.rootRef) {
|
|
|
+ return objects.get(tree.rootRef)!.obj!;
|
|
|
+ }
|
|
|
+ const obj = objects.get(current.ref)!.obj!;
|
|
|
+ for (const t of types) if (obj.type === t.type) return objects.get(current.ref)!.obj!;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async function updateSubtree(oldTree: TransformTree, tree: TransformTree, objects: Objects, root: Transform.Ref) {
|
|
|
- await updateNode(oldTree, tree, objects, root);
|
|
|
- const children = tree.nodes.get(root)!.children.values();
|
|
|
+ async function updateSubtree(ctx: UpdateContext, root: Ref) {
|
|
|
+ setObjectState(ctx, root, StateObject.StateType.Processing);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const update = await updateNode(ctx, root);
|
|
|
+ setObjectState(ctx, root, StateObject.StateType.Ok);
|
|
|
+ if (update.action === 'created') {
|
|
|
+ ctx.stateCtx.events.object.created.next({ ref: root });
|
|
|
+ } else if (update.action === 'updated') {
|
|
|
+ ctx.stateCtx.events.object.updated.next({ ref: root });
|
|
|
+ } else if (update.action === 'replaced') {
|
|
|
+ ctx.stateCtx.events.object.replaced.next({ ref: root, old: update.old });
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ doError(ctx, root, '' + e);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const children = ctx.tree.nodes.get(root)!.children.values();
|
|
|
while (true) {
|
|
|
const next = children.next();
|
|
|
if (next.done) return;
|
|
|
- await updateSubtree(oldTree, tree, objects, next.value);
|
|
|
+ await updateSubtree(ctx, next.value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- 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);
|
|
|
- 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 });
|
|
|
+ async function updateNode(ctx: UpdateContext, currentRef: Ref) {
|
|
|
+ const { oldTree, tree, objects } = ctx;
|
|
|
+ const transform = tree.getValue(currentRef)!;
|
|
|
+ const parent = findParent(tree, objects, currentRef, transform.transformer.definition.from);
|
|
|
+ // console.log('parent', transform.transformer.id, transform.transformer.definition.from[0].type, parent ? parent.ref : 'undefined')
|
|
|
+ if (!oldTree.nodes.has(currentRef) || !objects.has(currentRef)) {
|
|
|
+ // console.log('creating...', transform.transformer.id, oldTree.nodes.has(currentRef), objects.has(currentRef));
|
|
|
+ const obj = await createObject(ctx, transform.transformer, parent, transform.params);
|
|
|
+ obj.ref = currentRef;
|
|
|
+ objects.set(currentRef, {
|
|
|
+ obj,
|
|
|
+ state: StateObject.StateType.Ok,
|
|
|
+ version: transform.version,
|
|
|
+ props: { ...ctx.stateCtx.defaultObjectProps, ...transform.defaultProps }
|
|
|
+ });
|
|
|
+ return { action: 'created' };
|
|
|
} 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;
|
|
|
+ // console.log('updating...', transform.transformer.id);
|
|
|
+ const current = objects.get(currentRef)!;
|
|
|
+ const oldParams = oldTree.getValue(currentRef)!.params;
|
|
|
+ switch (await updateObject(ctx, transform.transformer, parent, current.obj!, oldParams, transform.params)) {
|
|
|
+ case Transformer.UpdateResult.Recreate: {
|
|
|
+ const obj = await createObject(ctx, transform.transformer, parent, transform.params);
|
|
|
+ obj.ref = currentRef;
|
|
|
+ objects.set(currentRef, {
|
|
|
+ obj,
|
|
|
+ state: StateObject.StateType.Ok,
|
|
|
+ version: transform.version,
|
|
|
+ props: { ...ctx.stateCtx.defaultObjectProps, ...current.props, ...transform.defaultProps }
|
|
|
+ });
|
|
|
+ return { action: 'replaced', old: current.obj! };
|
|
|
+ }
|
|
|
+ case Transformer.UpdateResult.Updated:
|
|
|
+ current.version = transform.version;
|
|
|
+ current.props = { ...ctx.stateCtx.defaultObjectProps, ...current.props, ...transform.defaultProps };
|
|
|
+ return { action: 'updated' };
|
|
|
+ default:
|
|
|
+ // TODO check if props need to be updated
|
|
|
+ return { action: 'none' };
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- 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;
|
|
|
+ function runTask<T>(t: T | Task<T>, ctx: RuntimeContext) {
|
|
|
+ if (typeof (t as any).run === 'function') return (t as Task<T>).runInContext(ctx);
|
|
|
+ return t as T;
|
|
|
}
|
|
|
|
|
|
- function createObject(transformer: Transformer, parent: StateObject, params: any) {
|
|
|
- return runTask(transformer.definition.apply(parent, params, 0 as any));
|
|
|
+ function createObject(ctx: UpdateContext, transformer: Transformer, a: StateObject, params: any) {
|
|
|
+ return runTask(transformer.definition.apply({ a, params }, ctx.stateCtx.globalContext), ctx.taskCtx);
|
|
|
}
|
|
|
|
|
|
- async function updateObject(transformer: Transformer, parent: StateObject, obj: StateObject, oldParams: any, params: any) {
|
|
|
+ async function updateObject(ctx: UpdateContext, 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 }, ctx.stateCtx.globalContext), ctx.taskCtx);
|
|
|
}
|
|
|
}
|