From d665ebf99f4ed8d7787b4a8695f1565a59b0964b Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Mon, 12 Nov 2018 13:22:01 +0100 Subject: [PATCH] mol-state: basic cellState support --- src/mol-model/shape/shape.ts | 2 +- .../structure/model/formats/mmcif.ts | 6 +- .../structure/model/formats/mmcif/atomic.ts | 2 +- .../structure/model/formats/mmcif/ihm.ts | 2 +- .../model/properties/custom/descriptor.ts | 2 +- .../model/properties/custom/indexed.ts | 6 +- src/mol-plugin/behavior/built-in/state.ts | 13 ++- src/mol-plugin/command/command.ts | 66 ++++++++------ src/mol-plugin/command/state.ts | 10 ++- src/mol-plugin/context.ts | 9 +- src/mol-plugin/ui/state-tree.tsx | 18 +++- src/mol-state/action.ts | 2 +- src/mol-state/object.ts | 16 ++-- src/mol-state/state.ts | 35 +++++--- src/mol-state/transform.ts | 40 ++++++--- src/mol-state/tree/builder.ts | 20 ++--- src/mol-state/tree/immutable.ts | 14 ++- src/mol-state/tree/transient.ts | 90 ++++++++++++++----- src/mol-util/uuid.ts | 19 ++-- .../model/properties/providers/pdbe.ts | 2 +- src/servers/model/server/jobs.ts | 2 +- src/servers/volume/server/query/execute.ts | 2 +- 22 files changed, 248 insertions(+), 130 deletions(-) diff --git a/src/mol-model/shape/shape.ts b/src/mol-model/shape/shape.ts index 95ab6cd63..6ded3bec5 100644 --- a/src/mol-model/shape/shape.ts +++ b/src/mol-model/shape/shape.ts @@ -25,7 +25,7 @@ export namespace Shape { let currentGroupCount = -1 return { - id: UUID.create(), + id: UUID.create22(), name, mesh, get groupCount() { diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index 414de8fbc..155a94777 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -169,7 +169,7 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, entities if (previous && atomic.sameAsPrevious) { return { ...previous, - id: UUID.create(), + id: UUID.create22(), modelNum: atom_site.pdbx_PDB_model_num.value(0), atomicConformation: atomic.conformation, _dynamicPropertyData: Object.create(null) @@ -182,7 +182,7 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, entities : format.data._name; return { - id: UUID.create(), + id: UUID.create22(), label, sourceData: format, modelNum: atom_site.pdbx_PDB_model_num.value(0), @@ -208,7 +208,7 @@ function createModelIHM(format: mmCIF_Format, data: IHMData, formatData: FormatD const coarse = getIHMCoarse(data, formatData); return { - id: UUID.create(), + id: UUID.create22(), label: data.model_name, sourceData: format, modelNum: data.model_id, diff --git a/src/mol-model/structure/model/formats/mmcif/atomic.ts b/src/mol-model/structure/model/formats/mmcif/atomic.ts index 23879400f..20bef5296 100644 --- a/src/mol-model/structure/model/formats/mmcif/atomic.ts +++ b/src/mol-model/structure/model/formats/mmcif/atomic.ts @@ -62,7 +62,7 @@ function createHierarchyData(atom_site: AtomSite, offsets: { residues: ArrayLike function getConformation(atom_site: AtomSite): AtomicConformation { return { - id: UUID.create(), + id: UUID.create22(), atomId: atom_site.id, occupancy: atom_site.occupancy, B_iso_or_equiv: atom_site.B_iso_or_equiv, diff --git a/src/mol-model/structure/model/formats/mmcif/ihm.ts b/src/mol-model/structure/model/formats/mmcif/ihm.ts index 4a3315613..8cba685fb 100644 --- a/src/mol-model/structure/model/formats/mmcif/ihm.ts +++ b/src/mol-model/structure/model/formats/mmcif/ihm.ts @@ -49,7 +49,7 @@ export function getIHMCoarse(data: IHMData, formatData: FormatData): { hierarchy gaussians: { ...gaussianData, ...gaussianKeys, ...gaussianRanges }, }, conformation: { - id: UUID.create(), + id: UUID.create22(), spheres: sphereConformation, gaussians: gaussianConformation } diff --git a/src/mol-model/structure/model/properties/custom/descriptor.ts b/src/mol-model/structure/model/properties/custom/descriptor.ts index 3dcb5d9f3..1d2abb21c 100644 --- a/src/mol-model/structure/model/properties/custom/descriptor.ts +++ b/src/mol-model/structure/model/properties/custom/descriptor.ts @@ -31,7 +31,7 @@ function ModelPropertyDescriptor<Ctx, Desc extends ModelPropertyDescriptor<Ctx>> namespace ModelPropertyDescriptor { export function getUUID(prop: ModelPropertyDescriptor): UUID { if (!(prop as any).__key) { - (prop as any).__key = UUID.create(); + (prop as any).__key = UUID.create22(); } return (prop as any).__key; } diff --git a/src/mol-model/structure/model/properties/custom/indexed.ts b/src/mol-model/structure/model/properties/custom/indexed.ts index 2276339bf..9c5ee6ceb 100644 --- a/src/mol-model/structure/model/properties/custom/indexed.ts +++ b/src/mol-model/structure/model/properties/custom/indexed.ts @@ -83,7 +83,7 @@ function arrayToMap<Idx extends IndexedCustomProperty.Index, T>(array: ArrayLike } class SegmentedMappedIndexedCustomProperty<Idx extends IndexedCustomProperty.Index, T = any> implements IndexedCustomProperty<Idx, T> { - readonly id: UUID = UUID.create(); + readonly id: UUID = UUID.create22(); readonly kind: Unit.Kind; has(idx: Idx): boolean { return this.map.has(idx); } get(idx: Idx) { return this.map.get(idx); } @@ -129,7 +129,7 @@ class SegmentedMappedIndexedCustomProperty<Idx extends IndexedCustomProperty.Ind } class ElementMappedCustomProperty<T = any> implements IndexedCustomProperty<ElementIndex, T> { - readonly id: UUID = UUID.create(); + readonly id: UUID = UUID.create22(); readonly kind: Unit.Kind; readonly level = 'atom'; has(idx: ElementIndex): boolean { return this.map.has(idx); } @@ -173,7 +173,7 @@ class ElementMappedCustomProperty<T = any> implements IndexedCustomProperty<Elem } class EntityMappedCustomProperty<T = any> implements IndexedCustomProperty<EntityIndex, T> { - readonly id: UUID = UUID.create(); + readonly id: UUID = UUID.create22(); readonly kind: Unit.Kind; readonly level = 'entity'; has(idx: EntityIndex): boolean { return this.map.has(idx); } diff --git a/src/mol-plugin/behavior/built-in/state.ts b/src/mol-plugin/behavior/built-in/state.ts index 80853b667..f6c3d253b 100644 --- a/src/mol-plugin/behavior/built-in/state.ts +++ b/src/mol-plugin/behavior/built-in/state.ts @@ -7,6 +7,14 @@ import { PluginCommands } from '../../command'; import { PluginContext } from '../../context'; +export function registerAll(ctx: PluginContext) { + SetCurrentObject(ctx); + Update(ctx); + ApplyAction(ctx); + RemoveObject(ctx); + ToggleExpanded(ctx); +} + export function SetCurrentObject(ctx: PluginContext) { PluginCommands.State.SetCurrentObject.subscribe(ctx, ({ state, ref }) => state.setCurrent(ref)); } @@ -22,11 +30,14 @@ export function ApplyAction(ctx: PluginContext) { export function RemoveObject(ctx: PluginContext) { PluginCommands.State.RemoveObject.subscribe(ctx, ({ state, ref }) => { const tree = state.tree.build().delete(ref).getTree(); - console.log('tree', tree); return ctx.runTask(state.update(tree)); }); } +export function ToggleExpanded(ctx: PluginContext) { + PluginCommands.State.ToggleExpanded.subscribe(ctx, ({ state, ref }) => state.updateCellState(ref, ({ isCollapsed }) => ({ isCollapsed: !isCollapsed }))); +} + // export const SetCurrentObject = PluginBehavior.create({ // name: 'set-current-data-object-behavior', // ctor: PluginBehavior.simpleCommandHandler(PluginCommands.State.SetCurrentObject, ({ state, ref }, ctx) => state.setCurrent(ref)), diff --git a/src/mol-plugin/command/command.ts b/src/mol-plugin/command/command.ts index fdf6f7f65..fcf1980a4 100644 --- a/src/mol-plugin/command/command.ts +++ b/src/mol-plugin/command/command.ts @@ -7,22 +7,22 @@ import { PluginContext } from '../context'; import { LinkedList } from 'mol-data/generic'; import { RxEventHelper } from 'mol-util/rx-event-helper'; +import { UUID } from 'mol-util'; export { PluginCommand } interface PluginCommand<T = unknown> { - readonly id: PluginCommand.Id, + readonly id: UUID, dispatch(ctx: PluginContext, params: T): Promise<void>, subscribe(ctx: PluginContext, action: PluginCommand.Action<T>): PluginCommand.Subscription, - params?: { toJSON(params: T): any, fromJSON(json: any): T } + params: { isImmediate: boolean } } /** namespace.id must a globally unique identifier */ -function PluginCommand<T>(namespace: string, id: string, params?: PluginCommand<T>['params']): PluginCommand<T> { - return new Impl(`${namespace}.${id}` as PluginCommand.Id, params); +function PluginCommand<T>(params?: Partial<PluginCommand<T>['params']>): PluginCommand<T> { + return new Impl({ isImmediate: false, ...params }); } -const cmdRepo = new Map<string, PluginCommand<any>>(); class Impl<T> implements PluginCommand<T> { dispatch(ctx: PluginContext, params: T): Promise<void> { return ctx.commands.dispatch(this, params) @@ -30,9 +30,8 @@ class Impl<T> implements PluginCommand<T> { subscribe(ctx: PluginContext, action: PluginCommand.Action<T>): PluginCommand.Subscription { return ctx.commands.subscribe(this, action); } - constructor(public id: PluginCommand.Id, public params: PluginCommand<T>['params']) { - if (cmdRepo.has(id)) throw new Error(`Command id '${id}' already in use.`); - cmdRepo.set(id, this); + id = UUID.create22(); + constructor(public params: PluginCommand<T>['params']) { } } @@ -44,7 +43,7 @@ namespace PluginCommand { } export type Action<T> = (params: T) => void | Promise<void> - type Instance = { id: string, params: any, resolve: () => void, reject: (e: any) => void } + type Instance = { cmd: PluginCommand<any>, params: any, resolve: () => void, reject: (e: any) => void } export class Manager { private subs = new Map<string, Action<any>[]>(); @@ -85,22 +84,27 @@ namespace PluginCommand { /** Resolves after all actions have completed */ - dispatch<T>(cmd: PluginCommand<T> | Id, params: T) { + dispatch<T>(cmd: PluginCommand<T>, params: T) { return new Promise<void>((resolve, reject) => { if (this.disposing) { reject('disposed'); return; } - const id = typeof cmd === 'string' ? cmd : (cmd as PluginCommand<T>).id; - const actions = this.subs.get(id); + const actions = this.subs.get(cmd.id); if (!actions) { resolve(); return; } - this.queue.addLast({ id, params, resolve, reject }); - this.next(); + const instance: Instance = { cmd, params, resolve, reject }; + + if (cmd.params.isImmediate) { + this.resolve(instance); + } else { + this.queue.addLast({ cmd, params, resolve, reject }); + this.next(); + } }); } @@ -111,29 +115,39 @@ namespace PluginCommand { } } - private executing = false; - private async next() { - if (this.queue.count === 0 || this.executing) return; - const cmd = this.queue.removeFirst()!; - - const actions = this.subs.get(cmd.id); + private async resolve(instance: Instance) { + const actions = this.subs.get(instance.cmd.id); if (!actions) { + try { + instance.resolve(); + } finally { + if (!instance.cmd.params.isImmediate && !this.disposing) this.next(); + } return; } try { - this.executing = true; + if (!instance.cmd.params.isImmediate) this.executing = true; // TODO: should actions be called "asynchronously" ("setImmediate") instead? for (const a of actions) { - await a(cmd.params); + await a(instance.params); } - cmd.resolve(); + instance.resolve(); } catch (e) { - cmd.reject(e); + instance.reject(e); } finally { - this.executing = false; - if (!this.disposing) this.next(); + if (!instance.cmd.params.isImmediate) { + this.executing = false; + if (!this.disposing) this.next(); + } } } + + private executing = false; + private async next() { + if (this.queue.count === 0 || this.executing) return; + const instance = this.queue.removeFirst()!; + this.resolve(instance); + } } } \ No newline at end of file diff --git a/src/mol-plugin/command/state.ts b/src/mol-plugin/command/state.ts index c253c75ed..e28518f23 100644 --- a/src/mol-plugin/command/state.ts +++ b/src/mol-plugin/command/state.ts @@ -8,10 +8,12 @@ import { PluginCommand } from './command'; import { Transform, State } from 'mol-state'; import { StateAction } from 'mol-state/action'; -export const SetCurrentObject = PluginCommand<{ state: State, ref: Transform.Ref }>('ms-data', 'set-current-object'); -export const ApplyAction = PluginCommand<{ state: State, action: StateAction.Instance, ref?: Transform.Ref }>('ms-data', 'apply-action'); -export const Update = PluginCommand<{ state: State, tree: State.Tree | State.Builder }>('ms-data', 'update'); +export const SetCurrentObject = PluginCommand<{ state: State, ref: Transform.Ref }>(); +export const ApplyAction = PluginCommand<{ state: State, action: StateAction.Instance, ref?: Transform.Ref }>(); +export const Update = PluginCommand<{ state: State, tree: State.Tree | State.Builder }>(); // export const UpdateObject = PluginCommand<{ ref: Transform.Ref, params: any }>('ms-data', 'update-object'); -export const RemoveObject = PluginCommand<{ state: State, ref: Transform.Ref }>('ms-data', 'remove-object'); \ No newline at end of file +export const RemoveObject = PluginCommand<{ state: State, ref: Transform.Ref }>(); + +export const ToggleExpanded = PluginCommand<{ state: State, ref: Transform.Ref }>({ isImmediate: true }); \ No newline at end of file diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 64364c828..7e98cd711 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -82,17 +82,14 @@ export class PluginContext { } private initBuiltInBehavior() { - BuiltInPluginBehaviors.State.ApplyAction(this); - BuiltInPluginBehaviors.State.RemoveObject(this); - BuiltInPluginBehaviors.State.SetCurrentObject(this); - BuiltInPluginBehaviors.State.Update(this); + BuiltInPluginBehaviors.State.registerAll(this); } async _test_initBehaviors() { const tree = this.state.behavior.tree.build() .toRoot().apply(PluginBehaviors.Representation.AddRepresentationToCanvas, { ref: PluginBehaviors.Representation.AddRepresentationToCanvas.id }) - .and().toRoot().apply(PluginBehaviors.Representation.HighlightLoci, { ref: PluginBehaviors.Representation.HighlightLoci.id }) - .and().toRoot().apply(PluginBehaviors.Representation.SelectLoci, { ref: PluginBehaviors.Representation.SelectLoci.id }) + .toRoot().apply(PluginBehaviors.Representation.HighlightLoci, { ref: PluginBehaviors.Representation.HighlightLoci.id }) + .toRoot().apply(PluginBehaviors.Representation.SelectLoci, { ref: PluginBehaviors.Representation.SelectLoci.id }) .getTree(); await this.runTask(this.state.behavior.update(tree)); diff --git a/src/mol-plugin/ui/state-tree.tsx b/src/mol-plugin/ui/state-tree.tsx index 564725d7f..019d4b780 100644 --- a/src/mol-plugin/ui/state-tree.tsx +++ b/src/mol-plugin/ui/state-tree.tsx @@ -9,6 +9,7 @@ import { PluginStateObject } from 'mol-plugin/state/objects'; import { State } from 'mol-state' import { PluginCommands } from 'mol-plugin/command'; import { PluginComponent } from './base'; +import { merge } from 'rxjs'; export class StateTree extends PluginComponent<{ state: State }, { }> { componentDidMount() { @@ -26,6 +27,12 @@ export class StateTree extends PluginComponent<{ state: State }, { }> { } export class StateTreeNode extends PluginComponent<{ nodeRef: string, state: State }, { }> { + componentDidMount() { + this.subscribe(merge(this.context.events.state.data.object.cellState, this.context.events.state.behavior.object.cellState), o => { + if (o.ref === this.props.nodeRef && o.state === this.props.state) this.forceUpdate(); + }); + } + render() { const n = this.props.state.tree.nodes.get(this.props.nodeRef)!; const cell = this.props.state.cells.get(this.props.nodeRef)!; @@ -50,10 +57,17 @@ export class StateTreeNode extends PluginComponent<{ nodeRef: string, state: Sta }}>{obj.label}</a> {obj.description ? <small>{obj.description}</small> : void 0}</>; } + const expander = <> + [<a href='#' onClick={e => { + e.preventDefault(); + PluginCommands.State.ToggleExpanded.dispatch(this.context, { state: this.props.state, ref: this.props.nodeRef }); + }}>{cell.transform.cellState.isCollapsed ? '+' : '-'}</a>] + </>; + const children = this.props.state.tree.children.get(this.props.nodeRef); return <div> - {remove} {label} - {children.size === 0 + {remove}{children.size === 0 ? void 0 : expander} {label} + {cell.transform.cellState.isCollapsed || children.size === 0 ? void 0 : <div style={{ marginLeft: '7px', paddingLeft: '3px', borderLeft: '1px solid #999' }}>{children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} />)}</div> } diff --git a/src/mol-state/action.ts b/src/mol-state/action.ts index fc86acbea..ad57879c7 100644 --- a/src/mol-state/action.ts +++ b/src/mol-state/action.ts @@ -55,7 +55,7 @@ namespace StateAction { export function create<A extends StateObject, T, P>(definition: Definition<A, T, P>): StateAction<A, T, P> { const action: StateAction<A, T, P> = { create(params) { return { action, params }; }, - id: UUID.create(), + id: UUID.create22(), definition }; return action; diff --git a/src/mol-state/object.ts b/src/mol-state/object.ts index 71cf01482..3160f50c8 100644 --- a/src/mol-state/object.ts +++ b/src/mol-state/object.ts @@ -29,7 +29,7 @@ namespace StateObject { return class implements StateObject<Data, T> { static type = type; static is(obj?: StateObject): obj is StateObject<Data, T> { return !!obj && type === obj.type; } - id = UUID.create(); + id = UUID.create22(); type = type; label: string; description?: string; @@ -49,6 +49,7 @@ interface StateObjectCell { version: string status: StateObjectCell.Status, + visibility: StateObjectCell.Visibility, errorText?: string, obj?: StateObject @@ -58,16 +59,11 @@ namespace StateObjectCell { export type Status = 'ok' | 'error' | 'pending' | 'processing' export interface State { - isObjectHidden: boolean, - isTransformHidden: boolean, - isBinding: boolean, + isHidden: boolean, isCollapsed: boolean } - export const DefaultState: State = { - isObjectHidden: false, - isTransformHidden: false, - isBinding: false, - isCollapsed: false - }; + export const DefaultState: State = { isHidden: false, isCollapsed: false }; + + export type Visibility = 'visible' | 'hidden' | 'partial' } \ No newline at end of file diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index d47a6be24..5ed8cb8d6 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -15,11 +15,12 @@ import { RxEventHelper } from 'mol-util/rx-event-helper'; import { StateTreeBuilder } from './tree/builder'; import { StateAction } from './action'; import { StateActionManager } from './action/manager'; +import { TransientTree } from './tree/transient'; export { State } class State { - private _tree: StateTree = StateTree.createEmpty(); + private _tree: TransientTree = StateTree.createEmpty().asTransient(); protected errorFree = true; private transformCache = new Map<Transform.Ref, unknown>(); @@ -46,7 +47,7 @@ class State { readonly actions = new StateActionManager(); - get tree() { return this._tree; } + get tree(): StateTree { return this._tree; } get current() { return this.behaviors.currentObject.value.ref; } build() { return this._tree.build(); } @@ -66,8 +67,14 @@ class State { this.behaviors.currentObject.next({ state: this, ref }); } - updateCellState(ref: Transform.Ref, state?: Partial<StateObjectCell.State>) { - // TODO + updateCellState(ref: Transform.Ref, stateOrProvider: ((old: StateObjectCell.State) => Partial<StateObjectCell.State>) | Partial<StateObjectCell.State>) { + const cell = this.cells.get(ref)!; + const state = typeof stateOrProvider === 'function' + ? stateOrProvider(cell.transform.cellState) + : stateOrProvider; + + cell.transform = this._tree.setCellState(ref, state); + this.events.object.cellState.next({ state: this, ref, cell }); } dispose() { @@ -96,7 +103,7 @@ class State { } update(tree: StateTree | StateTreeBuilder): Task<void> { - const _tree = StateTreeBuilder.is(tree) ? tree.getTree() : tree; + const _tree = (StateTreeBuilder.is(tree) ? tree.getTree() : tree).asTransient(); return Task.create('Update Tree', async taskCtx => { let updated = false; try { @@ -137,7 +144,9 @@ class State { sourceRef: void 0, obj: rootObject, status: 'ok', - version: root.version + visibility: 'visible', + version: root.version, + errorText: void 0 }); this.globalContext = params && params.globalContext; @@ -284,13 +293,19 @@ function initCellStatus(ctx: UpdateContext, roots: Ref[]) { } function initCellsVisitor(transform: Transform, _: any, ctx: UpdateContext) { - if (ctx.cells.has(transform.ref)) return; + if (ctx.cells.has(transform.ref)) { + if (transform.cellState && transform.cellState.isHidden) { + ctx.cells.get(transform.ref)!.visibility = 'hidden'; + } + return; + } const obj: StateObjectCell = { transform, sourceRef: void 0, status: 'pending', - version: UUID.create(), + visibility: transform.cellState && transform.cellState.isHidden ? 'hidden' : 'visible', + version: UUID.create22(), errorText: void 0 }; ctx.cells.set(transform.ref, obj); @@ -323,7 +338,7 @@ function _findNewCurrent(tree: StateTree, ref: Ref, deletes: Set<Ref>): Ref { if (deletes.has(s.value)) continue; const t = tree.nodes.get(s.value); - if (t.cellState && t.cellState.isTransformHidden) continue; + if (t.props && t.props.isGhost) continue; if (s.value === ref) { seenRef = true; if (!deletes.has(ref)) prevCandidate = ref; @@ -383,7 +398,7 @@ async function updateSubtree(ctx: UpdateContext, root: Ref) { ctx.parent.events.object.created.next({ state: ctx.parent, ref: root, obj: update.obj! }); if (!ctx.hadError) { const transform = ctx.tree.nodes.get(root); - if (!transform.cellState || !transform.cellState.isTransformHidden) ctx.newCurrent = root; + if (!transform.props || !transform.props.isGhost) ctx.newCurrent = root; } } else if (update.action === 'updated') { ctx.parent.events.object.updated.next({ state: ctx.parent, ref: root, action: 'in-place', obj: update.obj }); diff --git a/src/mol-state/transform.ts b/src/mol-state/transform.ts index c0203be3d..b8b74fc08 100644 --- a/src/mol-state/transform.ts +++ b/src/mol-state/transform.ts @@ -12,10 +12,10 @@ export interface Transform<A extends StateObject = StateObject, B extends StateO readonly parent: Transform.Ref, readonly transformer: Transformer<A, B, P>, readonly params: P, + readonly props: Transform.Props, readonly ref: Transform.Ref, readonly version: string, - readonly cellState?: Partial<StateObjectCell.State>, - readonly tag?: string + readonly cellState: StateObjectCell.State } export namespace Transform { @@ -23,23 +23,37 @@ export namespace Transform { export const RootRef = '-=root=-' as Ref; - export interface Options { ref?: Ref, tag?: string, cellState?: Partial<StateObjectCell.State> } + export interface Props { + tag?: string + isGhost?: boolean, + isBinding?: boolean + } + + export interface Options { + ref?: string, + props?: Props, + cellState?: Partial<StateObjectCell.State> + } export function create<A extends StateObject, B extends StateObject, P>(parent: Ref, transformer: Transformer<A, B, P>, params?: P, options?: Options): Transform<A, B, P> { - const ref = options && options.ref ? options.ref : UUID.create() as string as Ref; + const ref = options && options.ref ? options.ref : UUID.create22() as string as Ref; return { parent, transformer, params: params || {} as any, + props: (options && options.props) || { }, ref, - version: UUID.create(), - cellState: options && options.cellState, - tag: options && options.tag + version: UUID.create22(), + cellState: { ...StateObjectCell.DefaultState, ...(options && options.cellState) } } } - export function updateParams<T>(t: Transform, params: any): Transform { - return { ...t, params, version: UUID.create() }; + export function withParams<T>(t: Transform, params: any): Transform { + return { ...t, params, version: UUID.create22() }; + } + + export function withCellState<T>(t: Transform, state: Partial<StateObjectCell.State>): Transform { + return { ...t, cellState: { ...t.cellState, ...state } }; } export function createRoot(): Transform { @@ -50,10 +64,10 @@ export namespace Transform { parent: string, transformer: string, params: any, + props: Props, ref: string, version: string, - cellState?: Partial<StateObjectCell.State>, - tag?: string + cellState: StateObjectCell.State, } function _id(x: any) { return x; } @@ -65,10 +79,10 @@ export namespace Transform { parent: t.parent, transformer: t.transformer.id, params: pToJson(t.params), + props: t.props, ref: t.ref, version: t.version, cellState: t.cellState, - tag: t.tag }; } @@ -81,10 +95,10 @@ export namespace Transform { parent: t.parent as Ref, transformer, params: pFromJson(t.params), + props: t.props, ref: t.ref as Ref, version: t.version, cellState: t.cellState, - tag: t.tag }; } } \ No newline at end of file diff --git a/src/mol-state/tree/builder.ts b/src/mol-state/tree/builder.ts index d79770bf6..56f33a18e 100644 --- a/src/mol-state/tree/builder.ts +++ b/src/mol-state/tree/builder.ts @@ -9,7 +9,6 @@ import { TransientTree } from './transient'; import { StateObject } from '../object'; import { Transform } from '../transform'; import { Transformer } from '../transformer'; -import { shallowEqual } from 'mol-util'; export { StateTreeBuilder } @@ -63,30 +62,25 @@ namespace StateTreeBuilder { update<T extends Transformer<A, any, any>>(transformer: T, params: (old: Transformer.Params<T>) => Transformer.Params<T>): Root update(params: any): Root update<T extends Transformer<A, any, any>>(paramsOrTransformer: T, provider?: (old: Transformer.Params<T>) => Transformer.Params<T>) { - const old = this.state.tree.nodes.get(this.ref)!; let params: any; if (provider) { + const old = this.state.tree.nodes.get(this.ref)!; params = provider(old.params as any); } else { params = paramsOrTransformer; } - if (old.transformer.definition.params && old.transformer.definition.params.areEqual) { - if (old.transformer.definition.params.areEqual(old.params, params)) return this.root; - } else { - if (shallowEqual(old.params, params)) { - return this.root; - } + if (this.state.tree.setParams(this.ref, params)) { + this.editInfo.count++; + this.editInfo.lastUpdate = this.ref; } - this.editInfo.count++; - this.editInfo.lastUpdate = this.ref; - - this.state.tree.set(Transform.updateParams(old, params)); return this.root; } - and() { return this.root; } + to<A extends StateObject>(ref: Transform.Ref) { return this.root.to<A>(ref); } + toRoot<A extends StateObject>() { return this.root.toRoot<A>(); } + delete(ref: Transform.Ref) { return this.root.delete(ref); } getTree(): StateTree { return this.state.tree.asImmutable(); } diff --git a/src/mol-state/tree/immutable.ts b/src/mol-state/tree/immutable.ts index 73bf20757..e43db8316 100644 --- a/src/mol-state/tree/immutable.ts +++ b/src/mol-state/tree/immutable.ts @@ -34,9 +34,15 @@ namespace StateTree { readonly map: OrderedSet<Ref>['map'] } + interface _Map<T> { + readonly size: number, + has(ref: Ref): boolean, + get(ref: Ref): T + } + export type Node = Transform - export type Nodes = ImmutableMap<Ref, Transform> - export type Children = ImmutableMap<Ref, ChildSet> + export type Nodes = _Map<Transform> + export type Children = _Map<ChildSet> class Impl implements StateTree { get root() { return this.nodes.get(Transform.RootRef)! } @@ -67,7 +73,7 @@ namespace StateTree { type VisitorCtx = { tree: StateTree, state: any, f: (node: Node, tree: StateTree, state: any) => boolean | undefined | void }; - function _postOrderFunc(this: VisitorCtx, c: Ref | undefined) { _doPostOrder(this, this.tree.nodes.get(c!)); } + function _postOrderFunc(this: VisitorCtx, c: Ref | undefined) { _doPostOrder(this, this.tree.nodes.get(c!)!); } function _doPostOrder(ctx: VisitorCtx, root: Node) { const children = ctx.tree.children.get(root.ref); if (children && children.size) { @@ -85,7 +91,7 @@ namespace StateTree { return ctx.state; } - function _preOrderFunc(this: VisitorCtx, c: Ref | undefined) { _doPreOrder(this, this.tree.nodes.get(c!)); } + function _preOrderFunc(this: VisitorCtx, c: Ref | undefined) { _doPreOrder(this, this.tree.nodes.get(c!)!); } function _doPreOrder(ctx: VisitorCtx, root: Node) { const ret = ctx.f(root, ctx.tree, ctx.state); if (typeof ret === 'boolean' && !ret) return; diff --git a/src/mol-state/tree/transient.ts b/src/mol-state/tree/transient.ts index be807e77c..a944fe8aa 100644 --- a/src/mol-state/tree/transient.ts +++ b/src/mol-state/tree/transient.ts @@ -4,25 +4,29 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { OrderedSet } from 'immutable'; +import { Map as ImmutableMap, OrderedSet } from 'immutable'; import { Transform } from '../transform'; import { StateTree } from './immutable'; import { StateTreeBuilder } from './builder'; +import { StateObjectCell } from 'mol-state/object'; +import { shallowEqual } from 'mol-util/object'; +import { UUID } from 'mol-util'; export { TransientTree } class TransientTree implements StateTree { - nodes = this.tree.nodes; - children = this.tree.children; + nodes = this.tree.nodes as ImmutableMap<Transform.Ref, Transform>; + children = this.tree.children as ImmutableMap<Transform.Ref, OrderedSet<Transform.Ref>>; private changedNodes = false; private changedChildren = false; - private _mutations: Map<Transform.Ref, OrderedSet<Transform.Ref>> = void 0 as any; + private _childMutations: Map<Transform.Ref, OrderedSet<Transform.Ref>> | undefined = void 0; + private _transformMutations: Map<Transform.Ref, Transform> | undefined = void 0; - private get mutations() { - if (this._mutations) return this._mutations; - this._mutations = new Map(); - return this._mutations; + private get childMutations() { + if (this._childMutations) return this._childMutations; + this._childMutations = new Map(); + return this._childMutations; } get root() { return this.nodes.get(Transform.RootRef)! } @@ -41,13 +45,13 @@ class TransientTree implements StateTree { this.children = this.children.asMutable(); } - if (this.mutations.has(parent)) { - this.mutations.get(parent)!.add(child); + if (this.childMutations.has(parent)) { + this.childMutations.get(parent)!.add(child); } else { const set = (this.children.get(parent) as OrderedSet<Transform.Ref>).asMutable(); set.add(child); this.children.set(parent, set); - this.mutations.set(parent, set); + this.childMutations.set(parent, set); } } @@ -57,13 +61,13 @@ class TransientTree implements StateTree { this.children = this.children.asMutable(); } - if (this.mutations.has(parent)) { - this.mutations.get(parent)!.remove(child); + if (this.childMutations.has(parent)) { + this.childMutations.get(parent)!.remove(child); } else { const set = (this.children.get(parent) as OrderedSet<Transform.Ref>).asMutable(); set.remove(child); this.children.set(parent, set); - this.mutations.set(parent, set); + this.childMutations.set(parent, set); } } @@ -76,7 +80,7 @@ class TransientTree implements StateTree { this.children = this.children.asMutable(); } this.children.set(parent, set); - this.mutations.set(parent, set); + this.childMutations.set(parent, set); } add(transform: Transform) { @@ -111,7 +115,48 @@ class TransientTree implements StateTree { return this; } - set(transform: Transform) { + /** Calls Transform.definition.params.areEqual if available, otherwise uses shallowEqual to check if the params changed */ + setParams(ref: Transform.Ref, params: unknown) { + ensurePresent(this.nodes, ref); + + const transform = this.nodes.get(ref)!; + const def = transform.transformer.definition; + if (def.params && def.params.areEqual) { + if (def.params.areEqual(transform.params, params)) return false; + } else { + if (shallowEqual(transform.params, params)) { + return false; + } + } + + if (this._transformMutations && this._transformMutations.has(transform.ref)) { + const mutated = this._transformMutations.get(transform.ref)!; + (mutated.params as any) = params; + (mutated.version as UUID) = UUID.create22(); + } else { + this.set(Transform.withParams(transform, params)); + } + + return true; + } + + setCellState(ref: Transform.Ref, state: Partial<StateObjectCell.State>) { + ensurePresent(this.nodes, ref); + + if (this._transformMutations && this._transformMutations.has(ref)) { + const transform = this._transformMutations.get(ref)!; + const old = transform.cellState; + (transform.cellState as StateObjectCell.State) = { ...old, ...state }; + return transform; + } else { + const transform = this.nodes.get(ref); + const newT = Transform.withCellState(transform, state); + this.set(newT); + return newT; + } + } + + private set(transform: Transform) { ensurePresent(this.nodes, transform.ref); if (!this.changedNodes) { @@ -119,6 +164,11 @@ class TransientTree implements StateTree { this.nodes = this.nodes.asMutable(); } + if (!this._transformMutations) { + this._transformMutations = new Map(); + } + this._transformMutations.set(transform.ref, transform); + this.nodes.set(transform.ref, transform); return this; } @@ -145,15 +195,15 @@ class TransientTree implements StateTree { for (const n of st) { this.nodes.delete(n.ref); this.children.delete(n.ref); - if (this._mutations) this._mutations.delete(n.ref); + if (this._childMutations) this._childMutations.delete(n.ref); } return st; } asImmutable() { - if (!this.changedNodes && !this.changedChildren && !this._mutations) return this.tree; - if (this._mutations) this._mutations.forEach(fixChildMutations, this.children); + if (!this.changedNodes && !this.changedChildren && !this._childMutations) return this.tree; + if (this._childMutations) this._childMutations.forEach(fixChildMutations, this.children); return StateTree.create( this.changedNodes ? this.nodes.asImmutable() : this.nodes, this.changedChildren ? this.children.asImmutable() : this.children); @@ -164,7 +214,7 @@ class TransientTree implements StateTree { } } -function fixChildMutations(this: StateTree.Children, m: OrderedSet<Transform.Ref>, k: Transform.Ref) { this.set(k, m.asImmutable()); } +function fixChildMutations(this: ImmutableMap<Transform.Ref, OrderedSet<Transform.Ref>>, m: OrderedSet<Transform.Ref>, k: Transform.Ref) { this.set(k, m.asImmutable()); } function alreadyPresent(ref: Transform.Ref) { throw new Error(`Transform '${ref}' is already present in the tree.`); diff --git a/src/mol-util/uuid.ts b/src/mol-util/uuid.ts index fd96f6e1e..5ddf5bb22 100644 --- a/src/mol-util/uuid.ts +++ b/src/mol-util/uuid.ts @@ -10,19 +10,24 @@ type UUID = string & { '@type': 'uuid' } namespace UUID { const chars: string[] = []; - export function create(): UUID { + /** Creates 22 characted "base64" UUID */ + export function create22(): UUID { let d = (+new Date()) + now(); for (let i = 0; i < 16; i++) { chars[i] = String.fromCharCode((d + Math.random()*0xff)%0xff | 0); d = Math.floor(d/0xff); } return btoa(chars.join('')).replace(/\+/g, '-').replace(/\//g, '_').substr(0, 22) as UUID; - // const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - // const r = (d + Math.random()*16)%16 | 0; - // d = Math.floor(d/16); - // return (c==='x' ? r : (r&0x3|0x8)).toString(16); - // }); - // return uuid as any; + } + + export function createv4(): UUID { + let d = (+new Date()) + now(); + const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = (d + Math.random()*16)%16 | 0; + d = Math.floor(d/16); + return (c==='x' ? r : (r&0x3|0x8)).toString(16); + }); + return uuid as any; } } diff --git a/src/servers/model/properties/providers/pdbe.ts b/src/servers/model/properties/providers/pdbe.ts index df2b852b4..66fe25ef9 100644 --- a/src/servers/model/properties/providers/pdbe.ts +++ b/src/servers/model/properties/providers/pdbe.ts @@ -83,7 +83,7 @@ function getParam<T>(params: any, ...path: string[]): T | undefined { function apiQueryProvider(urlPrefix: string, cache: any) { - const cacheKey = UUID.create(); + const cacheKey = UUID.create22(); return async (model: Model) => { try { if (cache[cacheKey]) return cache[cacheKey]; diff --git a/src/servers/model/server/jobs.ts b/src/servers/model/server/jobs.ts index 7697d30c5..f1e408174 100644 --- a/src/servers/model/server/jobs.ts +++ b/src/servers/model/server/jobs.ts @@ -43,7 +43,7 @@ export function createJob<Name extends QueryName>(definition: JobDefinition<Name const normalizedParams = normalizeQueryParams(queryDefinition, definition.queryParams); const sourceId = definition.sourceId || '_local_'; return { - id: UUID.create(), + id: UUID.create22(), datetime_utc: `${new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')}`, key: `${sourceId}/${definition.entryId}`, sourceId, diff --git a/src/servers/volume/server/query/execute.ts b/src/servers/volume/server/query/execute.ts index d6ecf6ecf..c5d4272df 100644 --- a/src/servers/volume/server/query/execute.ts +++ b/src/servers/volume/server/query/execute.ts @@ -26,7 +26,7 @@ export default async function execute(params: Data.QueryParams, outputProvider: const start = getTime(); State.pendingQueries++; - const guid = UUID.create() as any as string; + const guid = UUID.create22() as any as string; params.detail = Math.min(Math.max(0, params.detail | 0), ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1); ConsoleLogger.logId(guid, 'Info', `id=${params.sourceId},encoding=${params.asBinary ? 'binary' : 'text'},detail=${params.detail},${queryBoxToString(params.box)}`); -- GitLab