diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 2416bd2a67270dd97e04474b30ae1a95b198d4b6..506105bb399409ddf3fe9c82f0bfeb5ba93b5e1c 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -236,7 +236,7 @@ interface Canvas3D { /** Sets drawPaused = false without starting the built in animation loop */ resume(): void identify(x: number, y: number): PickData | undefined - mark(loci: Representation.Loci, action: MarkerAction, noDraw?: boolean): void + mark(loci: Representation.Loci, action: MarkerAction): void getLoci(pickingId: PickingId | undefined): Representation.Loci notifyDidDraw: boolean, @@ -345,7 +345,30 @@ namespace Canvas3D { return { loci, repr }; } - function mark(reprLoci: Representation.Loci, action: MarkerAction, noDraw = false) { + let markBuffer: [reprLoci: Representation.Loci, action: MarkerAction][] = []; + + function mark(reprLoci: Representation.Loci, action: MarkerAction) { + // NOTE: might try to optimize a case with opposite actions for the + // same loci. Tho this might end up being more expensive (and error prone) + // then just applying everything "naively". + markBuffer.push([reprLoci, action]); + } + + function resolveMarking() { + let changed = false; + for (const [r, l] of markBuffer) { + changed = applyMark(r, l) || changed; + } + markBuffer = []; + if (changed) { + scene.update(void 0, true); + helper.handle.scene.update(void 0, true); + helper.camera.scene.update(void 0, true); + } + return changed; + } + + function applyMark(reprLoci: Representation.Loci, action: MarkerAction) { const { repr, loci } = reprLoci; let changed = false; if (repr) { @@ -355,21 +378,7 @@ namespace Canvas3D { changed = helper.camera.mark(loci, action) || changed; reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; }); } - if (changed) { - if (noDraw) { - // Even with `noDraw` make sure changes will be rendered. - // Note that with this calling mark (with or without `noDraw`) multiple times - // during a JS event loop iteration will only result in a single render call. - forceNextRender = true; - } else { - scene.update(void 0, true); - helper.handle.scene.update(void 0, true); - helper.camera.scene.update(void 0, true); - const prevPickDirty = pickHelper.dirty; - draw({ force: true, allowMulti: true }); - pickHelper.dirty = prevPickDirty; // marking does not change picking buffers - } - } + return changed; } function render(force: boolean, allowMulti: boolean) { @@ -386,6 +395,8 @@ namespace Canvas3D { y > gl.drawingBufferHeight || y + height < 0 ) return false; + const markingUpdated = resolveMarking(); + let didRender = false; controls.update(currentTime); const cameraChanged = camera.update(); @@ -393,9 +404,9 @@ namespace Canvas3D { const shouldRender = force || cameraChanged || resized || forceNextRender; forceNextRender = false; - const multiSampleChanged = multiSampleHelper.update(shouldRender, p.multiSample); + const multiSampleChanged = multiSampleHelper.update(markingUpdated || shouldRender, p.multiSample); - if (shouldRender || multiSampleChanged) { + if (shouldRender || multiSampleChanged || markingUpdated) { let cam: Camera | StereoCamera = camera; if (p.camera.stereo.name === 'on') { stereoCamera.update(); @@ -409,7 +420,8 @@ namespace Canvas3D { } else { passes.draw.render(ctx, p, true); } - pickHelper.dirty = true; + // if only marking has updated, do not set the flag to dirty + pickHelper.dirty = pickHelper.dirty || shouldRender; didRender = true; } diff --git a/src/mol-plugin-state/manager/interactivity.ts b/src/mol-plugin-state/manager/interactivity.ts index c9f972483613367f64c7f127754f35d07ab9201d..638cc4c07376f37dcf6912119a7d9c4d85c237d8 100644 --- a/src/mol-plugin-state/manager/interactivity.ts +++ b/src/mol-plugin-state/manager/interactivity.ts @@ -76,9 +76,11 @@ namespace InteractivityManager { /** * The `noRender` argument indicates that the action should only update the internal - * data structure but not render anything user visible. For example 1) no drawing of - * the canvas3d scene or 2) no ui update of loci labels. This is useful because some - * actions require clearing any markings before they can be applied. + * data structure but not render anything user visible. For example, no ui update of + * loci labels. + * + * This is useful because some actions require clearing any markings before + * they can be applied. */ export type LociMarkProvider = (loci: Representation.Loci, action: MarkerAction, /* test */ noRender?: boolean) => void diff --git a/src/mol-plugin-ui/sequence/sequence.tsx b/src/mol-plugin-ui/sequence/sequence.tsx index 7628f46ea8e7cdc37aa100ef272745e844b5d8be..0ba14c9c61e7a0843cebd25c6192e7eb79a96add 100644 --- a/src/mol-plugin-ui/sequence/sequence.tsx +++ b/src/mol-plugin-ui/sequence/sequence.tsx @@ -6,15 +6,15 @@ */ import * as React from 'react'; -import { PluginUIComponent } from '../base'; -import { MarkerAction } from '../../mol-util/marker-action'; -import { ButtonsType, ModifiersKeys, getButtons, getModifiers, getButton } from '../../mol-util/input/input-observer'; -import { SequenceWrapper } from './wrapper'; -import { StructureElement, StructureProperties, Unit } from '../../mol-model/structure'; import { Subject } from 'rxjs'; -import { debounceTime } from 'rxjs/operators'; +import { throttleTime } from 'rxjs/operators'; import { OrderedSet } from '../../mol-data/int'; +import { StructureElement, StructureProperties, Unit } from '../../mol-model/structure'; import { Representation } from '../../mol-repr/representation'; +import { ButtonsType, getButton, getButtons, getModifiers, ModifiersKeys } from '../../mol-util/input/input-observer'; +import { MarkerAction } from '../../mol-util/marker-action'; +import { PluginUIComponent } from '../base'; +import { SequenceWrapper } from './wrapper'; type SequenceProps = { sequenceWrapper: SequenceWrapper.Any, @@ -55,12 +55,10 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> { this.plugin.managers.interactivity.lociHighlights.addProvider(this.lociHighlightProvider); this.plugin.managers.interactivity.lociSelects.addProvider(this.lociSelectionProvider); - this.subscribe(debounceTime<{ seqIdx: number, buttons: number, button: number, modifiers: ModifiersKeys }>(15)(this.highlightQueue), (e) => { + this.subscribe(this.highlightQueue.pipe(throttleTime(3 * 16.666, void 0, { leading: true, trailing: true })), (e) => { const loci = this.getLoci(e.seqIdx < 0 ? void 0 : e.seqIdx); this.hover(loci, e.buttons, e.button, e.modifiers); }); - - // this.updateMarker() } componentWillUnmount() { diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts index 11fe074c1ff7c5d4452a81536bf0f62776383db0..2c3900ab45c4f34bb2f768898c13e87538996b3d 100644 --- a/src/mol-plugin/behavior/dynamic/representation.ts +++ b/src/mol-plugin/behavior/dynamic/representation.ts @@ -43,9 +43,9 @@ export const HighlightLoci = PluginBehavior.create({ name: 'representation-highlight-loci', category: 'interaction', ctor: class extends PluginBehavior.Handler<HighlightLociProps> { - private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction, noRender?: boolean) => { + private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction) => { if (!this.ctx.canvas3d || !this.params.mark) return; - this.ctx.canvas3d.mark(interactionLoci, action, noRender); + this.ctx.canvas3d.mark(interactionLoci, action); }; private getLoci(loci: Loci) { return this.params.preferAtoms && Bond.isLoci(loci) && loci.bonds.length === 2 @@ -113,9 +113,9 @@ export const SelectLoci = PluginBehavior.create({ category: 'interaction', ctor: class extends PluginBehavior.Handler<SelectLociProps> { private spine: StateTreeSpine.Impl; - private lociMarkProvider = (reprLoci: Representation.Loci, action: MarkerAction, noRender?: boolean) => { + private lociMarkProvider = (reprLoci: Representation.Loci, action: MarkerAction) => { if (!this.ctx.canvas3d || !this.params.mark) return; - this.ctx.canvas3d.mark({ loci: reprLoci.loci }, action, noRender); + this.ctx.canvas3d.mark({ loci: reprLoci.loci }, action); }; private getLoci(loci: Loci) { return this.params.preferAtoms && Bond.isLoci(loci) && loci.bonds.length === 2