From cfc62b748af943eacbaffb77fe44873638fbbb07 Mon Sep 17 00:00:00 2001 From: Kirill Zyusko Date: Thu, 8 Aug 2024 18:10:50 +0200 Subject: [PATCH] feat: synchronous handler mount (#538) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📜 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 ### Docs - mention that only `react-native-reanimated@3.0.0` 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| |-------|-----| |