Skip to content

Update how Gesture Handler exposes setGestureState to the Reanimated UI runtime #3207

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

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

j-piasecki
Copy link
Member

@j-piasecki j-piasecki commented Nov 12, 2024

Description

Changes how setGestureState is exposed to the UI runtime. Instead of the weird conditionally adding Reanimated as a dependency on Android and the weird cast on iOS it uses _WORKLET_RUNTIME const injected by Reanimated into the JS runtime. This allows Gesture Handler to decorate the UI runtime without direct dependencies between the libraries.

TODO:

  • look more closely at conditionally adding Reanimated as a dependency on Android as it does more than just handle setGestureState
  • restore FabricExample to the state from before this PR

Caution

This works only on the New Architecture (and breaks the old one)

Test plan

TODO

@j-piasecki j-piasecki requested a review from tomekzaw November 12, 2024 16:24
@j-piasecki j-piasecki force-pushed the @jpiasecki/update-reanimated-integration branch from 4b6cb29 to 039e228 Compare April 17, 2025 13:19

if (REANIMATED_AVAILABLE) {
// When Reanimated is available, setGestureState should be defined
if (global._setGestureStateNew) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use globalThis instead of global, I believe @tjzel can provide some more insights on that.

Suggested change
if (global._setGestureStateNew) {
if (globalThis._setGestureState) {

Also, I'm not sure if we need to include "new" in the name, how about using _setGestureState instead and renaming setGestureState to setGestureStateOld` as suggested in another comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to change the previous method - it's consumed by RNGH, but it's defined entirely on the Reanimated part. Changing it will very likely break the integration between Reanimated and older versions of RNGH.

And _setGestureStateNew is subject to change, though _setGestureState will be hard to beat 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using globalThis will help you with exotic web environments as it should point to the right context of window, global, self, etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the code to use globalThis. I also changed the name - it will be possible to change the state of the gesture from the JS thread as well, so names are:

  • _setGestureStateSync on UI runtime
  • _setGestureStateAsync on JS runtime


function tryInstallUIBindings() {
if (global._WORKLET_RUNTIME) {
console.log('Installing Gesture Handler UI bindings');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to keep those console.logs in .ts file? Maybe let's convert them to comments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, but they are useful for development. One of the todos from the PR description is get rid of the console logs

Comment on lines 16 to 19
setTimeout(() => {
console.log('Waiting for worklet runtime to be available');
tryInstallUIBindings();
}, 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not good, did this actually happen to you? I believe that _WORKLET_RUNTIME is injected synchronously once we import react-native-reanimated

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reanimated's index.ts doesn't have any side-effects and just requiring it might not trigger the TurboModule to initialize. I think it would be better to require executeOnUIRuntimeSync, then invoke it with a HostFunction that will invoke native tryInstallUIBindings, to make sure the pipeline is all set.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to a different approach - instead of decorating the UI runtime immediately, I do it lazily when the first gesture is created. This way, the possible delay should be mitigated entirely.

@tomekzaw tomekzaw changed the title Update how gesture handler exposes setGestureState to the ui runtime Update how Gesture Handler exposes setGestureState to the Reanimated UI runtime Apr 17, 2025
@tomekzaw
Copy link
Member

Additionally, I'd like to rename NativeMethods.h to GestureHandler.h or SetGestureState.h in react-native-reanimated, is that okay?

https://github.com/software-mansion/react-native-reanimated/blob/main/packages/react-native-reanimated/apple/reanimated/apple/native/NativeMethods.h

Copy link
Contributor

@tjzel tjzel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice work overall, I like the removals 😎

friend HybridBase;
jsi::Runtime* rnRuntime = nullptr;

jni::global_ref<RNGestureHandlerModule::javaobject> javaPart_;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You must release javaPart_ when native modules are invalidated, otherwise you'll create a retain cycle and leak memory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean in the destructor of the C++ class or something else?

Comment on lines 16 to 19
setTimeout(() => {
console.log('Waiting for worklet runtime to be available');
tryInstallUIBindings();
}, 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reanimated's index.ts doesn't have any side-effects and just requiring it might not trigger the TurboModule to initialize. I think it would be better to require executeOnUIRuntimeSync, then invoke it with a HostFunction that will invoke native tryInstallUIBindings, to make sure the pipeline is all set.

Comment on lines 149 to 176
jsi::Runtime &rt = *_rnRuntime;

const auto arrayBufferValue =
rt.global().getProperty(rt, "_WORKLET_RUNTIME").getObject(rt).getArrayBuffer(rt).data(rt);
const auto uiRuntimeAddress = reinterpret_cast<uintptr_t *>(&arrayBufferValue[0]);
jsi::Runtime &uiRuntime = *reinterpret_cast<jsi::Runtime *>(*uiRuntimeAddress);

__weak RNGestureHandlerModule *weakSelf = self;

auto setGestureStateNew = jsi::Function::createFromHostFunction(
uiRuntime,
jsi::PropNameID::forAscii(uiRuntime, "_setGestureStateNew"),
2,
[weakSelf](jsi::Runtime &runtime, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {
if (count == 2) {
const auto handlerTag = static_cast<int>(args[0].asNumber());
const auto state = static_cast<int>(args[1].asNumber());

// TODO: expose to JS and dispatch to UI thread if called on JS?
RNGestureHandlerModule *strongSelf = weakSelf;
if (strongSelf != nullptr) {
[strongSelf setGestureState:state forHandler:handlerTag];
}
}
return jsi::Value::undefined();
});

uiRuntime.global().setProperty(uiRuntime, "_setGestureStateNew", std::move(setGestureStateNew));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we move this code to the same place where isViewFlatteningDisabled is defined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs to be delayed since there's no guarantee UI runtime will be initialized and injected into the JS one by this point.

@j-piasecki j-piasecki force-pushed the @jpiasecki/update-reanimated-integration branch from 08271fd to 12c9f11 Compare May 27, 2025 12:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants