Newer
Older
/**
* 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 { ImmutableTree } from './util/immutable-tree';
import { Transformer } from './transformer';
import { StateContext } from './context';
import { UUID } from 'mol-util';
export function create(params?: { globalContext?: unknown, defaultObjectProps: unknown }) {
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, props: { ...defaultObjectProps } });
context: StateContext.create({
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);
})
}
async function _update(ctx: UpdateContext): Promise<State> {
const roots = findUpdateRoots(ctx.objects, ctx.tree);
for (const d of deletes) {
ctx.objects.delete(d);
ctx.stateCtx.events.object.removed.next({ ref: d });
for (const root of roots) {
}
return {
tree: ctx.tree,
objects: ctx.objects,
context: ctx.stateCtx
tree: StateTree,
objects: Objects
}
function findUpdateRoots(objects: Objects, tree: StateTree) {
const findState = {
objects
};
ImmutableTree.doPreOrder(tree, tree.nodes.get(tree.rootRef)!, findState, (n, _, s) => {
if (!s.objects.has(n.ref)) {
s.roots.push(n.ref);
return false;
}
const o = s.objects.get(n.ref)!;
if (o.version !== n.value.version) {
s.roots.push(n.ref);
return false;
}
return true;
});
return findState.roots;
}
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) {
if (ctx.objects.has(ref)) {
const obj = ctx.objects.get(ref)!;
obj.state = state;
obj.errorText = errorText;
} else {
const obj = { state, version: UUID.create(), errorText, props: { ...ctx.stateCtx.defaultObjectProps } };
if (changed) ctx.stateCtx.events.object.stateChanged.next({ ref });
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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)!;
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!;
for (const t of types) if (obj.type === t.type) return objects.get(current.ref)!.obj!;
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);
} 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;
async function updateNode(ctx: UpdateContext, currentRef: Ref) {
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' };
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' };
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(ctx: UpdateContext, transformer: Transformer, a: StateObject, params: any) {
return runTask(transformer.definition.apply({ a, params }, ctx.stateCtx.globalContext), ctx.taskCtx);
async function updateObject(ctx: UpdateContext, transformer: Transformer, a: StateObject, b: StateObject, oldParams: any, newParams: any) {
if (!transformer.definition.update) {
return Transformer.UpdateResult.Recreate;
return runTask(transformer.definition.update({ a, oldParams, b, newParams }, ctx.stateCtx.globalContext), ctx.taskCtx);