Browse Source

StructureHierarchyManager sync on demand
+ PluginComponent updates

David Sehnal 5 years ago
parent
commit
513bfeaae7

+ 2 - 2
src/mol-plugin-state/animation/manager.ts

@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { PluginComponent } from '../component';
+import { StatefulPluginComponent } from '../component';
 import { PluginContext } from '../../mol-plugin/context';
 import { PluginStateAnimation } from './model';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
@@ -15,7 +15,7 @@ export { PluginAnimationManager }
 // TODO: handle unregistered animations on state restore
 // TODO: better API
 
-class PluginAnimationManager extends PluginComponent<PluginAnimationManager.State> {
+class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationManager.State> {
     private map = new Map<string, PluginStateAnimation>();
     private animations: PluginStateAnimation[] = [];
     private _current: PluginAnimationManager.Current;

+ 2 - 2
src/mol-plugin-state/camera.ts

@@ -7,11 +7,11 @@
 import { Camera } from '../mol-canvas3d/camera';
 import { OrderedMap } from 'immutable';
 import { UUID } from '../mol-util';
-import { PluginComponent } from './component';
+import { StatefulPluginComponent } from './component';
 
 export { CameraSnapshotManager }
 
-class CameraSnapshotManager extends PluginComponent<{ entries: OrderedMap<string, CameraSnapshotManager.Entry> }> {
+class CameraSnapshotManager extends StatefulPluginComponent<{ entries: OrderedMap<string, CameraSnapshotManager.Entry> }> {
     readonly events = {
         changed: this.ev()
     };

+ 20 - 6
src/mol-plugin-state/component.ts

@@ -1,19 +1,36 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { shallowMergeArray } from '../mol-util/object';
 import { RxEventHelper } from '../mol-util/rx-event-helper';
+import { Subscription, Observable } from 'rxjs';
 
-export class PluginComponent<State> {
+export class PluginComponent {
     private _ev: RxEventHelper | undefined;
+    private subs: Subscription[] | undefined = void 0;
+
+    protected subscribe<T>(obs: Observable<T>, action: (v: T) => void) {
+        if (typeof this.subs === 'undefined') this.subs = [];
+        this.subs.push(obs.subscribe(action));
+    }
 
     protected get ev() {
         return this._ev || (this._ev = RxEventHelper.create());
     }
 
+    dispose() {
+        if (this._ev) this._ev.dispose();
+        if (this.subs) {
+            for (const s of this.subs) s.unsubscribe();
+            this.subs = void 0;
+        }
+    }
+}
+
+export class StatefulPluginComponent<State> extends PluginComponent {
     private _state: State;
 
     protected updateState(...states: Partial<State>[]): boolean {
@@ -30,11 +47,8 @@ export class PluginComponent<State> {
         return this._state;
     }
 
-    dispose() {
-        if (this._ev) this._ev.dispose();
-    }
-
     constructor(initialState: State) {
+        super();
         this._state = initialState;
     }
 }

+ 2 - 2
src/mol-plugin-state/manager/interactivity.ts

@@ -13,7 +13,7 @@ import { ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer'
 import { MarkerAction } from '../../mol-util/marker-action';
 import { shallowEqual } from '../../mol-util/object';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
-import { PluginComponent } from '../component';
+import { StatefulPluginComponent } from '../component';
 import { StructureSelectionManager } from './structure/selection';
 
 export { InteractivityManager };
@@ -31,7 +31,7 @@ const DefaultInteractivityFocusOptions = {
 
 export type InteractivityFocusLociOptions = typeof DefaultInteractivityFocusOptions
 
-class InteractivityManager extends PluginComponent<InteractivityManagerState> {
+class InteractivityManager extends StatefulPluginComponent<InteractivityManagerState> {
     readonly lociSelects: InteractivityManager.LociSelectManager;
     readonly lociHighlights: InteractivityManager.LociHighlightManager;
 

+ 3 - 3
src/mol-plugin-state/manager/structure/component.ts

@@ -16,7 +16,7 @@ import { UUID } from '../../../mol-util';
 import { ColorNames } from '../../../mol-util/color/names';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { StructureRepresentationPresetProvider } from '../../builder/structure/representation-preset';
-import { PluginComponent } from '../../component';
+import { StatefulPluginComponent } from '../../component';
 import { StructureComponentParams } from '../../helpers/structure-component';
 import { clearStructureOverpaint, setStructureOverpaint } from '../../helpers/structure-overpaint';
 import { StructureSelectionQueries, StructureSelectionQuery, StructureSelectionQueryOptions } from '../../helpers/structure-selection-query';
@@ -32,13 +32,13 @@ interface StructureComponentManagerState {
     options: StructureComponentManager.Options
 }
 
-class StructureComponentManager extends PluginComponent<StructureComponentManagerState> {
+class StructureComponentManager extends StatefulPluginComponent<StructureComponentManagerState> {
     readonly events = {
         optionsUpdated: this.ev<undefined>()
     }
 
     get currentStructures() {
-        return this.plugin.managers.structure.hierarchy.state.selection.structures;
+        return this.plugin.managers.structure.hierarchy.selection.structures;
     }
 
     get pivotStructure(): StructureRef | undefined {

+ 39 - 27
src/mol-plugin-state/manager/structure/hierarchy.ts

@@ -12,22 +12,24 @@ import { StateTransform } from '../../../mol-state';
 import { applyTrajectoryHierarchyPreset } from '../../builder/structure/hierarchy-preset';
 import { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state';
 
-interface StructureHierarchyManagerState {
-    hierarchy: StructureHierarchy,
-    selection: {
-        trajectories: ReadonlyArray<TrajectoryRef>,
-        models: ReadonlyArray<ModelRef>,
-        structures: ReadonlyArray<StructureRef>
+export class StructureHierarchyManager extends PluginComponent {
+    private state = {
+        syncedTree: this.dataState.tree,
+
+        hierarchy: StructureHierarchy(),
+        selection: {
+            trajectories: [] as ReadonlyArray<TrajectoryRef>,
+            models: [] as ReadonlyArray<ModelRef>,
+            structures: []  as ReadonlyArray<StructureRef>
+        }
     }
-}
 
-export class StructureHierarchyManager extends PluginComponent<StructureHierarchyManagerState> {
     readonly behaviors = {
         selection: this.ev.behavior({
-            hierarchy: this.state.hierarchy,
-            trajectories: this.state.selection.trajectories,
-            models: this.state.selection.models,
-            structures: this.state.selection.structures
+            hierarchy: this.current,
+            trajectories: this.selection.trajectories,
+            models: this.selection.models,
+            structures: this.selection.structures
         })
     }
 
@@ -39,7 +41,7 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
 
     get currentComponentGroups() {
         if (this._currentComponentGroups) return this._currentComponentGroups;
-        this._currentComponentGroups = StructureHierarchyManager.getComponentGroups(this.state.selection.structures);
+        this._currentComponentGroups = StructureHierarchyManager.getComponentGroups(this.selection.structures);
         return this._currentComponentGroups;
     }
 
@@ -47,17 +49,19 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
     get seletionSet() {
         if (this._currentSelectionSet) return this._currentSelectionSet;
         this._currentSelectionSet = new Set();
-        for (const r of this.state.selection.trajectories) this._currentSelectionSet.add(r.cell.transform.ref);
-        for (const r of this.state.selection.models) this._currentSelectionSet.add(r.cell.transform.ref);
-        for (const r of this.state.selection.structures) this._currentSelectionSet.add(r.cell.transform.ref);
+        for (const r of this.selection.trajectories) this._currentSelectionSet.add(r.cell.transform.ref);
+        for (const r of this.selection.models) this._currentSelectionSet.add(r.cell.transform.ref);
+        for (const r of this.selection.structures) this._currentSelectionSet.add(r.cell.transform.ref);
         return this._currentSelectionSet;
     }
 
     get current() {
+        this.sync();
         return this.state.hierarchy;
     }
 
     get selection() {
+        this.sync();
         return this.state.selection;
     }
 
@@ -75,7 +79,11 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
     }
 
     private sync() {
-        const update = buildStructureHierarchy(this.plugin.state.data, this.state.hierarchy);
+        if (this.state.syncedTree === this.dataState.tree) return;
+
+        this.state.syncedTree = this.dataState.tree;
+
+        const update = buildStructureHierarchy(this.plugin.state.data, this.current);
         if (!update.changed) {
             return;
         }
@@ -88,12 +96,16 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
         this._currentComponentGroups = void 0;
         this._currentSelectionSet = void 0;
 
-        this.updateState({ hierarchy, selection: { trajectories, models, structures } });
+        this.state.hierarchy = hierarchy;
+        this.state.selection.trajectories = trajectories;
+        this.state.selection.models = models;
+        this.state.selection.structures = structures;
+
         this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
     }
 
     updateCurrent(refs: HierarchyRef[], action: 'add' | 'remove') {
-        const hierarchy = this.state.hierarchy;
+        const hierarchy = this.current;
         const set = action === 'add'
             ? SetUtils.union(this.seletionSet, new Set(refs.map(r => r.cell.transform.ref)))
             : SetUtils.difference(this.seletionSet, new Set(refs.map(r => r.cell.transform.ref)));
@@ -115,7 +127,10 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
         this._currentComponentGroups = void 0;
         this._currentSelectionSet = void 0;
 
-        this.updateState({ selection: { trajectories, models, structures } });
+        this.state.selection.trajectories = trajectories;
+        this.state.selection.models = models;
+        this.state.selection.structures = structures;
+
         this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
     }
 
@@ -163,17 +178,14 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
     }
 
     constructor(private plugin: PluginContext) {
-        super({
-            hierarchy: StructureHierarchy(),
-            selection: { trajectories: [], models: [], structures: [] }
-        });
+        super();
 
-        plugin.state.data.events.changed.subscribe(e => {
+        this.subscribe(plugin.state.data.events.changed, e => {
             if (e.inTransaction || plugin.behaviors.state.isAnimating.value) return;
             this.sync();
         });
 
-        plugin.behaviors.state.isAnimating.subscribe(isAnimating => {
+        this.subscribe(plugin.behaviors.state.isAnimating, isAnimating => {
             if (!isAnimating && !plugin.behaviors.state.isUpdating.value) this.sync();
         });
     }
@@ -206,7 +218,7 @@ export namespace StructureHierarchyManager {
     }
 
     export function getSelectedStructuresDescription(plugin: PluginContext) {
-        const { structures } = plugin.managers.structure.hierarchy.state.selection;
+        const { structures } = plugin.managers.structure.hierarchy.selection;
         if (structures.length === 0) return '';
 
         if (structures.length === 1) {

+ 2 - 2
src/mol-plugin-state/manager/structure/measurement.ts

@@ -11,7 +11,7 @@ import { StateTransforms } from '../../transforms';
 import { PluginCommands } from '../../../mol-plugin/commands';
 import { arraySetAdd } from '../../../mol-util/array';
 import { PluginStateObject } from '../../objects';
-import { PluginComponent } from '../../component';
+import { StatefulPluginComponent } from '../../component';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { MeasurementRepresentationCommonTextParams } from '../../../mol-repr/shape/loci/common';
 
@@ -37,7 +37,7 @@ export interface StructureMeasurementManagerState {
     options: StructureMeasurementOptions
 }
 
-class StructureMeasurementManager extends PluginComponent<StructureMeasurementManagerState>  {
+class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasurementManagerState>  {
     readonly behaviors = {
         state: this.ev.behavior(this.state)
     };

+ 3 - 3
src/mol-plugin-state/manager/structure/selection.ts

@@ -17,7 +17,7 @@ import { StateObject, StateObjectRef } from '../../../mol-state';
 import { Task } from '../../../mol-task';
 import { structureElementStatsLabel } from '../../../mol-theme/label';
 import { arrayRemoveAtInPlace } from '../../../mol-util/array';
-import { PluginComponent } from '../../component';
+import { StatefulPluginComponent } from '../../component';
 import { StructureSelectionQuery } from '../../helpers/structure-selection-query';
 import { PluginStateObject } from '../../objects';
 import { UUID } from '../../../mol-util';
@@ -33,7 +33,7 @@ const HISTORY_CAPACITY = 8;
 
 export type StructureSelectionModifier = 'add' | 'remove' | 'intersect' | 'set'
 
-export class StructureSelectionManager extends PluginComponent<StructureSelectionManagerState> {
+export class StructureSelectionManager extends StatefulPluginComponent<StructureSelectionManagerState> {
     readonly events = {
         changed: this.ev<undefined>(),
         additionsHistoryUpdated: this.ev<undefined>()
@@ -384,7 +384,7 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
     }
 
     private get applicableStructures() {
-        return this.plugin.managers.structure.hierarchy.state.selection.structures
+        return this.plugin.managers.structure.hierarchy.selection.structures
             .filter(s => !!s.cell.obj)
             .map(s => s.cell.obj!.data);
     }

+ 2 - 2
src/mol-plugin-state/snapshots.ts

@@ -7,12 +7,12 @@
 import { List } from 'immutable';
 import { UUID } from '../mol-util';
 import { PluginState } from '../mol-plugin/state';
-import { PluginComponent } from './component';
+import { StatefulPluginComponent } from './component';
 import { PluginContext } from '../mol-plugin/context';
 
 export { PluginStateSnapshotManager }
 
-class PluginStateSnapshotManager extends PluginComponent<{
+class PluginStateSnapshotManager extends StatefulPluginComponent<{
     current?: UUID | undefined,
     entries: List<PluginStateSnapshotManager.Entry>,
     isPlaying: boolean,

+ 1 - 1
src/mol-plugin-ui/structure/components.tsx

@@ -103,7 +103,7 @@ class ComponentEditorControls extends PurePluginUIComponent<{}, ComponentEditorC
         if (!item) return;
         const mng = this.plugin.managers.structure;
 
-        const structures = mng.hierarchy.state.selection.structures;
+        const structures = mng.hierarchy.selection.structures;
         if (item.value === null) mng.component.clear(structures);
         else mng.component.applyPreset(structures, item.value as any);
     }

+ 1 - 1
src/mol-plugin-ui/structure/source.tsx

@@ -115,7 +115,7 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
     }
 
     get label() {
-        const { structures, models, trajectories } = this.plugin.managers.structure.hierarchy.state.selection;
+        const { structures, models, trajectories } = this.plugin.managers.structure.hierarchy.selection;
 
         if (structures.length === 1) {
             const s = structures[0];

+ 2 - 2
src/mol-plugin/layout.ts

@@ -6,7 +6,7 @@
  */
 
 import { ParamDefinition as PD } from '../mol-util/param-definition';
-import { PluginComponent } from '../mol-plugin-state/component';
+import { StatefulPluginComponent } from '../mol-plugin-state/component';
 import { PluginContext } from './context';
 import { PluginCommands } from './commands';
 
@@ -59,7 +59,7 @@ interface RootState {
     zIndex: string | null
 }
 
-export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
+export class PluginLayout extends StatefulPluginComponent<PluginLayoutStateProps> {
     readonly events = {
         updated: this.ev()
     }

+ 2 - 2
src/mol-plugin/util/toast.ts

@@ -6,7 +6,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { PluginComponent } from '../../mol-plugin-state/component';
+import { StatefulPluginComponent } from '../../mol-plugin-state/component';
 import { OrderedMap } from 'immutable';
 import { PluginContext } from '../context';
 import { PluginCommands } from '../commands';
@@ -27,7 +27,7 @@ export interface PluginToast {
     timeoutMs?: number
 }
 
-export class PluginToastManager extends PluginComponent<{
+export class PluginToastManager extends StatefulPluginComponent<{
     entries: OrderedMap<number, PluginToastManager.Entry>
 }> {
     readonly events = {