-
-
Notifications
You must be signed in to change notification settings - Fork 414
modify the transform implementation to allow for concurrent animations #3487
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
base: main
Are you sure you want to change the base?
Conversation
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.
Allowing concurrent animations is a nice feature, but without any changes to the platform APIs I don't think this implementation is backward compatible? It should be.
Some general notes:
- Implementations should be added to
.cpp
files (unless it's a class/function template implementation), not in headers. - Do not use single-letter variables.
src/mbgl/map/transform.hpp
Outdated
@@ -16,6 +16,30 @@ | |||
|
|||
namespace mbgl { | |||
|
|||
template <class T> |
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.
Why are these structs with everything public?
Why are you using a shared pointer? Does that data need to be heap allocated?
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.
Why are these structs with everything public?
They don't have to be public, will move them to be private.
Why are you using a shared pointer? Does that data need to be heap allocated?
It needs to be shared across all PropertyAnimations
member variables. We need it so that a latlng
and zoom
that share the same PropertyAnimation
(such as when you update the camera to translate and zoom at the same time) you don't have to keep track of two copies of the same PropertyAnimation
and only have to update one when necessary and the other would be updated as well. For example, if an animation has a finish frame callback and that animation updates both the latlng and the zoom values of the camera we only call the finish frame callback once for either of the latlng or zoom PropertyAnimation
objects and we wouldn't call it again for the other PropertyAnimation
. This is necessary to keep backwards compatibility for animations that span multiple properties.
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.
@louwers I've exposed a flag to enable or disable this feature during navigation for both android and ios. I've also cleaned up large parts of the code and fixed some of the failing tests. Please let me know if you need help understanding the changes or if you think some of the changes should be done differently.
src/mbgl/map/transform.hpp
Outdated
bool animationTransitionFrame(std::shared_ptr<PropertyAnimation>&, double); | ||
void animationFinishFrame(std::shared_ptr<PropertyAnimation>&); | ||
|
||
void visit_pas(const std::function<void(std::shared_ptr<PropertyAnimation>&)>& f) { |
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.
Nit: we tend to use camelCase.
Bloaty Results (iOS) 🐋Compared to main
Full report: https://maplibre-native.s3.eu-central-1.amazonaws.com/bloaty-results-ios/pr-3487-compared-to-main.txt |
This implementation is backwards compatible. What changes to the platform APIs are you expecting? |
The previous implementation of the transform class overrides the current animation with the new one. This new implmentation changes that to allow for concurrent transformations to occur for different types of transfomrations at the same time. For example, you can have two concurrent animations where one modifies the zoom while the other modifies the position of the camera without overriding the other. This change is necessary to allow for concurrent camera animations during navigation.
This exposes a new flag to the maplibre ios interface to allow users to choose to have concurrent camera animations during tracking. This makes it so that all calls to `cancelTransforms` from ios are ignored and independent concurrent camera animations are ran concurrently rather than overriding each other.
this makes it so that users of the android integration can choose to have indepedent camera animations run concurrently instead of them waiting on each other or canceling each other.
06e7a0e
to
5caff41
Compare
src/mbgl/map/transform.hpp
Outdated
const AnimationOptions&, | ||
const std::function<void(double)>&, | ||
const Duration&); | ||
void startTransition(const CameraOptions&, const Duration&, std::shared_ptr<Animation>&); |
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.
Passing a shared pointer by reference does not make sense I think.
Just take a normal reference and dereference the pointer when calling it.
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 0f387f2
src/mbgl/map/transform.hpp
Outdated
scaling(scaling_), | ||
rotating(rotating_) {} | ||
|
||
double interpolant(TimePoint); |
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.
Typo?
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 wasn't sure about the name myself but that's the name of a function that returns the value used for interpolation https://en.wiktionary.org/wiki/interpolant
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 it can be const right?
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.
Yes
* | ||
* @return whether concurrent camera animations are enabled or disabled | ||
*/ | ||
public LocationComponentOptions.Builder concurrentCameraAnimationsEnabled(Boolean concurrentCameraAnimationsEnabled) { |
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.
Enabling concurrent camera animations is not only relevant for the LocationComponent, is it? So it should not be a setting that exists only here.
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.
Currently that's the only use case I can think of. If you want to have multiple animations at the same time from the application side then you could just bundle the animations into a single setCamera
call.
If you still think that concurrent camera animations should be universal then I'd also have to change the logic around cancelTransitions
to not check for the current camera mode.
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 you want to have multiple animations at the same time from the application side then you could just bundle the animations into a single
setCamera
call.
Why is this not possible in the location component?
What if someone wants to implement their own location component?
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 I understand. Would it make more sense to add a boolean to the java Transform
class and expose the concurrent animations flag from the MapView
class instead?
private: | ||
template <class T> | ||
struct Property { | ||
std::shared_ptr<Animation> animation; |
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.
Why is this a shared pointer?
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.
The Animation
object is used to keep track of the properties current animation. It keeps track whether we've ran the animation's frame callback, animation's finish callback, and the panning, scaling, and rotating state.
So, to avoid updating the above for every property we use a shared pointer to ensure that if any one of the properties finished running the animation's frame or finish callbacks then the other one doesn't do it again.
bool animationTransitionFrame(Animation&, double); | ||
void animationFinishFrame(Animation&); | ||
|
||
void visitProperties(const std::function<void(Animation&)>& f) { |
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.
Please don't use single-letter variables.
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'm not sure if I can come up with a more descrptive name for the function, maybe I can call it func
. I followed the same style you already have for visit functions similar to RenderOrchestrator::visitLayerGroups
.
src/mbgl/map/transform.cpp
Outdated
@@ -692,4 +806,15 @@ void Transform::setFreeCameraOptions(const FreeCameraOptions& options) { | |||
state.setFreeCameraOptions(options); | |||
} | |||
|
|||
double Transform::Animation::interpolant(TimePoint now) { | |||
bool isAnimated = duration != Duration::zero(); |
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.
Can use the method you added.
src/mbgl/map/transform.cpp
Outdated
.target = endPoint, | ||
.set = true, | ||
.frameLatLngFunc = | ||
[=, this](TimePoint now) { |
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.
src/mbgl/map/transform.cpp
Outdated
if (!activeAnimation) { | ||
activeAnimation = true; | ||
|
||
bool panning = false, scaling = false, rotating = false; |
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.
Nit: we tend not to use multiple variable declarations.
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-name-one
src/mbgl/map/transform.hpp
Outdated
bool ran = false; // Did this property animation run this frame | ||
bool finished = false; // Did we execute the finish frame for this property animation this frame | ||
bool done = false; // Did this property animation reach the end of the frame | ||
bool panning = false, scaling = false, rotating = false; |
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.
Please define separately https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-name-one
@@ -132,22 +132,87 @@ class Transform : private util::noncopyable { | |||
FreeCameraOptions getFreeCameraOptions() const; | |||
void setFreeCameraOptions(const FreeCameraOptions& options); | |||
|
|||
struct Animation { |
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.
Does this need to be a struct
? Can it be a class?
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's just a container class of related information. I don't think there is any need to make the properties private by default and add accessors. I can make the whole definition of Animation
be private to avoid others using it if that makes the usage clearer?
src/mbgl/map/transform.cpp
Outdated
@@ -103,13 +103,13 @@ void Transform::jumpTo(const CameraOptions& camera) { | |||
* smooth animation between old and new values. The map will retain the current | |||
* values for any options not included in `options`. | |||
*/ | |||
void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions& animation) { | |||
void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions& options) { |
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.
Nit: instead of renaming this to options
, perhaps use animationOptions
? Goes for several places.
src/mbgl/map/transform.hpp
Outdated
struct Animation { | ||
TimePoint start; | ||
Duration duration; | ||
AnimationOptions options; |
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.
An Animation
has a duration
, but an Animation
also has an AnimationsOptions
object with an optional duration
. I think this needs some more refactoring...
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.
So in the flyTo
and easeTo
functions where we initialize the Animation
object there is logic around setting the duration
value that doesn't necessarily equal to the duration
value in AnimationOptions
. Maybe I could change those to override the value in AnimationOptions
but that might have unintended side effects.
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.
Left some more comments. As discussed on Slack, needs test coverage before we can consider merging this.
General comment: please use const
for definitions wherever possible.
Bloaty Results 🐋Compared to main
Full report: https://maplibre-native.s3.eu-central-1.amazonaws.com/bloaty-results/pr-3487-compared-to-main.txtCompared to d387090 (legacy)
Full report: https://maplibre-native.s3.eu-central-1.amazonaws.com/bloaty-results/pr-3487-compared-to-legacy.txt |
Benchmark Results ⚡
Full report: https://maplibre-native.s3.eu-central-1.amazonaws.com/benchmark-results/pr-3487-compared-to-main.txt |
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 just tried the GLFW app, you can build and run it with:
cmake --preset macos
cmake --build build-macos --target mbgl-glfw
build-macos/platform/glfw/mbgl-glfw --style https://tiles.openfreemap.org/styles/liberty
You can jump between various locations with the 'A' key.
It seems to be broken with this PR.
Before:
before.mov
After:
Screen.Recording.2025-07-21.at.18.54.56.mov
I am skeptical of the approach taken by this PR. Whether concurrent camera animations are enabled or not should be set on the C++ level, not defined at the platform level, definitely not tucked away in an internal component only used by the platform itself. Relying on cancelTransitions
being called is brittle and not backward-compatible on all platforms. Also the Transform class having knowledge of the location component is not a good design. In its current form I do not feel comfortable approving this PR.
What I suggest now and for next time: create an issue describing the problem first. Perhaps with a video or two. I still do not 100% understand the problem being addressed here. Especially when modifying fundamental code in the library that has the potential to disrupt our users, it is better to discuss the issue first or even write a design proposal. That way we can together explore the solution space and choose a good approach before code is written.
It took a while for my opinion to crystalize. I apologize that you went though several rounds of reviews and addressing comments. I aim to do better next time and I hope this does not discourage you from contributing.
@louwers I fully understand and I should have started with an issue or some sort of longer form discussion about the problem. I just got a bit too excited and shared this PR hoping we can have this discussion here. I'll post the issue with all the necessary details and the approach I took here and we can go from there. I fully understand your reluctance to merge this and I expected it. I'll make sure to follow the same process going forward for other significant changes. |
The previous implementation of the transform class overrides the current animation with the new one. This new implmentation changes that to allow for concurrent transformations to occur for different types of transfomrations at the same time. For example, you can have two concurrent animations where one modifies the zoom while the other modifies the position of the camera without overriding the other.
This change is necessary to allow for concurrent camera animations during navigation.