-
-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: synchronous handler mount (#538)
## 📜 Description Mount workletized handlers synchronously 😎 ## 💡 Motivation and Context Previously we were storing all worklet handlers in a global object and broadcasted events through all of them. However such approach had one big downside: updating shared value from JS is asynchronous (and if JS thread becomes busy, then mounting can take significant time). In most of the cases it's not a big problem, **but** if keyboard moves when handler is not attached yet, then such keyboard movement is not getting tracked and as a result an actual keyboard position is not synchronized with shared values (if we are using components, such as `KeyboardAvoidingView` then they will not handle keyboard appearance properly). I've considered two approaches how to fix it: ### 1️⃣ Distinguish which events were not sent to particular handler and send them after actual mount That was the first idea and I thought it's quite perspective, but when I implemented it, I quickly realized, that: - it bring more hidden complexity; - it produces more race conditions - we can verify whether event was handled only in JS (only there we know which handlers are mounted and from UI thread we can send all ids of handlers that handled event), but we may have a situation, when handler skipped 10/30 events and handled last 20 events. In this case we shouldn't send these events, but we don't distinguish whether these events belong to the same group of events or not, so if we send them from JS to worklet, we may have a situation, where we handle last 20 events and only after that we handle first 10 events. So at this point of time I realized, that it's not straightforward appraoch and started to look into different solutions. ### 2️⃣ Attach handler through JSI function Another approach that I considered is attaching worklet handlers to the view directly (without intermediate handlers). I discovered, that we call JSI function, which means that we have no delays or async stuff and was very inspired by that fact. I did some experiments and indeed it proved, that handlers can be attached synchronously. However I discovered one use case - we still attach handler from `useEffect` (which is executed asynchronously) and worklet handlers are added via `addUiBlock`, so it's again asynchronous. So even with JSI we could have a delay up to 2 frames, which wasn't acceptable in certain cases. At this point of time I thought that it's unreal to complete my objective, but then decided to try to use `useSyncEffect`. And with `useSyncEffect` it looks like we have only `addUIBlock` asynchronous and it's kind of acceptable (my handlers gets mounted faster, than keyboard events arrive). So I re-worked code, added unit tests for `useSyncEffect`, run e2e and CI and everything seems to be pretty good so fat. I know, that it's not very highly desirable to run synchronous events in react, but I feel like this approach is a last resort and want to try it. I also did performance benchmarks and didn't notice anything - UI gives 59/60 FPS and JS thread give 55 FPS (when navigating between screens). So I think this approach is an acceptable option that worth to try 🚀 ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### Docs - mention that only `[email protected]` is a minimal supported version; ### JS - added infrastructure for hook testing in `src` folder; - removed `useSharedHandlers` hook; - added `useEventHandlerRegistration` hook; - changed signature for `setKeyboardHandler` and `setInputHandlers` method; - assign `ref` to `KeyboardControllerView`; - add `event-mapping` and `event-handler` files; - added `useSyncEffect` hook; - removed `useFocusedInputTextHandler`, `useFocusedInputSelectionHandler` and `uuid` functions; ## 🤔 How Has This Been Tested? Tested manually on iPhone 15 Pro. Also verified that e2e tests are not failing. ## 📸 Screenshots (if appropriate): |Before|After| |-------|-----| |<video src="https://github.com/user-attachments/assets/b839212e-16da-4437-85e6-187b97a3ea55">|<video src="https://github.com/user-attachments/assets/f78c2726-e103-4720-8041-9aafd812b30f">| ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
- Loading branch information
1 parent
6861faf
commit cfc62b7
Showing
17 changed files
with
328 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ docs/build/ | |
lib/ | ||
node_modules/ | ||
vendor/ | ||
.gradle/ | ||
|
||
*.lottie.json | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
let REACore = null; | ||
|
||
try { | ||
REACore = require("react-native-reanimated/src/core"); | ||
} catch (e1) { | ||
try { | ||
REACore = require("react-native-reanimated/src/reanimated2/core"); | ||
} catch (e2) { | ||
console.warn("Failed to load REACore from both paths"); | ||
} | ||
} | ||
const registerEventHandler = REACore.registerEventHandler; | ||
const unregisterEventHandler = REACore.unregisterEventHandler; | ||
|
||
export { registerEventHandler, unregisterEventHandler }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
declare function registerEventHandler( | ||
handler: (event: never) => void, | ||
eventName: string, | ||
viewTag: number, | ||
): number; | ||
declare function unregisterEventHandler(id: number): void; | ||
|
||
export { registerEventHandler, unregisterEventHandler }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import type { FocusedInputHandler, KeyboardHandler } from "./types"; | ||
|
||
export const keyboardEventsMap = new Map<keyof KeyboardHandler, string>([ | ||
["onStart", "onKeyboardMoveStart"], | ||
["onMove", "onKeyboardMove"], | ||
["onEnd", "onKeyboardMoveEnd"], | ||
["onInteractive", "onKeyboardMoveInteractive"], | ||
]); | ||
export const focusedInputEventsMap = new Map<keyof FocusedInputHandler, string>( | ||
[ | ||
["onChangeText", "onFocusedInputTextChanged"], | ||
["onSelectionChange", "onFocusedInputSelectionChanged"], | ||
], | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.