-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sync continuously-updating interaction handlers with render loop #6005
Conversation
@mourner and others on @mapbox/gl-core, could you give this a quick look to validate the approach, before I propagate it to the other handlers ( |
src/ui/camera.js
Outdated
* | ||
* @private | ||
*/ | ||
_startAnimation(onFrame: (Transform) => void, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marked with _
because, since Camera
gets mixed directly into Map
, I didn't want to make it "public". Eventually, this should be a public method on an internal Camera
class that's separate from Map
. Similarly, a refactor like that would allow for a better naming of _updateCamera
src/ui/camera.js
Outdated
*/ | ||
_updateCamera() { | ||
if (this._onFrame) { | ||
this._onFrame(this.transform); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Later, we could also consider a design where, rather than mutating the Transform
directly, onFrame
just returns CameraOptions
. Consolidating the responsibility for actual Transform
manipulation into one place would slightly simplify the code in easeTo
, flyTo
, handlers, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach seems solid to me (albeit I have little to no experience working with the camera animations / render loop codebase) – makes sense to me that the camera would be the single source of truth for animations and animation-related events. It would be nice if any new state tracking could be contained within the camera and/or eliminated, but that may not be possible right now.
src/ui/handler/drag_pan.js
Outdated
}, { originalEvent: e }); | ||
_onUp(e: MouseEvent | TouchEvent | FocusEvent) { | ||
if (!this.isActive()) return; | ||
this._map.stop(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to delete this._previousPos
here to avoid the map stuttering back to the end of the last dragpan on a subsequent dragpan.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a good approach, with a few exceptions I'm worried about:
startAnimation
term feels confusing in context of updating transform once in a single frame (since there no animation happening, and it's not clear when the stop handler happens too in this context). Maybe we could rename/refactor this a bit with clearer terms? E.g. naming this as_setCameraUpdateHandler
, and finding a different place for the stop logic?- You're introducing a lot of closures that get created on every frame. Eliminating them would ensure the code is as fast as it was, and it would also improve code clarity.
BTW there's something similar going on in Leaflet — it schedules camera updates throw requestAnimFrame rather than syncrhonously updating DOM.
b1be8aa
to
8d60c4c
Compare
@mourner removed closures and attempted to clarify the code a bit more. I still lean towards calling it This is ready for a real review now @mollymerp @mourner |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, pending a green build. Also, would be nice to move down/up/move methods to their former positions so that there's less noise in the diff.
* Since we `_update` on move, and that already triggers a `_rerender()`, we don't also need to set a separate 'move' listener that calls `_rerender()` * Since `_updateEasing()` triggers a `move` event, which in turn schedules a subsequent frame, we don't _also_ need to check `isEasing()` at the end of `render`.
9c46380
to
e4bb33b
Compare
This refactors `Camera#ease()`, which assumed an animation with a fixed end time, in terms of `Camera#_startAnimation()` (and `Camera#stop()`), which doesn't. The advantage of this slight generalization is that `_startAnimation()` can then also be used by continuously-updating interaction handlers (`DragPan`, etc.)
Instead of updating the transform directly within the mousemove handler, we cede control to the render loop by doing our transform updates in the callback we pass to `Camera#_startAnimation`. This way, we synchronously update the transform, render the map, and fire the `move` event (and thus trigger any listeners that might perform DOM updates).
e4bb33b
to
0f4441b
Compare
Closes #6063 Caused by #6005 because, after that change, the drag handlers relied on the `move` event to trigger a map rerender to pick up the next batch of mouse movements. This worked fine while dragging was in progress, but after the user paused and the render frame triggered by the last `move` event was complete, there was nothing to re-initiate the render loop once the mouse moved again.
Closes #6063 Caused by #6005 because, after that change, the drag handlers relied on the `move` event to trigger a map rerender to pick up the next batch of mouse movements. This worked fine while dragging was in progress, but after the user paused and the render frame triggered by the last `move` event was complete, there was nothing to re-initiate the render loop once the mouse moved again.
Closes #6063 Caused by #6005 because, after that change, the drag handlers relied on the `move` event to trigger a map rerender to pick up the next batch of mouse movements. This worked fine while dragging was in progress, but after the user paused and the render frame triggered by the last `move` event was complete, there was nothing to re-initiate the render loop once the mouse moved again.
…box#6073) Closes mapbox#6063 Caused by mapbox#6005 because, after that change, the drag handlers relied on the `move` event to trigger a map rerender to pick up the next batch of mouse movements. This worked fine while dragging was in progress, but after the user paused and the render frame triggered by the last `move` event was complete, there was nothing to re-initiate the render loop once the mouse moved again.
…box#6073) Closes mapbox#6063 Caused by mapbox#6005 because, after that change, the drag handlers relied on the `move` event to trigger a map rerender to pick up the next batch of mouse movements. This worked fine while dragging was in progress, but after the user paused and the render frame triggered by the last `move` event was complete, there was nothing to re-initiate the render loop once the mouse moved again.
@@ -1528,7 +1525,7 @@ class Map extends Camera { | |||
// Even though `_styleDirty` and `_sourcesDirty` are reset in this | |||
// method, synchronous events fired during Style#update or | |||
// Style#_updateSources could have caused them to be set again. | |||
if (this._sourcesDirty || this._repaint || this._styleDirty || this._placementDirty || this.isEasing()) { | |||
if (this._sourcesDirty || this._repaint || this._styleDirty || this._placementDirty) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're planning to fully convert the handlers to use render-loop-driven camera, then we should:
- Have handlers call
map._update
explicitly when they need to trigger a render, as in Ensure drag handlers request render frames on move #6068 - Remove the
move
/zoom
=>this._update
bindings - Restore this
this.isEasing()
trigger
Closes #5390
Quoting from that issue:
This PR aims to solve this issue by having the
DragPan
handler (and the other continuously-updating handlers) cede control to the camera, which in turn is driven by the render loop.I think it's also a step towards further refactoring of camera of
Camera
that eliminates some of the complicated internal state, simplifies/clarifiesmove
-related event firing, etc.Launch Checklist
write tests for all new functionality(blocked by Test interaction handlers, UI controls, and browser utilities #1550)