Skip to content
Snippets Groups Projects
Commit 72a156ed authored by David Sehnal's avatar David Sehnal
Browse files

mol-plugin: switch between data and behavior trees

parent 458eae3b
No related branches found
No related tags found
No related merge requests found
...@@ -17,7 +17,7 @@ export function registerDefault(ctx: PluginContext) { ...@@ -17,7 +17,7 @@ export function registerDefault(ctx: PluginContext) {
export function Reset(ctx: PluginContext) { export function Reset(ctx: PluginContext) {
PluginCommands.Camera.Reset.subscribe(ctx, () => { PluginCommands.Camera.Reset.subscribe(ctx, () => {
const sel = ctx.state.data.select(q => q.root.subtree().ofType(SO.Molecule.Structure)); const sel = ctx.state.dataState.select(q => q.root.subtree().ofType(SO.Molecule.Structure));
if (!sel.length) return; if (!sel.length) return;
const center = (sel[0].obj! as SO.Molecule.Structure).data.boundary.sphere.center; const center = (sel[0].obj! as SO.Molecule.Structure).data.boundary.sphere.center;
......
...@@ -12,14 +12,15 @@ export function registerDefault(ctx: PluginContext) { ...@@ -12,14 +12,15 @@ export function registerDefault(ctx: PluginContext) {
} }
export function SyncRepresentationToCanvas(ctx: PluginContext) { export function SyncRepresentationToCanvas(ctx: PluginContext) {
ctx.events.state.data.object.created.subscribe(e => { const events = ctx.state.dataState.events;
events.object.created.subscribe(e => {
if (!SO.isRepresentation3D(e.obj)) return; if (!SO.isRepresentation3D(e.obj)) return;
ctx.canvas3d.add(e.obj.data); ctx.canvas3d.add(e.obj.data);
ctx.canvas3d.requestDraw(true); ctx.canvas3d.requestDraw(true);
// TODO: update visiblity // TODO: update visiblity
}); });
ctx.events.state.data.object.updated.subscribe(e => { events.object.updated.subscribe(e => {
if (e.oldObj && SO.isRepresentation3D(e.oldObj)) { if (e.oldObj && SO.isRepresentation3D(e.oldObj)) {
ctx.canvas3d.remove(e.oldObj.data); ctx.canvas3d.remove(e.oldObj.data);
ctx.canvas3d.requestDraw(true); ctx.canvas3d.requestDraw(true);
...@@ -32,7 +33,7 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) { ...@@ -32,7 +33,7 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) {
ctx.canvas3d.add(e.obj.data); ctx.canvas3d.add(e.obj.data);
ctx.canvas3d.requestDraw(true); ctx.canvas3d.requestDraw(true);
}); });
ctx.events.state.data.object.removed.subscribe(e => { events.object.removed.subscribe(e => {
const oo = e.obj; const oo = e.obj;
if (!SO.isRepresentation3D(oo)) return; if (!SO.isRepresentation3D(oo)) return;
ctx.canvas3d.remove(oo.data); ctx.canvas3d.remove(oo.data);
...@@ -42,7 +43,7 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) { ...@@ -42,7 +43,7 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) {
} }
export function UpdateRepresentationVisibility(ctx: PluginContext) { export function UpdateRepresentationVisibility(ctx: PluginContext) {
ctx.events.state.data.object.cellState.subscribe(e => { ctx.state.dataState.events.object.cellState.subscribe(e => {
const cell = e.state.cells.get(e.ref)!; const cell = e.state.cells.get(e.ref)!;
if (!SO.isRepresentation3D(cell.obj)) return; if (!SO.isRepresentation3D(cell.obj)) return;
......
...@@ -30,8 +30,16 @@ export class PluginContext { ...@@ -30,8 +30,16 @@ export class PluginContext {
readonly events = { readonly events = {
state: { state: {
data: this.state.data.events, object: {
behavior: this.state.behavior.events, cellState: merge(this.state.dataState.events.object.cellState, this.state.behaviorState.events.object.cellState),
cellCreated: merge(this.state.dataState.events.object.cellCreated, this.state.behaviorState.events.object.cellCreated),
created: merge(this.state.dataState.events.object.created, this.state.behaviorState.events.object.created),
removed: merge(this.state.dataState.events.object.removed, this.state.behaviorState.events.object.removed),
updated: merge(this.state.dataState.events.object.updated, this.state.behaviorState.events.object.updated)
},
// data: this.state.dataState.events,
// behavior: this.state.behaviorState.events,
cameraSnapshots: this.state.cameraSnapshots.events, cameraSnapshots: this.state.cameraSnapshots.events,
snapshots: this.state.snapshots.events, snapshots: this.state.snapshots.events,
}, },
...@@ -40,10 +48,10 @@ export class PluginContext { ...@@ -40,10 +48,10 @@ export class PluginContext {
}; };
readonly behaviors = { readonly behaviors = {
state: { // state: {
data: this.state.data.behaviors, // data: this.state.dataState.behaviors,
behavior: this.state.behavior.behaviors // behavior: this.state.behaviorState.behaviors
}, // },
canvas: { canvas: {
highlightLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any }>({ loci: EmptyLoci }), highlightLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any }>({ loci: EmptyLoci }),
selectLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any }>({ loci: EmptyLoci }), selectLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any }>({ loci: EmptyLoci }),
...@@ -98,20 +106,20 @@ export class PluginContext { ...@@ -98,20 +106,20 @@ export class PluginContext {
BuiltInPluginBehaviors.Representation.registerDefault(this); BuiltInPluginBehaviors.Representation.registerDefault(this);
BuiltInPluginBehaviors.Camera.registerDefault(this); BuiltInPluginBehaviors.Camera.registerDefault(this);
merge(this.state.data.events.log, this.state.behavior.events.log).subscribe(e => this.events.log.next(e)); merge(this.state.dataState.events.log, this.state.behaviorState.events.log).subscribe(e => this.events.log.next(e));
} }
async _test_initBehaviors() { async _test_initBehaviors() {
const tree = this.state.behavior.tree.build() const tree = this.state.behaviorState.tree.build()
.toRoot().apply(PluginBehaviors.Representation.HighlightLoci, { ref: PluginBehaviors.Representation.HighlightLoci.id }) .toRoot().apply(PluginBehaviors.Representation.HighlightLoci, { ref: PluginBehaviors.Representation.HighlightLoci.id })
.toRoot().apply(PluginBehaviors.Representation.SelectLoci, { ref: PluginBehaviors.Representation.SelectLoci.id }) .toRoot().apply(PluginBehaviors.Representation.SelectLoci, { ref: PluginBehaviors.Representation.SelectLoci.id })
.getTree(); .getTree();
await this.runTask(this.state.behavior.update(tree)); await this.runTask(this.state.behaviorState.update(tree));
} }
_test_initDataActions() { _test_initDataActions() {
this.state.data.actions this.state.dataState.actions
.add(CreateStructureFromPDBe) .add(CreateStructureFromPDBe)
.add(StateTransforms.Data.Download) .add(StateTransforms.Data.Download)
.add(StateTransforms.Data.ParseCif) .add(StateTransforms.Data.ParseCif)
...@@ -132,18 +140,17 @@ export class PluginContext { ...@@ -132,18 +140,17 @@ export class PluginContext {
} }
private initEvents() { private initEvents() {
merge(this.events.state.data.object.created, this.events.state.behavior.object.created).subscribe(o => { this.events.state.object.created.subscribe(o => {
if (!SO.isBehavior(o.obj)) return; if (!SO.isBehavior(o.obj)) return;
console.log('registering behavior', o.obj.label);
o.obj.data.register(); o.obj.data.register();
}); });
merge(this.events.state.data.object.removed, this.events.state.behavior.object.removed).subscribe(o => { this.events.state.object.removed.subscribe(o => {
if (!SO.isBehavior(o.obj)) return; if (!SO.isBehavior(o.obj)) return;
o.obj.data.unregister(); o.obj.data.unregister();
}); });
merge(this.events.state.data.object.updated, this.events.state.behavior.object.updated).subscribe(o => { this.events.state.object.updated.subscribe(o => {
if (o.action === 'recreate') { if (o.action === 'recreate') {
if (o.oldObj && SO.isBehavior(o.oldObj)) o.oldObj.data.unregister(); if (o.oldObj && SO.isBehavior(o.oldObj)) o.oldObj.data.unregister();
if (o.obj && SO.isBehavior(o.obj)) o.obj.data.register(); if (o.obj && SO.isBehavior(o.obj)) o.obj.data.register();
......
...@@ -10,20 +10,37 @@ import { Camera } from 'mol-canvas3d/camera'; ...@@ -10,20 +10,37 @@ import { Camera } from 'mol-canvas3d/camera';
import { PluginBehavior } from './behavior'; import { PluginBehavior } from './behavior';
import { CameraSnapshotManager } from './state/camera'; import { CameraSnapshotManager } from './state/camera';
import { PluginStateSnapshotManager } from './state/snapshots'; import { PluginStateSnapshotManager } from './state/snapshots';
import { RxEventHelper } from 'mol-util/rx-event-helper';
export { PluginState } export { PluginState }
class PluginState { class PluginState {
readonly data: State; private ev = RxEventHelper.create();
readonly behavior: State;
readonly dataState: State;
readonly behaviorState: State;
readonly cameraSnapshots = new CameraSnapshotManager(); readonly cameraSnapshots = new CameraSnapshotManager();
readonly snapshots = new PluginStateSnapshotManager(); readonly snapshots = new PluginStateSnapshotManager();
readonly behavior = {
kind: this.ev.behavior<PluginState.Kind>('data'),
currentObject: this.ev.behavior<State.ObjectEvent>({} as any)
}
setKind(kind: PluginState.Kind) {
const current = this.behavior.kind.value;
if (kind !== current) {
this.behavior.kind.next(kind);
this.behavior.currentObject.next(kind === 'data'
? this.dataState.behaviors.currentObject.value
: this.behaviorState.behaviors.currentObject.value)
}
}
getSnapshot(): PluginState.Snapshot { getSnapshot(): PluginState.Snapshot {
return { return {
data: this.data.getSnapshot(), data: this.dataState.getSnapshot(),
behaviour: this.behavior.getSnapshot(), behaviours: this.behaviorState.getSnapshot(),
cameraSnapshots: this.cameraSnapshots.getStateSnapshot(), cameraSnapshots: this.cameraSnapshots.getStateSnapshot(),
canvas3d: { canvas3d: {
camera: this.plugin.canvas3d.camera.getSnapshot() camera: this.plugin.canvas3d.camera.getSnapshot()
...@@ -32,29 +49,41 @@ class PluginState { ...@@ -32,29 +49,41 @@ class PluginState {
} }
async setSnapshot(snapshot: PluginState.Snapshot) { async setSnapshot(snapshot: PluginState.Snapshot) {
await this.plugin.runTask(this.behavior.setSnapshot(snapshot.behaviour)); await this.plugin.runTask(this.behaviorState.setSnapshot(snapshot.behaviours));
await this.plugin.runTask(this.data.setSnapshot(snapshot.data)); await this.plugin.runTask(this.dataState.setSnapshot(snapshot.data));
this.cameraSnapshots.setStateSnapshot(snapshot.cameraSnapshots); this.cameraSnapshots.setStateSnapshot(snapshot.cameraSnapshots);
this.plugin.canvas3d.camera.setState(snapshot.canvas3d.camera); this.plugin.canvas3d.camera.setState(snapshot.canvas3d.camera);
this.plugin.canvas3d.requestDraw(true); this.plugin.canvas3d.requestDraw(true);
} }
dispose() { dispose() {
this.data.dispose(); this.ev.dispose();
this.behavior.dispose(); this.dataState.dispose();
this.behaviorState.dispose();
this.cameraSnapshots.dispose(); this.cameraSnapshots.dispose();
} }
constructor(private plugin: import('./context').PluginContext) { constructor(private plugin: import('./context').PluginContext) {
this.data = State.create(new SO.Root({ }), { globalContext: plugin }); this.dataState = State.create(new SO.Root({ }), { globalContext: plugin });
this.behavior = State.create(new PluginBehavior.Root({ }), { globalContext: plugin }); this.behaviorState = State.create(new PluginBehavior.Root({ }), { globalContext: plugin });
this.dataState.behaviors.currentObject.subscribe(o => {
if (this.behavior.kind.value === 'data') this.behavior.currentObject.next(o);
});
this.behaviorState.behaviors.currentObject.subscribe(o => {
if (this.behavior.kind.value === 'behavior') this.behavior.currentObject.next(o);
});
this.behavior.currentObject.next(this.dataState.behaviors.currentObject.value);
} }
} }
namespace PluginState { namespace PluginState {
export type Kind = 'data' | 'behavior'
export interface Snapshot { export interface Snapshot {
data: State.Snapshot, data: State.Snapshot,
behaviour: State.Snapshot, behaviours: State.Snapshot,
cameraSnapshots: CameraSnapshotManager.StateSnapshot, cameraSnapshots: CameraSnapshotManager.StateSnapshot,
canvas3d: { canvas3d: {
camera: Camera.Snapshot camera: Camera.Snapshot
......
...@@ -118,17 +118,23 @@ class ActionContol extends PluginComponent<ActionContol.Props, { params: any, in ...@@ -118,17 +118,23 @@ class ActionContol extends PluginComponent<ActionContol.Props, { params: any, in
state = this.defaultState() state = this.defaultState()
nothingToUpdate() {
return <div>Nothing to update</div>;
}
render() { render() {
console.log('render', this.props.nodeRef, this.action.id);
const cell = this.cell; const cell = this.cell;
if (cell.status !== 'ok' || (this.isUpdate && cell.transform.ref === Transform.RootRef)) return null; if (cell.status !== 'ok' || (this.isUpdate && cell.transform.ref === Transform.RootRef)) return this.nothingToUpdate();
const paramDefs = this.getParamDefinitions();
if (this.isUpdate && Object.keys(paramDefs).length === 0) return this.nothingToUpdate();
const action = this.action; const action = this.action;
return <div> return <div>
<div style={{ borderBottom: '1px solid #999', marginBottom: '5px' }}><h3>{(action.definition.display && action.definition.display.name) || action.id}</h3></div> <div style={{ borderBottom: '1px solid #999', marginBottom: '5px' }}><h3>{(action.definition.display && action.definition.display.name) || action.id}</h3></div>
<ParameterControls params={this.getParamDefinitions()} values={this.state.params} changes={this.changes} onEnter={this.onEnter} isEnabled={!this.state.busy} /> <ParameterControls params={paramDefs} values={this.state.params} changes={this.changes} onEnter={this.onEnter} isEnabled={!this.state.busy} />
<div style={{ textAlign: 'right' }}> <div style={{ textAlign: 'right' }}>
<span style={{ color: 'red' }}>{this.state.error}</span> <span style={{ color: 'red' }}>{this.state.error}</span>
......
...@@ -22,15 +22,15 @@ export class TrajectoryControls extends PluginComponent { ...@@ -22,15 +22,15 @@ export class TrajectoryControls extends PluginComponent {
return <div> return <div>
<b>Trajectory: </b> <b>Trajectory: </b>
<button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, { <button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
state: this.plugin.state.data, state: this.plugin.state.dataState,
action: UpdateTrajectory.create({ action: 'advance', by: -1 }) action: UpdateTrajectory.create({ action: 'advance', by: -1 })
})}>&lt;&lt;</button> })}>&lt;&lt;</button>
<button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, { <button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
state: this.plugin.state.data, state: this.plugin.state.dataState,
action: UpdateTrajectory.create({ action: 'reset' }) action: UpdateTrajectory.create({ action: 'reset' })
})}>Reset</button> })}>Reset</button>
<button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, { <button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
state: this.plugin.state.data, state: this.plugin.state.dataState,
action: UpdateTrajectory.create({ action: 'advance', by: +1 }) action: UpdateTrajectory.create({ action: 'advance', by: +1 })
})}>&gt;&gt;</button><br /> })}>&gt;&gt;</button><br />
</div> </div>
......
...@@ -10,7 +10,6 @@ import { StateTree } from './state-tree'; ...@@ -10,7 +10,6 @@ import { StateTree } from './state-tree';
import { Viewport, ViewportControls } from './viewport'; import { Viewport, ViewportControls } from './viewport';
import { Controls, TrajectoryControls } from './controls'; import { Controls, TrajectoryControls } from './controls';
import { PluginComponent, PluginReactContext } from './base'; import { PluginComponent, PluginReactContext } from './base';
import { merge } from 'rxjs';
import { CameraSnapshots } from './camera'; import { CameraSnapshots } from './camera';
import { StateSnapshots } from './state'; import { StateSnapshots } from './state';
import { List } from 'immutable'; import { List } from 'immutable';
...@@ -18,15 +17,14 @@ import { LogEntry } from 'mol-util/log-entry'; ...@@ -18,15 +17,14 @@ import { LogEntry } from 'mol-util/log-entry';
import { formatTime } from 'mol-util'; import { formatTime } from 'mol-util';
import { BackgroundTaskProgress } from './task'; import { BackgroundTaskProgress } from './task';
import { ActionContol } from './action'; import { ActionContol } from './action';
import { PluginState } from 'mol-plugin/state';
export class Plugin extends React.Component<{ plugin: PluginContext }, {}> { export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
render() { render() {
return <PluginReactContext.Provider value={this.props.plugin}> return <PluginReactContext.Provider value={this.props.plugin}>
<div style={{ position: 'absolute', width: '100%', height: '100%', fontFamily: 'monospace' }}> <div style={{ position: 'absolute', width: '100%', height: '100%', fontFamily: 'monospace' }}>
<div style={{ position: 'absolute', width: '350px', height: '100%', overflowY: 'scroll', padding: '10px' }}> <div style={{ position: 'absolute', width: '350px', height: '100%', overflowY: 'scroll', padding: '10px' }}>
<StateTree state={this.props.plugin.state.data} /> <State />
<h3>Behaviors</h3>
<StateTree state={this.props.plugin.state.behavior} />
</div> </div>
<div style={{ position: 'absolute', left: '350px', right: '300px', top: '0', bottom: '100px' }}> <div style={{ position: 'absolute', left: '350px', right: '300px', top: '0', bottom: '100px' }}>
<Viewport /> <Viewport />
...@@ -55,6 +53,26 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> { ...@@ -55,6 +53,26 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
} }
} }
export class State extends PluginComponent {
componentDidMount() {
this.subscribe(this.plugin.state.behavior.kind, () => this.forceUpdate());
}
set(kind: PluginState.Kind) {
// TODO: do command for this?
this.plugin.state.setKind(kind);
}
render() {
const kind = this.plugin.state.behavior.kind.value;
return <>
<button onClick={() => this.set('data')} style={{ fontWeight: kind === 'data' ? 'bold' : 'normal'}}>Data</button>
<button onClick={() => this.set('behavior')} style={{ fontWeight: kind === 'behavior' ? 'bold' : 'normal'}}>Behavior</button>
<StateTree state={kind === 'data' ? this.plugin.state.dataState : this.plugin.state.behaviorState} />
</>
}
}
export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> { export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> {
private wrapper = React.createRef<HTMLDivElement>(); private wrapper = React.createRef<HTMLDivElement>();
...@@ -85,33 +103,33 @@ export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> { ...@@ -85,33 +103,33 @@ export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> {
} }
export class CurrentObject extends PluginComponent { export class CurrentObject extends PluginComponent {
get current() {
return this.plugin.state.behavior.currentObject.value;
}
componentDidMount() { componentDidMount() {
// let current: State.ObjectEvent | undefined = void 0; this.subscribe(this.plugin.state.behavior.currentObject, o => {
this.subscribe(merge(this.plugin.behaviors.state.data.currentObject, this.plugin.behaviors.state.behavior.currentObject), o => { this.forceUpdate();
// current = o;
this.forceUpdate()
}); });
this.subscribe(this.plugin.events.state.data.object.updated, ({ ref, state }) => { this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => {
console.log('curr event', +new Date); const current = this.current;
const current = this.plugin.behaviors.state.data.currentObject.value;
if (current.ref !== ref || current.state !== state) return; if (current.ref !== ref || current.state !== state) return;
console.log('curr event pass', +new Date);
this.forceUpdate(); this.forceUpdate();
}); });
} }
render() { render() {
console.log('curr', +new Date); const current = this.current;
const current = this.plugin.behaviors.state.data.currentObject.value;
const ref = current.ref; const ref = current.ref;
// const n = this.props.plugin.state.data.tree.nodes.get(ref)!; // const n = this.props.plugin.state.data.tree.nodes.get(ref)!;
const obj = this.plugin.state.data.cells.get(ref)!; const obj = current.state.cells.get(ref)!;
const type = obj && obj.obj ? obj.obj.type : void 0; const type = obj && obj.obj ? obj.obj.type : void 0;
console.log(obj);
const actions = type const actions = type
? current.state.actions.fromType(type) ? current.state.actions.fromType(type)
: [] : []
......
...@@ -9,7 +9,6 @@ import { PluginStateObject } from 'mol-plugin/state/objects'; ...@@ -9,7 +9,6 @@ import { PluginStateObject } from 'mol-plugin/state/objects';
import { State } from 'mol-state' import { State } from 'mol-state'
import { PluginCommands } from 'mol-plugin/command'; import { PluginCommands } from 'mol-plugin/command';
import { PluginComponent } from './base'; import { PluginComponent } from './base';
import { merge } from 'rxjs';
export class StateTree extends PluginComponent<{ state: State }, { }> { export class StateTree extends PluginComponent<{ state: State }, { }> {
componentDidMount() { componentDidMount() {
...@@ -28,7 +27,7 @@ export class StateTree extends PluginComponent<{ state: State }, { }> { ...@@ -28,7 +27,7 @@ export class StateTree extends PluginComponent<{ state: State }, { }> {
export class StateTreeNode extends PluginComponent<{ nodeRef: string, state: State }, { }> { export class StateTreeNode extends PluginComponent<{ nodeRef: string, state: State }, { }> {
componentDidMount() { componentDidMount() {
this.subscribe(merge(this.plugin.events.state.data.object.cellState, this.plugin.events.state.behavior.object.cellState), o => { this.subscribe(this.plugin.events.state.object.cellState, o => {
if (o.ref === this.props.nodeRef && o.state === this.props.state) this.forceUpdate(); if (o.ref === this.props.nodeRef && o.state === this.props.state) this.forceUpdate();
}); });
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment