diff --git a/src/mol-plugin/behavior/dynamic/animation.ts b/src/mol-plugin/behavior/dynamic/animation.ts index d54e53db816499d74d497d28afd9e842eb6a386f..2b3a1cee14b172862acd3a79390ce0b660ef8c41 100644 --- a/src/mol-plugin/behavior/dynamic/animation.ts +++ b/src/mol-plugin/behavior/dynamic/animation.ts @@ -64,7 +64,7 @@ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps> rotate(rad: number) { this.updatedUnitTransforms.clear() const state = this.ctx.state.dataState - const reprs = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)) + const reprs = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)) Mat4.rotate(this.rotMat, this.tmpMat, rad, this.rotVec) for (const r of reprs) { if (!SO.isRepresentation3D(r.obj)) return @@ -112,7 +112,7 @@ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps> explode(p: number) { this.updatedUnitTransforms.clear() const state = this.ctx.state.dataState - const reprs = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)); + const reprs = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)); for (const r of reprs) { if (!SO.isRepresentation3D(r.obj)) return const structure = getRootStructure(r, state) @@ -187,5 +187,5 @@ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps> // function getRootStructure(root: StateObjectCell, state: State) { - return state.query(StateSelection.Generators.byValue(root).rootOfType([PluginStateObject.Molecule.Structure]))[0]; + return state.select(StateSelection.Generators.byValue(root).rootOfType([PluginStateObject.Molecule.Structure]))[0]; } \ No newline at end of file diff --git a/src/mol-plugin/behavior/dynamic/labels.ts b/src/mol-plugin/behavior/dynamic/labels.ts index f30f4464e52091c277f7cdcf9531faf4f5d5a5c5..0faa3ffbd84254fac455c412c2e153fd3da2938f 100644 --- a/src/mol-plugin/behavior/dynamic/labels.ts +++ b/src/mol-plugin/behavior/dynamic/labels.ts @@ -112,7 +112,7 @@ export const SceneLabels = PluginBehavior.create<SceneLabelsProps>({ /** Update structures to be labeled, returns true if changed */ private updateStructures(p: SceneLabelsProps) { const state = this.ctx.state.dataState - const structures = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Structure)); + const structures = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Structure)); const rootStructures = new Set<SO.Molecule.Structure>() for (const s of structures) { const rootStructure = getRootStructure(s, state) diff --git a/src/mol-plugin/behavior/static/state.ts b/src/mol-plugin/behavior/static/state.ts index f3d852d82e218b3cc1455ea579b70bd88032dae4..2e53ede8c6ae73956b50ce2fc5061749bfd8a3a1 100644 --- a/src/mol-plugin/behavior/static/state.ts +++ b/src/mol-plugin/behavior/static/state.ts @@ -51,17 +51,17 @@ export function SetCurrentObject(ctx: PluginContext) { } export function Update(ctx: PluginContext) { - PluginCommands.State.Update.subscribe(ctx, ({ state, tree }) => ctx.runTask(state.update(tree))); + PluginCommands.State.Update.subscribe(ctx, ({ state, tree }) => ctx.runTask(state.updateTree(tree))); } export function ApplyAction(ctx: PluginContext) { - PluginCommands.State.ApplyAction.subscribe(ctx, ({ state, action, ref }) => ctx.runTask(state.apply(action.action, action.params, ref))); + PluginCommands.State.ApplyAction.subscribe(ctx, ({ state, action, ref }) => ctx.runTask(state.applyAction(action.action, action.params, ref))); } export function RemoveObject(ctx: PluginContext) { PluginCommands.State.RemoveObject.subscribe(ctx, ({ state, ref }) => { const tree = state.tree.build().delete(ref).getTree(); - return ctx.runTask(state.update(tree)); + return ctx.runTask(state.updateTree(tree)); }); } diff --git a/src/mol-plugin/command/base.ts b/src/mol-plugin/command/base.ts index fcf1980a4e158b1dc29f27a7c085b5c187327095..d58e5e1b709d80b1e28a530f9e681d0b28d2af24 100644 --- a/src/mol-plugin/command/base.ts +++ b/src/mol-plugin/command/base.ts @@ -42,7 +42,7 @@ namespace PluginCommand { unsubscribe(): void } - export type Action<T> = (params: T) => void | Promise<void> + export type Action<T> = (params: T) => unknown | Promise<unknown> type Instance = { cmd: PluginCommand<any>, params: any, resolve: () => void, reject: (e: any) => void } export class Manager { diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 2749c5eee811344b9646c7a403a3cbb11eed5b72..f841c936382c7a440fa7f935db19cf6de87b1af0 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -152,7 +152,7 @@ export class PluginContext { tree.toRoot().apply(b.transformer, b.defaultParams, { ref: b.transformer.id }); } - await this.runTask(this.state.behaviorState.update(tree, true)); + await this.runTask(this.state.behaviorState.updateTree(tree, true)); } initDataActions() { @@ -181,9 +181,6 @@ export class PluginContext { this.lociLabels = new LociLabelManager(this); - // TODO: find a better solution for this. - setTimeout(() => this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`), 500); + this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`); } - - // settings = ; } \ No newline at end of file diff --git a/src/mol-plugin/state/actions/basic.ts b/src/mol-plugin/state/actions/basic.ts index e2be44425b97f00504319e7623a88291115aa0ee..bd45155cf267a0497daea4a7533707855a3b87be 100644 --- a/src/mol-plugin/state/actions/basic.ts +++ b/src/mol-plugin/state/actions/basic.ts @@ -74,7 +74,7 @@ const DownloadStructure = StateAction.build({ } const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams); - return state.update(createStructureTree(ctx, data, params.source.params.supportProps)); + return state.updateTree(createStructureTree(ctx, data, params.source.params.supportProps)); }); export const OpenStructure = StateAction.build({ @@ -84,7 +84,7 @@ export const OpenStructure = StateAction.build({ })(({ params, state }, ctx: PluginContext) => { const b = state.build(); const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) }); - return state.update(createStructureTree(ctx, data, false)); + return state.updateTree(createStructureTree(ctx, data, false)); }); function createStructureTree(ctx: PluginContext, b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, supportProps: boolean): StateTree { @@ -123,7 +123,7 @@ export const CreateComplexRepresentation = StateAction.build({ })(({ ref, state }, ctx: PluginContext) => { const root = state.build().to(ref); complexRepresentation(ctx, root); - return state.update(root.getTree()); + return state.updateTree(root.getTree()); }); export const UpdateTrajectory = StateAction.build({ @@ -133,7 +133,7 @@ export const UpdateTrajectory = StateAction.build({ by: PD.makeOptional(PD.Numeric(1, { min: -1, max: 1, step: 1 })) } })(({ params, state }) => { - const models = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Model) + const models = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Model) .filter(c => c.transform.transformer === StateTransforms.Model.ModelFromTrajectory)); const update = state.build(); @@ -157,7 +157,7 @@ export const UpdateTrajectory = StateAction.build({ } } - return state.update(update); + return state.updateTree(update); }); // @@ -175,15 +175,13 @@ function getVolumeData(format: VolumeFormat, b: StateTreeBuilder.To<PluginStateO } function createVolumeTree(format: VolumeFormat, ctx: PluginContext, b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>): StateTree { - - const root = getVolumeData(format, b) + return getVolumeData(format, b) .apply(StateTransforms.Representation.VolumeRepresentation3D, - VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface')); - - return root.getTree(); + VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface')) + .getTree(); } -function getFileFormat(format: VolumeFormat | 'auto', file: FileInput): VolumeFormat { +function getFileFormat(format: VolumeFormat | 'auto', file: FileInput, data?: Uint8Array): VolumeFormat { if (format === 'auto') { const fileFormat = getFileInfo(file).ext if (fileFormat in VolumeFormats) { @@ -205,11 +203,20 @@ export const OpenVolume = StateAction.build({ ['auto', 'Automatic'], ['ccp4', 'CCP4'], ['mrc', 'MRC'], ['map', 'MAP'], ['dsn6', 'DSN6'], ['brix', 'BRIX'] ]), } -})(({ params, state }, ctx: PluginContext) => { - const b = state.build(); - const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: true }); - const format = getFileFormat(params.format, params.file) - return state.update(createVolumeTree(format, ctx, data)); +})(async ({ params, state }, ctx: PluginContext) => { + const dataTree = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: true }); + const volumeData = await ctx.runTask(state.updateTree(dataTree)); + + // Alternative for more complex states where the builder is not a simple StateTreeBuilder.To<>: + /* + const dataRef = dataTree.ref; + await ctx.runTask(state.updateTree(dataTree)); + const dataCell = state.select(dataRef)[0]; + */ + + const format = getFileFormat(params.format, params.file, volumeData.data as Uint8Array) + const volumeTree = state.build().to(dataTree.ref); + return state.updateTree(createVolumeTree(format, ctx, volumeTree)); }); export { DownloadDensity }; @@ -276,5 +283,5 @@ const DownloadDensity = StateAction.build({ } const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams); - return state.update(createVolumeTree(format, ctx, data)); + return state.updateTree(createVolumeTree(format, ctx, data)); }); \ No newline at end of file diff --git a/src/mol-state/action.ts b/src/mol-state/action.ts index 1b178246fbc2fe31d421b4c116db744492117813..53b41b53b66b1727944f135ebdb4baf9176350be 100644 --- a/src/mol-state/action.ts +++ b/src/mol-state/action.ts @@ -71,7 +71,7 @@ namespace StateAction { params: def.params as Transformer.Definition<Transformer.From<T>, any, Transformer.Params<T>>['params'], run({ cell, state, params }) { const tree = state.build().to(cell.transform.ref).apply(transformer, params); - return state.update(tree); + return state.updateTree(tree) as Task<void>; } }) } diff --git a/src/mol-state/object.ts b/src/mol-state/object.ts index 495206df1f0a9ae667baf3b5e10c2eb463b78a97..3e2df3343df0b95b85698bbbbdabcfbda6dbbbd6 100644 --- a/src/mol-state/object.ts +++ b/src/mol-state/object.ts @@ -105,7 +105,7 @@ export class StateObjectTracker<T extends StateObject> { } update() { - const cell = this.state.query(this.query)[0]; + const cell = this.state.select(this.query)[0]; const version = cell ? cell.transform.version : void 0; const changed = this.cell !== cell || this.version !== version; this.cell = cell; diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 9733c06add1823d77dcef5785eb3dfe878783532..586321e8957c77a65bd6e08320ff5f206e5e6f55 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -33,7 +33,7 @@ class State { readonly globalContext: unknown = void 0; readonly events = { cell: { - stateUpdated: this.ev<State.ObjectEvent & { cellState: StateObjectCell.State}>(), + stateUpdated: this.ev<State.ObjectEvent & { cellState: StateObjectCell.State }>(), created: this.ev<State.ObjectEvent & { cell: StateObjectCell }>(), removed: this.ev<State.ObjectEvent & { parent: Transform.Ref }>(), }, @@ -67,7 +67,7 @@ class State { setSnapshot(snapshot: State.Snapshot) { const tree = StateTree.fromJSON(snapshot.tree); - return this.update(tree); + return this.updateTree(tree); } setCurrent(ref: Transform.Ref) { @@ -89,26 +89,28 @@ class State { } /** - * Select Cells by ref or a query generated on the fly. - * @example state.select('test') - * @example state.select(q => q.byRef('test').subtree()) + * Select Cells using the provided selector. + * @example state.query(StateSelection.Generators.byRef('test').ancestorOfType([type])) + * @example state.query('test') */ - select(selector: Transform.Ref | ((q: typeof StateSelection.Generators) => StateSelection.Selector)) { - if (typeof selector === 'string') return StateSelection.select(selector, this); - return StateSelection.select(selector(StateSelection.Generators), this) + select(selector: StateSelection.Selector) { + return StateSelection.select(selector, this) } /** - * Select Cells using the provided selector. - * @example state.select('test') + * Select Cells by building a query generated on the fly. * @example state.select(q => q.byRef('test').subtree()) */ - query(selector: StateSelection.Selector) { - return StateSelection.select(selector, this) + selectQ(selector: (q: typeof StateSelection.Generators) => StateSelection.Selector) { + if (typeof selector === 'string') return StateSelection.select(selector, this); + return StateSelection.select(selector(StateSelection.Generators), this) } - /** If no ref is specified, apply to root */ - apply<A extends StateAction>(action: A, params: StateAction.Params<A>, ref: Transform.Ref = Transform.RootRef): Task<void> { + /** + * Creates a Task that applies the specified StateAction (i.e. must use run* on the result) + * If no ref is specified, apply to root. + */ + applyAction<A extends StateAction>(action: A, params: StateAction.Params<A>, ref: Transform.Ref = Transform.RootRef): Task<void> { return Task.create('Apply Action', ctx => { const cell = this.cells.get(ref); if (!cell) throw new Error(`'${ref}' does not exist.`); @@ -118,41 +120,59 @@ class State { }); } - update(tree: StateTree | StateTreeBuilder, silent: boolean = false): Task<void> { - const _tree = (StateTreeBuilder.is(tree) ? tree.getTree() : tree).asTransient(); + /** + * Reconcialites the existing state tree with the new version. + * + * If the tree is StateTreeBuilder.To<T>, the corresponding StateObject is returned by the task. + * @param tree Tree instance or a tree builder instance + * @param doNotReportTiming Indicates whether to log timing of the individual transforms + */ + updateTree<T extends StateObject>(tree: StateTree | StateTreeBuilder | StateTreeBuilder.To<T>, doNotLogTiming?: boolean): Task<T> + updateTree(tree: StateTree | StateTreeBuilder, doNotLogTiming?: boolean): Task<void> + updateTree(tree: StateTree | StateTreeBuilder, doNotLogTiming: boolean = false): Task<any> { return Task.create('Update Tree', async taskCtx => { let updated = false; try { - const oldTree = this._tree; - this._tree = _tree; + const ctx = this.updateTreeAndCreateCtx(tree, taskCtx, doNotLogTiming); + updated = await update(ctx); + if (StateTreeBuilder.isTo(tree)) { + const cell = this.select(tree.ref)[0]; + return cell && cell.obj; + } + } finally { + if (updated) this.events.changed.next(); + } + }); + } + + private updateTreeAndCreateCtx(tree: StateTree | StateTreeBuilder, taskCtx: RuntimeContext, doNotLogTiming: boolean) { + const _tree = (StateTreeBuilder.is(tree) ? tree.getTree() : tree).asTransient(); + const oldTree = this._tree; + this._tree = _tree; - const ctx: UpdateContext = { - parent: this, - editInfo: StateTreeBuilder.is(tree) ? tree.editInfo : void 0, + const ctx: UpdateContext = { + parent: this, + editInfo: StateTreeBuilder.is(tree) ? tree.editInfo : void 0, - errorFree: this.errorFree, - taskCtx, - oldTree, - tree: _tree, - cells: this.cells as Map<Transform.Ref, StateObjectCell>, - transformCache: this.transformCache, + errorFree: this.errorFree, + taskCtx, + oldTree, + tree: _tree, + cells: this.cells as Map<Transform.Ref, StateObjectCell>, + transformCache: this.transformCache, - results: [], + results: [], - silent, + silent: doNotLogTiming, - changed: false, - hadError: false, - newCurrent: void 0 - }; + changed: false, + hadError: false, + newCurrent: void 0 + }; - this.errorFree = true; - // TODO: handle "cancelled" error? Or would this be handled automatically? - updated = await update(ctx); - } finally { - if (updated) this.events.changed.next(); - } - }); + this.errorFree = true; + + return ctx; } constructor(rootObject: StateObject, params?: { globalContext?: unknown }) { @@ -167,8 +187,8 @@ class State { version: root.version, errorText: void 0, params: { - definition: { }, - values: { } + definition: {}, + values: {} } }); @@ -311,7 +331,7 @@ async function update(ctx: UpdateContext) { const current = ctx.parent.current; const currentCell = ctx.cells.get(current); if (currentCell && ( - currentCell.obj === StateObject.Null + currentCell.obj === StateObject.Null || (currentCell.status === 'error' && currentCell.errorText === ParentNullErrorText))) { newCurrent = findNewCurrent(ctx.oldTree, current, [], ctx.cells); ctx.parent.setCurrent(newCurrent); @@ -523,7 +543,7 @@ async function updateSubtree(ctx: UpdateContext, root: Ref) { function resolveParams(ctx: UpdateContext, transform: Transform, src: StateObject) { const prms = transform.transformer.definition.params; - const definition = prms ? prms(src, ctx.parent.globalContext) : { }; + const definition = prms ? prms(src, ctx.parent.globalContext) : {}; const values = transform.params ? transform.params : ParamDefinition.getDefaultValues(definition); return { definition, values }; } diff --git a/src/mol-state/tree/builder.ts b/src/mol-state/tree/builder.ts index 2eb22efd86b5797c1845f75603646ca94af8ff79..0cd0faa0b36088c79de318bd233bdba057a07c59 100644 --- a/src/mol-state/tree/builder.ts +++ b/src/mol-state/tree/builder.ts @@ -33,6 +33,10 @@ namespace StateTreeBuilder { return !!obj && typeof (obj as StateTreeBuilder).getTree === 'function'; } + export function isTo(obj: any): obj is StateTreeBuilder.To<any> { + return !!obj && typeof (obj as StateTreeBuilder).getTree === 'function' && typeof (obj as StateTreeBuilder.To<any>).ref === 'string'; + } + export class Root implements StateTreeBuilder { private state: State; get editInfo() { return this.state.editInfo; }