diff --git a/src/apps/canvas/component/representation.tsx b/src/apps/canvas/component/representation.tsx index ab7e997086e69cd5131b3785f29ad547c3d48530..261f54e4518d054c22e66e2e2bb5fd211db3d9ed 100644 --- a/src/apps/canvas/component/representation.tsx +++ b/src/apps/canvas/component/representation.tsx @@ -15,7 +15,6 @@ export interface RepresentationComponentProps<P extends PD.Params> { app: App canvas3d: Canvas3D repr: Representation<P> - params: P } export interface RepresentationComponentState { @@ -29,7 +28,7 @@ export class RepresentationComponent<P extends PD.Params> extends React.Componen private stateFromRepr(repr: Representation<P>) { return { label: repr.label, - reprParams: this.props.params, + reprParams: repr.params, reprProps: repr.props } } @@ -42,8 +41,6 @@ export class RepresentationComponent<P extends PD.Params> extends React.Componen await this.props.app.runTask(this.props.repr.createOrUpdate(this.props.app.reprCtx, { [k]: v }).run( progress => this.props.app.log(progress) ), 'Representation Update') - this.props.canvas3d.add(this.props.repr) - this.props.canvas3d.requestDraw(true) this.setState(this.stateFromRepr(this.props.repr)) } diff --git a/src/apps/canvas/component/structure-view.tsx b/src/apps/canvas/component/structure-view.tsx index 4f8178115c073676ee7ffb99057c89a71fcc51ab..6cd0828674ea1d63321f71e07b944c023bfa8614 100644 --- a/src/apps/canvas/component/structure-view.tsx +++ b/src/apps/canvas/component/structure-view.tsx @@ -85,7 +85,7 @@ export class StructureViewComponent extends React.Component<StructureViewCompone } render() { - const { structureView, label, structure, modelIds, assemblyIds, symmetryFeatureIds, active, structureRepresentations } = this.state + const { structureView, label, modelIds, assemblyIds, symmetryFeatureIds, active, structureRepresentations } = this.state const modelIdOptions = modelIds.map(m => { return <option key={m.id} value={m.id}>{m.label}</option> @@ -175,9 +175,6 @@ export class StructureViewComponent extends React.Component<StructureViewCompone return <div key={i}> <RepresentationComponent repr={structureRepresentations[k] as Representation<any>} - params={ - structureView.app.structureRepresentationRegistry.get(k)!.getParams(structureView.app.reprCtx, structure!) - } canvas3d={structureView.canvas3d} app={structureView.app} /> diff --git a/src/apps/canvas/component/volume-view.tsx b/src/apps/canvas/component/volume-view.tsx index 51239bb52b54aeb9b0a1a8b804b4bb1195e5a38c..6a9f309612f8ac006a8aff94e8f7d403e197c9f6 100644 --- a/src/apps/canvas/component/volume-view.tsx +++ b/src/apps/canvas/component/volume-view.tsx @@ -62,7 +62,7 @@ export class VolumeViewComponent extends React.Component<VolumeViewComponentProp // } render() { - const { volumeView, label, volume, active, volumeRepresentations } = this.state + const { volumeView, label, active, volumeRepresentations } = this.state return <div> <div> @@ -90,9 +90,6 @@ export class VolumeViewComponent extends React.Component<VolumeViewComponentProp return <div key={i}> <RepresentationComponent repr={volumeRepresentations[k] as Representation<any>} - params={ - volumeView.app.volumeRepresentationRegistry.get(k)!.getParams(volumeView.app.reprCtx, volume!) - } canvas3d={volumeView.viewer} app={volumeView.app} /> diff --git a/src/apps/canvas/structure-view.ts b/src/apps/canvas/structure-view.ts index 3caefbd7e190fd443cda406742dfb6deb79fa8c8..f82151cda14b7afb1a3b546b067f400d7f17f14b 100644 --- a/src/apps/canvas/structure-view.ts +++ b/src/apps/canvas/structure-view.ts @@ -199,11 +199,11 @@ export async function StructureView(app: App, canvas3d: Canvas3D, models: Readon } else { repr = app.structureRepresentationRegistry.create(k, app.reprCtx, structure) structureRepresentations[k] = repr + canvas3d.add(repr) } await app.runTask(repr.createOrUpdate(app.reprCtx, {}, {}, structure).run( progress => app.log(progress) ), 'Create/update representation') - canvas3d.add(repr) } else { if (structureRepresentations[k]) { canvas3d.remove(structureRepresentations[k]) diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 576e0d68ce2d079ff4c0f0198c78af661d66bd85..7d60e100eb25a604ab1b9ab7370b47f5a5b36738 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subscription } from 'rxjs'; import { Vec3, Mat4, EPSILON } from 'mol-math/linear-algebra' import InputObserver from 'mol-util/input/input-observer' @@ -38,11 +38,11 @@ interface Canvas3D { center: (p: Vec3) => void - hide: (repr: Representation<any>) => void - show: (repr: Representation<any>) => void + hide: (repr: Representation.Any) => void + show: (repr: Representation.Any) => void - add: (repr: Representation<any>) => void - remove: (repr: Representation<any>) => void + add: (repr: Representation.Any) => void + remove: (repr: Representation.Any) => void update: () => void clear: () => void @@ -52,7 +52,7 @@ interface Canvas3D { pick: () => void identify: (x: number, y: number) => Promise<PickingId | undefined> mark: (loci: Loci, action: MarkerAction) => void - getLoci: (pickingId: PickingId) => { loci: Loci, repr?: Representation<any> } + getLoci: (pickingId: PickingId) => { loci: Loci, repr?: Representation.Any } readonly reprCount: BehaviorSubject<number> readonly identified: BehaviorSubject<string> @@ -76,7 +76,8 @@ namespace Canvas3D { export function create(canvas: HTMLCanvasElement, container: Element, props: Partial<Canvas3DProps> = {}): Canvas3D { const p = { ...props, ...DefaultCanvas3DProps } - const reprMap = new Map<Representation<any>, Set<RenderObject>>() + const reprRenderObjects = new Map<Representation.Any, Set<RenderObject>>() + const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>() const reprCount = new BehaviorSubject(0) const identified = new BehaviorSubject('') @@ -123,7 +124,7 @@ namespace Canvas3D { function getLoci(pickingId: PickingId) { let loci: Loci = EmptyLoci let repr: Representation.Any = Representation.Empty - reprMap.forEach((_, _repr) => { + reprRenderObjects.forEach((_, _repr) => { const _loci = _repr.getLoci(pickingId) if (!isEmptyLoci(_loci)) { if (!isEmptyLoci(loci)) console.warn('found another loci') @@ -136,7 +137,7 @@ namespace Canvas3D { function mark(loci: Loci, action: MarkerAction) { let changed = false - reprMap.forEach((roSet, repr) => { + reprRenderObjects.forEach((roSet, repr) => { changed = repr.mark(loci, action) || changed }) if (changed) { @@ -268,6 +269,23 @@ namespace Canvas3D { } } + function add(repr: Representation.Any) { + const oldRO = reprRenderObjects.get(repr) + const newRO = new Set<RenderObject>() + repr.renderObjects.forEach(o => newRO.add(o)) + if (oldRO) { + SetUtils.difference(newRO, oldRO).forEach(o => scene.add(o)) + SetUtils.difference(oldRO, newRO).forEach(o => scene.remove(o)) + scene.update() + } else { + repr.renderObjects.forEach(o => scene.add(o)) + } + reprRenderObjects.set(repr, newRO) + reprCount.next(reprRenderObjects.size) + scene.update() + requestDraw(true) + } + handleResize() return { @@ -278,42 +296,35 @@ namespace Canvas3D { Vec3.set(camera.target, p[0], p[1], p[2]) }, - hide: (repr: Representation<any>) => { - const renderObjectSet = reprMap.get(repr) + hide: (repr: Representation.Any) => { + const renderObjectSet = reprRenderObjects.get(repr) if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = false) }, - show: (repr: Representation<any>) => { - const renderObjectSet = reprMap.get(repr) + show: (repr: Representation.Any) => { + const renderObjectSet = reprRenderObjects.get(repr) if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = true) }, - add: (repr: Representation<any>) => { - const oldRO = reprMap.get(repr) - const newRO = new Set<RenderObject>() - repr.renderObjects.forEach(o => newRO.add(o)) - if (oldRO) { - SetUtils.difference(newRO, oldRO).forEach(o => scene.add(o)) - SetUtils.difference(oldRO, newRO).forEach(o => scene.remove(o)) - scene.update() - } else { - repr.renderObjects.forEach(o => scene.add(o)) - } - reprMap.set(repr, newRO) - reprCount.next(reprMap.size) - scene.update() + add: (repr: Representation.Any) => { + add(repr) + reprUpdatedSubscriptions.set(repr, repr.updated.subscribe(_ => add(repr))) }, - remove: (repr: Representation<any>) => { - const renderObjectSet = reprMap.get(repr) - if (renderObjectSet) { - renderObjectSet.forEach(o => scene.remove(o)) - reprMap.delete(repr) - reprCount.next(reprMap.size) + remove: (repr: Representation.Any) => { + const updatedSubscription = reprUpdatedSubscriptions.get(repr) + if (updatedSubscription) { + updatedSubscription.unsubscribe() + } + const renderObjects = reprRenderObjects.get(repr) + if (renderObjects) { + renderObjects.forEach(o => scene.remove(o)) + reprRenderObjects.delete(repr) + reprCount.next(reprRenderObjects.size) scene.update() } }, update: () => scene.update(), clear: () => { - reprMap.clear() + reprRenderObjects.clear() scene.clear() }, diff --git a/src/mol-repr/representation.ts b/src/mol-repr/representation.ts index 99d40b0720676f462779cfa27fc6d01701801122..948d6a033b88614a89252f1cfa74296ba277dc0d 100644 --- a/src/mol-repr/representation.ts +++ b/src/mol-repr/representation.ts @@ -15,6 +15,7 @@ import { getQualityProps } from './util'; import { ColorTheme } from 'mol-theme/color'; import { SizeTheme } from 'mol-theme/size'; import { ThemeProps, Theme, ThemeRegistryContext } from 'mol-theme/theme'; +import { BehaviorSubject } from 'rxjs'; // export interface RepresentationProps { // visuals?: string[] @@ -67,6 +68,7 @@ export interface RepresentationContext { export { Representation } interface Representation<D, P extends PD.Params = {}> { readonly label: string + readonly updated: BehaviorSubject<number> readonly renderObjects: ReadonlyArray<RenderObject> readonly props: Readonly<PD.DefaultValues<P>> readonly params: Readonly<P> @@ -78,7 +80,7 @@ interface Representation<D, P extends PD.Params = {}> { namespace Representation { export type Any = Representation<any> export const Empty: Representation<any> = { - label: '', renderObjects: [], props: {}, params: {}, + label: '', renderObjects: [], props: {}, params: {}, updated: new BehaviorSubject(0), createOrUpdate: () => Task.constant('', undefined), getLoci: () => EmptyLoci, mark: () => false, @@ -88,6 +90,8 @@ namespace Representation { export type Def<D, P extends PD.Params = {}> = { [k: string]: (getParams: RepresentationParamsGetter<D, P>) => Representation<any, P> } export function createMulti<D, P extends PD.Params = {}>(label: string, getParams: RepresentationParamsGetter<D, P>, reprDefs: Def<D, P>): Representation<D, P> { + const updated = new BehaviorSubject(0) + let currentParams: P let currentProps: PD.DefaultValues<P> let currentData: D @@ -100,12 +104,15 @@ namespace Representation { return { label, + updated, get renderObjects() { - const { visuals } = currentProps const renderObjects: RenderObject[] = [] - for (let i = 0, il = reprList.length; i < il; ++i) { - if (!visuals || visuals.includes(reprMap[i])) { - renderObjects.push(...reprList[i].renderObjects) + if (currentProps) { + const { visuals } = currentProps + for (let i = 0, il = reprList.length; i < il; ++i) { + if (!visuals || visuals.includes(reprMap[i])) { + renderObjects.push(...reprList[i].renderObjects) + } } } return renderObjects @@ -115,9 +122,7 @@ namespace Representation { reprList.forEach(r => Object.assign(props, r.props)) return props as P }, - get params() { - return currentParams - }, + get params() { return currentParams }, createOrUpdate: (ctx: RepresentationContext, props: Partial<P> = {}, themeProps: ThemeProps = {}, data?: D) => { if (data && data !== currentData) { currentParams = getParams(ctx, data) @@ -134,6 +139,7 @@ namespace Representation { await reprList[i].createOrUpdate(ctx, currentProps, themeProps, currentData).runInContext(runtime) } } + updated.next(updated.getValue() + 1) }) }, getLoci: (pickingId: PickingId) => { diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index 7338b20822dd28e062615421c34dd350bcca83e8..b48f16439458b61a9de504fc2f085099be570e3c 100644 --- a/src/mol-repr/shape/representation.ts +++ b/src/mol-repr/shape/representation.ts @@ -19,6 +19,7 @@ import { PickingId } from 'mol-geo/geometry/picking'; import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data'; import { LocationIterator } from 'mol-geo/util/location-iterator'; import { ThemeProps, createTheme } from 'mol-theme/theme'; +import { BehaviorSubject } from 'rxjs'; export interface ShapeRepresentation<P extends ShapeParams> extends Representation<Shape, P> { } @@ -30,6 +31,7 @@ export const ShapeParams = { export type ShapeParams = typeof ShapeParams export function ShapeRepresentation<P extends ShapeParams>(): ShapeRepresentation<P> { + const updated = new BehaviorSubject(0) const renderObjects: RenderObject[] = [] let _renderObject: MeshRenderObject | undefined let _shape: Shape @@ -55,11 +57,13 @@ export function ShapeRepresentation<P extends ShapeParams>(): ShapeRepresentatio _renderObject = createMeshRenderObject(values, state) renderObjects.push(_renderObject) + updated.next(updated.getValue() + 1) }); } return { label: 'Shape mesh', + updated, get renderObjects () { return renderObjects }, get params () { return currentParams }, get props () { return currentProps }, diff --git a/src/mol-repr/structure/complex-representation.ts b/src/mol-repr/structure/complex-representation.ts index 25a9d3596bb71786c0fe2e8bc86ff1a388a33d72..7486dd23f89ba5a64c144a0ac68ccc0279a2f559 100644 --- a/src/mol-repr/structure/complex-representation.ts +++ b/src/mol-repr/structure/complex-representation.ts @@ -15,8 +15,10 @@ import { MarkerAction } from 'mol-geo/geometry/marker-data'; import { RepresentationContext, RepresentationParamsGetter } from 'mol-repr/representation'; import { Theme, ThemeProps, createTheme } from 'mol-theme/theme'; import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { BehaviorSubject } from 'rxjs'; export function ComplexRepresentation<P extends StructureParams>(label: string, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: () => ComplexVisual<P>): StructureRepresentation<P> { + const updated = new BehaviorSubject(0) let visual: ComplexVisual<P> | undefined let _structure: Structure @@ -36,6 +38,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string, return Task.create('Creating or updating ComplexRepresentation', async runtime => { if (!visual) visual = visualCtor() await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, structure) + updated.next(updated.getValue() + 1) }); } @@ -58,6 +61,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string, }, get props() { return _props }, get params() { return _params }, + get updated() { return updated }, createOrUpdate, getLoci, mark, diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index 545c0b05bcd19f0d5918e8bed91ea4f5c01a6ef6..44897ad4feca3d32df5d78966e3438023d7ddf0e 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -17,6 +17,7 @@ import { MarkerAction } from 'mol-geo/geometry/marker-data'; import { Theme, ThemeProps, createTheme } from 'mol-theme/theme'; import { ParamDefinition as PD } from 'mol-util/param-definition'; import { UnitKind, UnitKindOptions } from './visual/util/common'; +import { BehaviorSubject } from 'rxjs'; export const UnitsParams = { ...StructureParams, @@ -27,6 +28,7 @@ export type UnitsParams = typeof UnitsParams export interface UnitsVisual<P extends UnitsParams> extends Visual<StructureGroup, P> { } export function UnitsRepresentation<P extends UnitsParams>(label: string, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> { + const updated = new BehaviorSubject(0) let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>() let _structure: Structure @@ -91,7 +93,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar // visuals.set(group.hashCode, { visual, group }) // }) // unusedVisuals.forEach(visual => visual.destroy()) - } else if (structure && _structure.hashCode === structure.hashCode) { + } else if (structure && structure !== _structure && _structure.hashCode === structure.hashCode) { // console.log('_structure.hashCode === structure.hashCode') // Expects that for structures with the same hashCode, // the unitSymmetryGroups are the same as well. @@ -113,11 +115,12 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = [] // TODO avoid allocation visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ])) for (let i = 0, il = visualsList.length; i < il; ++i) { - const [ visual, group ] = visualsList[i] - await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure: _structure }) + const [ visual ] = visualsList[i] + await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props) } } if (structure) _structure = structure + updated.next(updated.getValue() + 1) }); } @@ -154,6 +157,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar }, get props() { return _props }, get params() { return _params }, + get updated() { return updated }, createOrUpdate, getLoci, mark, diff --git a/src/mol-repr/volume/representation.ts b/src/mol-repr/volume/representation.ts index c4fa484d7c5ed69fbb53f8a84a90d9f33cd76964..0831340cf2e8576834d0240357cae77ffb8044c6 100644 --- a/src/mol-repr/volume/representation.ts +++ b/src/mol-repr/volume/representation.ts @@ -20,6 +20,7 @@ import { NullLocation } from 'mol-model/location'; import { VisualUpdateState } from 'mol-repr/util'; import { ValueCell } from 'mol-util'; import { ThemeProps, Theme, createTheme } from 'mol-theme/theme'; +import { BehaviorSubject } from 'rxjs'; export interface VolumeVisual<P extends VolumeParams> extends Visual<VolumeData, P> { } @@ -147,6 +148,7 @@ export const VolumeParams = { export type VolumeParams = typeof VolumeParams export function VolumeRepresentation<P extends VolumeParams>(label: string, getParams: RepresentationParamsGetter<VolumeData, P>, visualCtor: (volume: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> { + const updated = new BehaviorSubject(0) let visual: VolumeVisual<P> let _volume: VolumeData @@ -180,6 +182,7 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, getP await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, volume) busy = false } + updated.next(updated.getValue() + 1) }); } @@ -202,6 +205,7 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, getP }, get props () { return _props }, get params() { return _params }, + get updated() { return updated }, createOrUpdate, getLoci, mark,