The settled hover pick, retained so a click can act on it (issue #1917).
Why a cache rather than a fresh pick
Picking is hover-driven: PickingSystem fires ~120 ms after the cursor
stops, reads back the GPU pick buffer asynchronously, and delivers the
result through a callback. There is no synchronous "pick at (x, y)".
Adding one would not help, because of user activation: a browser only
honours window.open from within a short window after a real user gesture,
and an await on a GPU readback risks spending it — turning "open the link"
into "popup blocked". Acting on the pick the tooltip is ALREADY showing is
both synchronous and more honest: the click lands on the element the user
read the name of and decided to click.
Why it is safe
The risk is obvious — a cached pick describes a moment, and the moment can
pass. It is fully covered by two counters PickingSystem already maintains,
so nothing here needs its own invalidation scheme:
signal
advances on
pickGeneration
camera move (controls change), resize, persp↔ortho swap, FOV edit (projection-changed, #1916), layers-panel pick invalidator, any mousemove, mouseleave, dispose, and each new pick
visibleSignature
a layer being hidden or shown — which deliberately does NOT dirty the buffer, so the generation counter alone would miss it
A third check on the cursor position is belt-and-braces: real mouse movement
fires mousemove, which already advances the generation. It is kept so the
staleness rule is self-contained rather than resting on that invariant
holding elsewhere forever, and so a synthetic or coalesced event stream
cannot slip a click through at the wrong place.
Note what is deliberately NOT an invalidator: PickingSystem.suppress().
pointerdown → controls start → suppress(true) is the first half of an
ordinary click, so treating it as invalidating would make every click refuse
itself. suppress accordingly leaves pickGeneration alone, and a test
pins that.
The settled hover pick, retained so a click can act on it (issue #1917).
Why a cache rather than a fresh pick
Picking is hover-driven:
PickingSystemfires ~120 ms after the cursor stops, reads back the GPU pick buffer asynchronously, and delivers the result through a callback. There is no synchronous "pick at (x, y)".Adding one would not help, because of user activation: a browser only honours
window.openfrom within a short window after a real user gesture, and anawaiton a GPU readback risks spending it — turning "open the link" into "popup blocked". Acting on the pick the tooltip is ALREADY showing is both synchronous and more honest: the click lands on the element the user read the name of and decided to click.Why it is safe
The risk is obvious — a cached pick describes a moment, and the moment can pass. It is fully covered by two counters
PickingSystemalready maintains, so nothing here needs its own invalidation scheme:pickGenerationchange), resize, persp↔ortho swap, FOV edit (projection-changed, #1916), layers-panel pick invalidator, any mousemove, mouseleave, dispose, and each new pickvisibleSignatureA third check on the cursor position is belt-and-braces: real mouse movement fires
mousemove, which already advances the generation. It is kept so the staleness rule is self-contained rather than resting on that invariant holding elsewhere forever, and so a synthetic or coalesced event stream cannot slip a click through at the wrong place.Note what is deliberately NOT an invalidator:
PickingSystem.suppress(). pointerdown → controlsstart→suppress(true)is the first half of an ordinary click, so treating it as invalidating would make every click refuse itself.suppressaccordingly leavespickGenerationalone, and a test pins that.