-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Add setImmediate implementation to the UI runtime #3970
Conversation
… things and need to pull in changes from main
This reverts commit 924a1b1.
android/src/main/cpp/NativeProxy.cpp
Outdated
@@ -323,7 +303,7 @@ void NativeProxy::installJSIBindings( | |||
std::shared_ptr<UIManager> uiManager = | |||
binding->getScheduler()->getUIManager(); | |||
module->setUIManager(uiManager); | |||
module->setNewestShadowNodesRegistry(newestShadowNodesRegistry_); | |||
module->setNewestShadowNodesRegistry(newezstShadowNodesRegistry_); |
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.
module->setNewestShadowNodesRegistry(newezstShadowNodesRegistry_); | |
module->setNewestShadowNodesRegistry(newestShadowNodesRegistry_); |
std::vector<FrameCallback> callbacks = frameCallbacks; | ||
frameCallbacks.clear(); |
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.
You should be able to just move-construct callbacks
from frameCallbacks
here, right?
std::vector<FrameCallback> callbacks = frameCallbacks; | |
frameCallbacks.clear(); | |
std::vector<FrameCallback> callbacks = std::move(frameCallbacks); |
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 wasn't my code so prefer not to make this change. Is this behavior defined that the original vector gets reset when moved? Typically move constructors does not specify behavior of the moved object as normally the assumption is that the moved object is never used again. Here we still want to use the vector
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 should be fine.
C++ standard says that the moved-from object is left in a "valid but unspecified state" (so it should be fine to use the old vector later), and the std::vector
spec further specifies that "After the move, other
is guaranteed to be empty()." when move-constructing a vector, so it should clear it as well.
In case you don't feel safe using a vector after moving from it, I would still suggest doing something like
std::vector<FrameCallback> callbacks = frameCallbacks; | |
frameCallbacks.clear(); | |
std::vector<FrameCallback> callbacks; | |
std::swap(callbacks, frameCallbacks) |
since this is unambiguous in what it does, but still avoids copies/allocations.
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 just like to avoid copying the callbacks here, since they can potentially capture a bunch of things - in this case it's a couple of shared_ptr
s, which means unnecessary work on the atomic reference counters inside.
…e mocked when running tests on node 14
src/reanimated2/mappers.ts
Outdated
if (mappers.size !== sortedMappers.length) { | ||
updateMappersOrder(); | ||
} | ||
for (const mapper of sortedMappers) { | ||
if (mapper.dirty) { | ||
mapper.dirty = false; | ||
mapper.worklet(); | ||
mapper.worklet(7); |
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 this 7
is 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.
idk, must've got here by mistake, mapper's worklets don't take arguments
} | ||
_runOnUIQueue.push([worklet, args]); | ||
if (_runOnUIQueue.length === 1) { | ||
setImmediate(() => { |
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 is Reanimated or react-native setImmediate
? If this is setImmediate
from react-native, could we add some comment or use different naming to identify two version easily.
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 is meant to the same setImmediate. We provide a compatibility layer with the exception that reanimated's version doesn't support cancelling callbacks (yet). So we aim for both setImmediate from RN runtime and UI runtimes to be compatible and have same behavior hence we use the standard naming
requestAnimationFrame(mapperFrame); | ||
frameRequested = true; | ||
if (!runRequested) { | ||
setImmediate(mapperRun); |
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 setImmediate
is better than requestAnimationFrame
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.
rAF
will delay the run of the mappers by one frame – the callback will run happen in the following frame. Whereas setImmediate
will execute the callback at the end of the tasks that are currently scheduled on the run loop. The reason we schedule the run in the first place is that there is a chance multiple mappers gets updated in one frame and so we want to run the updates only once as it requires traversing dependency graph.
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.
One issue with this implementation is that it's historically very problematic with Next.js, since setImmediate
is not available on the server.
The current behavior leads to #4140
Would this work?
const scheduleUpdates = typeof window != 'undefined' ? global.setImmediate : requestAnimationFrame;
I posted a question #3925 a month ago and haven't gotten any answer yet. I just saw this PR by chance and I don't know the details and context of this PR at all. However, the quotations sound quite relevant to my previous question.
Does this change solves the problem discussed in the discussion? |
This change impacts the way we execute updates on the main thread. Now they are all batches and will run in a single transaction. So I'd expect this pr to change the way your example work but the easiest way to find out would be to just try it out. |
## Summary Before this change we'd only call `performOperations` for a subset of filtered events (initially introduced in #312). The event's that weren't considered "direct" would require additional animation frame for having their updates flushed. This change makes it so that we call `performOperations` for all types of events therefore matching the behavior [on Android](https://github.com/software-mansion/react-native-reanimated/blob/99b8b3ed56e36ca615cce7164ccaf04d154571b1/android/src/main/java/com/swmansion/reanimated/NodesManager.java#L283) A consequence of this change was that for some event types, the updates reanimated was performing weren't getting flushed onto screen. Namely, we noticed this problem in Pager example where a view pager component with some custom set of events is used and event handlers update shared values. Even though such event would trigger shared value update, and these updates would trigger the style to recalculate and we'd even call updateProps method to apply the updated props, we'd still see no result as in that example the changes require layout run. Without `performOperation` call the layout would not be executed unless react would rerender or other time-based animation would run. The problem became apparent after #3970 where we changed the place where updates are performed from requestAnimationFrame to setImmediate. Before this change, since we were running the updates in "animation frame" the `performOperation` method was being run by the frame scheduler. We, however were getting these updates delayed by one frame because of that. This issue also wasn't noticed prior to shareable rewrite from #3722 because before, we were always starting frame updater for every single update happening to shared value even if it was due to an event. As a result, we were getting the stuff updated on screen but again, with a delay of one frame. ## Test plan Run pager example on iOS.
… flow as other types of events (#4098) ## Summary This diff fixes an issue with animated views not updating when animating along keyboard. This problem regressed after #3970 where we changed the logic of when enqueued callbacks for animation frame and immediates are flushed. The logic has been moved from c++ to js and we added some code to execute animation frame and immediates as a part of event handling process. However, keyboard events have a separate flow of providing event data (we should unify this at some point), and we missed updating it in that place. This PR adds similar logic to [what we use for regular events](https://github.com/software-mansion/react-native-reanimated/blob/535b5d64c0720da3a9a39d99a67e878fe09b5154/src/reanimated2/core.ts#L117) into the code responsible for handling keyboard events. ## Test plan Run animated keyboard example on iOS and Android.
## Summary This PR fixes issue with UI not updating when style changes are connected to events on iOS Fabric. This problem has been introduced in #3970 where we migrated from using `rAF` and started using `setImmediate` instead. As a result we had to manually flush set-immediates queue in certain conditions. Specifically this was already happening after animations frame was run (i.e. in `requestAnimationFrame` callback) but should also be done directly after handling events much like we already do on Android [here](https://github.com/software-mansion/react-native-reanimated/blob/99b8b3ed56e36ca615cce7164ccaf04d154571b1/android/src/main/java/com/swmansion/reanimated/NodesManager.java#L283) and on iOS-paper [here](https://github.com/software-mansion/react-native-reanimated/blob/d9a55c556fc32fcb5db59acc92cbedd6452af9dc/ios/REANodesManager.mm#L334). The reason this stopped working on iOS and not on Android was that Android-fabric implementation still uses old architecture flow for handling events while iOS uses new C++ based `react::EventListener` API (that apparently wasn't working on Android last time we checked). This PR adds flushing operations queue in that new C++ based flow. Note that this problem didn't occur before Shareable Rewrite (#3722) because before all mapper updates would result in us scheduling new animation frames. So the updates were happening eventually, were just delayed by one frame. ## Test plan Run Fabric example on iOS, test Article progress example.
Hi! Can create an issue as well, but I think this PR may have broken running react-native-reanimated on the Web without a Babel plugin. The issue we are getting is that any use of In the PR that adds support for running without a Babel plugin (#3997), the export function runOnUI<A extends any[], R>(
worklet: ComplexWorkletFunction<A, R>
): (...args: A) => void {
- if (__DEV__) {
+ if (__DEV__ && !shouldBeUseWeb()) { From what I can tell, the same needs to be done for export function runOnUIImmediately<A extends any[], R>(
worklet: ComplexWorkletFunction<A, R>
): (...args: A) => void {
- if (__DEV__) {
+ if (__DEV__ && !shouldBeUseWeb()) {
if (worklet.__workletHash === undefined) { Perhaps the error message |
This PR also added a regression on Next.js, which utilizes |
Fixes software-mansion#3970 (comment) This logic matches that of `runOnUI`.
Fixes #3970 (comment) This logic matches that of `runOnUI`. <!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please follow the template so that the reviewers can easily understand what the code changes affect. --> ## Summary <!-- Explain the motivation for this PR. Include "Fixes #<number>" if applicable. --> ## Test plan <!-- Provide a minimal but complete code snippet that can be used to test out this change along with instructions how to run it and a description of the expected behavior. --> --------- Co-authored-by: Krzysztof Piaskowy <krzysztof.piaskowy@swmansion.com>
) ## Summary This PR adds an implementation of web's setImmediate API to the reanimated's UI runtime. We utilize this implementation in mappers and event handlers for more accurate animation/gesture tracking as well as unblock further optimizations. There are several things that this PR changes: 1) We add event timestamp metadata to touch/scroll/sensor/etc events. This change is in preparation of providing a true system timestamps for such events (we don't do that yet but instead call system timer in order to pass down the current time) 2) We update performance.now API to use the same reference point for the provided time as requestAnimationFrame API. Before performance.now would use a different reference point which made it impossible to use it as a starting point for animations. 3) We update event handler callbacks such that they perform a flush for rAF and immediates queues 4) We populate global.__frameTimestamp for all the code that executes within requestAnimationFrame and event callbacks. We then use it in useAnimatedStyle as a reference point for starting animations and in case it wasn't set, we use performance.now 5) We add some code in initializers.ts to define setImmediate method and to register method for flushing immediates queue 6) We introduce set-immediate-based batching for runOnUI calls on the main RN runtime. We then flush all "immediates" after the whole batch is processed on the UI runtime. ## Test plan Check a bunch of examples from the example app: "Use animated style", "Bokeh", "Drag and snap". For touch based interaction verify that movement doesn't lag behind the event. The way this can be done is by screen recording drag and snap example with touch indicators on, then checking on the recorded clip in between frames whether there is a correct movement aligned with the updates of the touch indicator. The above needs to be done for all configurations: Android/iOS/Fabric/Paper
## Summary Before this change we'd only call `performOperations` for a subset of filtered events (initially introduced in software-mansion#312). The event's that weren't considered "direct" would require additional animation frame for having their updates flushed. This change makes it so that we call `performOperations` for all types of events therefore matching the behavior [on Android](https://github.com/software-mansion/react-native-reanimated/blob/99b8b3ed56e36ca615cce7164ccaf04d154571b1/android/src/main/java/com/swmansion/reanimated/NodesManager.java#L283) A consequence of this change was that for some event types, the updates reanimated was performing weren't getting flushed onto screen. Namely, we noticed this problem in Pager example where a view pager component with some custom set of events is used and event handlers update shared values. Even though such event would trigger shared value update, and these updates would trigger the style to recalculate and we'd even call updateProps method to apply the updated props, we'd still see no result as in that example the changes require layout run. Without `performOperation` call the layout would not be executed unless react would rerender or other time-based animation would run. The problem became apparent after software-mansion#3970 where we changed the place where updates are performed from requestAnimationFrame to setImmediate. Before this change, since we were running the updates in "animation frame" the `performOperation` method was being run by the frame scheduler. We, however were getting these updates delayed by one frame because of that. This issue also wasn't noticed prior to shareable rewrite from software-mansion#3722 because before, we were always starting frame updater for every single update happening to shared value even if it was due to an event. As a result, we were getting the stuff updated on screen but again, with a delay of one frame. ## Test plan Run pager example on iOS.
… flow as other types of events (software-mansion#4098) ## Summary This diff fixes an issue with animated views not updating when animating along keyboard. This problem regressed after software-mansion#3970 where we changed the logic of when enqueued callbacks for animation frame and immediates are flushed. The logic has been moved from c++ to js and we added some code to execute animation frame and immediates as a part of event handling process. However, keyboard events have a separate flow of providing event data (we should unify this at some point), and we missed updating it in that place. This PR adds similar logic to [what we use for regular events](https://github.com/software-mansion/react-native-reanimated/blob/535b5d64c0720da3a9a39d99a67e878fe09b5154/src/reanimated2/core.ts#L117) into the code responsible for handling keyboard events. ## Test plan Run animated keyboard example on iOS and Android.
## Summary This PR fixes issue with UI not updating when style changes are connected to events on iOS Fabric. This problem has been introduced in software-mansion#3970 where we migrated from using `rAF` and started using `setImmediate` instead. As a result we had to manually flush set-immediates queue in certain conditions. Specifically this was already happening after animations frame was run (i.e. in `requestAnimationFrame` callback) but should also be done directly after handling events much like we already do on Android [here](https://github.com/software-mansion/react-native-reanimated/blob/99b8b3ed56e36ca615cce7164ccaf04d154571b1/android/src/main/java/com/swmansion/reanimated/NodesManager.java#L283) and on iOS-paper [here](https://github.com/software-mansion/react-native-reanimated/blob/d9a55c556fc32fcb5db59acc92cbedd6452af9dc/ios/REANodesManager.mm#L334). The reason this stopped working on iOS and not on Android was that Android-fabric implementation still uses old architecture flow for handling events while iOS uses new C++ based `react::EventListener` API (that apparently wasn't working on Android last time we checked). This PR adds flushing operations queue in that new C++ based flow. Note that this problem didn't occur before Shareable Rewrite (software-mansion#3722) because before all mapper updates would result in us scheduling new animation frames. So the updates were happening eventually, were just delayed by one frame. ## Test plan Run Fabric example on iOS, test Article progress example.
Fixes software-mansion#3970 (comment) This logic matches that of `runOnUI`. <!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please follow the template so that the reviewers can easily understand what the code changes affect. --> ## Summary <!-- Explain the motivation for this PR. Include "Fixes #<number>" if applicable. --> ## Test plan <!-- Provide a minimal but complete code snippet that can be used to test out this change along with instructions how to run it and a description of the expected behavior. --> --------- Co-authored-by: Krzysztof Piaskowy <krzysztof.piaskowy@swmansion.com>
Summary
This PR adds an implementation of web's setImmediate API to the reanimated's UI runtime. We utilize this implementation in mappers and event handlers for more accurate animation/gesture tracking as well as unblock further optimizations.
There are several things that this PR changes:
Test plan
Check a bunch of examples from the example app: "Use animated style", "Bokeh", "Drag and snap".
For touch based interaction verify that movement doesn't lag behind the event. The way this can be done is by screen recording drag and snap example with touch indicators on, then checking on the recorded clip in between frames whether there is a correct movement aligned with the updates of the touch indicator.
The above needs to be done for all configurations: Android/iOS/Fabric/Paper