-
-
Notifications
You must be signed in to change notification settings - Fork 634
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
Fix Map freeze when Camera.easeTo
is called at any movement event (move, zoom, rotate, pitch) from another Camera.easeTo
call
#3853
base: main
Are you sure you want to change the base?
Conversation
@@ -1010,9 +1010,29 @@ describe('#easeTo', () => { | |||
} | |||
}); | |||
|
|||
camera.easeTo({zoom: 20, bearing: 90, pitch: 60, duration: 500}, {done: true}); | |||
camera.easeTo({zoom: 20, bearing: 90, pitch: 60, duration: 500}); |
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 also edited old tests that I've wrote to improve their functioning
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.
"done" property should have been added in the second animation's event to ensure that it ended well
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3853 +/- ##
==========================================
- Coverage 86.91% 86.64% -0.28%
==========================================
Files 241 241
Lines 32341 32390 +49
Branches 1976 1971 -5
==========================================
- Hits 28110 28063 -47
- Misses 3299 3386 +87
- Partials 932 941 +9 ☔ View full report in Codecov by Sentry. |
I wonder what's the "user story" here. |
Why not simply use |
I'm working on 3D Map with models, buildings etc, I use Maplibre as Map core. I want to easeTo a 40 pitch when I zoom in uppon level 15, and I want to easeTo a 0 pitch, 0 bearing when I zoom out under 15. I listen for 'zoom' event and when it passes from 14 to 15 or 15 to 16, I call my animation. The map freeze occurs in my case, when I zoom in / zoom out from NavigationControl because it calls an easeTo, that fires this 'zoom' event. |
You can try out here : https://codepen.io/ToHold/pen/oNOBgyg |
@HarelM I don't like to use setTimeout, because I don't like time based logics in algorithms. That's why it would be awesome if we could talk about this fix, if you want me to improve or change what I proposed in this PR. |
Do you expect that you pitch animation will happen concurrently with zoom animation? In your PR you are fixing the freeze, but does it allow to run 2 animations at once? I bet it doesn't (though I didn't try). |
I thought about it more and I think you can use |
@sbachinin it shouldn't allow multiple animation at the same time, I know that it's too complicated to handle this. _easeAnimations is just a container that should store only one animation expect when an animation is created during move events. It's just a way to have its own "_canceled" property that avoid an unexpected RenderFrame (taskid) request. I tagged _easeAnimations as @internal and explained in comments that it's impossible to have 2 animations in a same time. Camera.stop() should everytime cancel all easeAnimations in _easeAnimations. There is just in Camera._stop that we can specified an easeId, but it should be use for internal use and with caution. I tried to explain the problem with this diagram, I hope, this will clarify where the problem is. |
@HarelM the problem with await map.once("moveend") is that it will only fire after the end of my scrolls : https://codepen.io/ToHold/pen/vYbmdmJ |
I know that _easeAnimations as an array can be confusing, we can imagine a _previousEaseAnimation, _currentEaseAnimation. What do you think about it ? |
If the problem is with a misalignment, I would prefer to fix that and not try to create something more generic. |
@@ -1159,33 +1194,38 @@ export abstract class Camera extends Evented { | |||
} | |||
} | |||
|
|||
_afterEase(eventData?: any, easeId?: string) { | |||
_afterEase(eventData?: any, easeId?: string | number) { |
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.
easeId
can also be a number here, but only a string in interruptingEaseId
below.
Personally I think a string makes more sense as it's more descriptive.
* Interface that handle all ease animation property, it avoid any stop/add issue during move events | ||
*/ | ||
export interface EaseAnimation { | ||
easeId: string | number; |
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.
See https://github.com/maplibre/maplibre-gl-js/pull/3853/files#r1535566400
It's typically just a string
(number not allowed), except here and in the other location I commented + onEaseEnd
.
* @internal | ||
* Contains ease animations but it's important to understand that it can't have two or more animations at the same time. | ||
* It's just used to handle ease animations stop. | ||
* Every active animations should be stopped before runnin a new one. |
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.
What does this mean? Does this mean it still can't independently ease rotation, zoom and padding for example?
I independently ran into the easeTo
problem this week, but we have a different problem:
We have a folding sidebar and we want to shift the vanishing point (example: https://maplibre.org/maplibre-gl-js/docs/examples/offset-vanishing-point-with-padding/ )
So in our handler where the sidebar gets unfolded, we call easeTo
to shift the vanishing point.
However, the user can interrupt this (accidentally) by triggering a different animation (probably also by map interaction, but definitely if our app tries to set the zoom / move the camera center to focus on something which requires the sidebar (flyTo
/easeTo
), but which might happen sometime during the transition as data is fetched first).
However, that means the vanishing points will be incorrect forever afterwards because the padding transition gets aborted by flyTo
/easeTo
.
We definitely need the option to control the padding (and have it non-interruptable, by user interaction or another easeTo
without padding) and independently control the zoom, rotation, center (which can be interruptable by the user).
It's not a good idea to just chain these animations (as discussed on the main thread), because the padding changes should align with the animation of the sidebar sliding out, but the camera should already start moving (and moving the camera manually during that time should freeze the camera center, but not the padding transition).
Given the complexity, I also wonder if it might be acceptable to remove the animation interface from maplibre entirely.
It could probably live in an external library / plugin which just calls map.setCamera({center, zoom, pitch, bearing})
.
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'd recommend to create a discussion / multiple discussions about the subjects that you suggest here and in Slack. They seem to make sense but there's too much stuff here ).
As for the padding, I've recently researched this stuff and found the whole concept of padding very confusing. My conclusion so far is that we can remove padding altogether and use offset
option for easeTo/flyTo instead.
Reason why:
- First of all, padding creates this permanent offset of vanishing point. This is a maintenance problem for maplibre and also a possible cause of many small bugs. And I can't figure out why this offset was made permanent. Maybe you have an idea why?
- Padding in easeTo/flyTo is actually an offset and nothing else. And we already have an
offset
option so they are basically duplicates, i.e. they solve one problem. (Though offset in itself is a little weird and needs to be fixed).
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.
What does this mean? Does this mean it still can't independently ease rotation, zoom and padding for example?
I independently ran into the
easeTo
problem this week, but we have a different problem:We have a folding sidebar and we want to shift the vanishing point (example: https://maplibre.org/maplibre-gl-js/docs/examples/offset-vanishing-point-with-padding/ )
So in our handler where the sidebar gets unfolded, we call
easeTo
to shift the vanishing point. However, the user can interrupt this (accidentally) by triggering a different animation (probably also by map interaction, but definitely if our app tries to set the zoom / move the camera center to focus on something which requires the sidebar (flyTo
/easeTo
), but which might happen sometime during the transition as data is fetched first). However, that means the vanishing points will be incorrect forever afterwards because the padding transition gets aborted byflyTo
/easeTo
.We definitely need the option to control the padding (and have it non-interruptable, by user interaction or another
easeTo
without padding) and independently control the zoom, rotation, center (which can be interruptable by the user).It's not a good idea to just chain these animations (as discussed on the main thread), because the padding changes should align with the animation of the sidebar sliding out, but the camera should already start moving (and moving the camera manually during that time should freeze the camera center, but not the padding transition).
Given the complexity, I also wonder if it might be acceptable to remove the animation interface from maplibre entirely. It could probably live in an external library / plugin which just calls
map.setCamera({center, zoom, pitch, bearing})
.
It was not intended to independently have ease, rotation, zoom and padding. The goal was to set easeId inside an independent object. Because in movement fired events it can appears that easeId can be overwrite by the end of a stopped ease animation. That's why I decided to store easeId an callback, inside seperated objects. But it's still calling a stop at the begining of an ease.
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.
It can be a first step to achieve this, but it was not intended for.
What's the final word ? Which changes are you expecting ? |
Here's my 2 cents:
Once a user story is defined and we have found no better solution to the problem a short design of the required changes can be made so that we will be able to understand the fix that is going to be introduced. |
Here is the user story : #4010 |
Do we want to continue working on this or should this PR be closed? |
Launch Checklist
CHANGELOG.md
under the## main
section.Hi, I'm still working with MapLibre Camera animations and I found a new case that cause a map freeze. Let me explain : when an easeTo animation is call during another easeTo animation's event, it will offset a wrong taskqueue id. It happens because Camera._stop is called from Camera._renderFrameCallback > Camera._onEaseFrame > Camera._fireMoveEvents that will delete Camera._easeFrameId, but also recreate a new one. It causes an unexpected _requestRenderFrame call that offset all taskqueue.
To avoid this case, I decided to create an EaseAnimation interface, that contains it's own callback and canceled property. Canceled property is now checked before _requestRenderFrame, and avoid this offset. Moreover, the fact that each EaseAnimation got their own callbacks ensures that _onEaseFrame, or _onEaseEnd will never be undefined such as it could happen before this PR : #3332
Reproduce : https://codepen.io/ToHold/pen/ZEZBgrq