diff --git a/apps/basic-example/ios/Podfile.lock b/apps/basic-example/ios/Podfile.lock index 24285befb2..d4b9994336 100644 --- a/apps/basic-example/ios/Podfile.lock +++ b/apps/basic-example/ios/Podfile.lock @@ -1844,6 +1844,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies + - RNWorklets - Yoga - RNReanimated (4.4.1): - hermes-engine @@ -2307,7 +2308,7 @@ SPEC CHECKSUMS: ReactCodegen: 21807c5e7d6d0e334667f755e23063834d581e62 ReactCommon: d5c1bb4427bf51c443de5926aac332c89ddd9363 ReactNativeDependencies: fa0a54b3f5319ae0e3b9aff32bfee7a424b88e66 - RNGestureHandler: f07cf8b0a45a4eee18163629f78413b0a534aece + RNGestureHandler: 2d8900683c602d861399d08bac02cb91b683d298 RNReanimated: d6e6865fa9d5ce60863a9e244de45de4f9890e46 RNWorklets: 04a35c45bd72d24914cbaf24bdfa4e30e1eab2df Yoga: fe50ab299e578f397fef753cf309c6703a4db29b diff --git a/packages/react-native-gesture-handler/RNGestureHandler.podspec b/packages/react-native-gesture-handler/RNGestureHandler.podspec index 57b87c0be5..98d17dd3f9 100644 --- a/packages/react-native-gesture-handler/RNGestureHandler.podspec +++ b/packages/react-native-gesture-handler/RNGestureHandler.podspec @@ -27,6 +27,10 @@ Pod::Spec.new do |s| install_modules_dependencies(s); + if GestureHandlerUtils.react_native_worklets_podspec_exists() + s.dependency "RNWorklets" + end + if ENV['USE_FRAMEWORKS'] != nil add_dependency(s, "React-FabricComponents", :additional_framework_paths => [ "react/renderer/textlayoutmanager/platform/ios", diff --git a/packages/react-native-gesture-handler/android/build.gradle b/packages/react-native-gesture-handler/android/build.gradle index 3f5f514cc1..04507c7e90 100644 --- a/packages/react-native-gesture-handler/android/build.gradle +++ b/packages/react-native-gesture-handler/android/build.gradle @@ -107,6 +107,10 @@ def shouldUseCommonInterfaceFromRNSVG() { major > 15 } +def shouldUseRuntimeFromWorklets() { + return rootProject.subprojects.find { it.name == 'react-native-worklets' } != null +} + def reactNativeArchitectures() { def value = project.getProperties().get("reactNativeArchitectures") return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] @@ -152,6 +156,7 @@ android { cppFlags "-O2", "-frtti", "-fexceptions", "-Wall", "-Werror", "-std=c++20", "-DANDROID" arguments "-DREACT_NATIVE_DIR=${REACT_NATIVE_DIR}", "-DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}", + "-DRNGH_USE_WORKLETS=${shouldUseRuntimeFromWorklets()}", "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" abiFilters(*reactNativeArchitectures()) @@ -226,6 +231,13 @@ dependencies { } } + if (shouldUseRuntimeFromWorklets()) { + implementation(rootProject.subprojects.find { it.name == 'react-native-worklets' }) { + // resolves "Duplicate class com.facebook.jni.CppException" + exclude group: 'com.facebook.fbjni' + } + } + if (shouldUseCommonInterfaceFromRNSVG()) { implementation rootProject.subprojects.find { it.name == 'react-native-svg' } } diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt index 046f404eba..69fafbcd1e 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt @@ -3,6 +3,7 @@ package com.swmansion.gesturehandler.react import com.facebook.jni.HybridData import com.facebook.proguard.annotations.DoNotStrip import com.facebook.react.bridge.JSApplicationIllegalArgumentException +import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReadableMap @@ -120,7 +121,7 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : @ReactMethod override fun installUIRuntimeBindings(): Boolean { if (!uiRuntimeDecorated) { - uiRuntimeDecorated = decorateUIRuntime() + uiRuntimeDecorated = decorateUIRuntimeWithWorklets(getWorkletsModule()) || decorateUIRuntime() } return uiRuntimeDecorated @@ -160,8 +161,19 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : } } + private fun getWorkletsModule(): NativeModule? = try { + @Suppress("UNCHECKED_CAST") + val workletsModuleClass = Class.forName("com.swmansion.worklets.WorkletsModule") as Class + reactApplicationContext.getNativeModule(workletsModuleClass) + } catch (_: ClassNotFoundException) { + null + } catch (_: ClassCastException) { + null + } + private external fun initHybrid(): HybridData private external fun getBindingsInstallerCxx(): BindingsInstallerHolder + private external fun decorateUIRuntimeWithWorklets(workletsModule: Any?): Boolean private external fun decorateUIRuntime(): Boolean private external fun invalidateNative(): Unit diff --git a/packages/react-native-gesture-handler/android/src/main/jni/CMakeLists.txt b/packages/react-native-gesture-handler/android/src/main/jni/CMakeLists.txt index 8c0dc69242..74f73f8c99 100644 --- a/packages/react-native-gesture-handler/android/src/main/jni/CMakeLists.txt +++ b/packages/react-native-gesture-handler/android/src/main/jni/CMakeLists.txt @@ -44,6 +44,15 @@ endif() find_package(ReactAndroid REQUIRED CONFIG) find_package(fbjni REQUIRED CONFIG) +if(${RNGH_USE_WORKLETS}) + find_package(react-native-worklets REQUIRED CONFIG) + target_compile_definitions(${PACKAGE_NAME} PRIVATE RNGH_USE_WORKLETS=1) + target_include_directories( + ${PACKAGE_NAME} + PRIVATE + "${REACT_NATIVE_DIR}/ReactCommon/jsiexecutor" + ) +endif() target_link_libraries( ${PACKAGE_NAME} @@ -51,3 +60,7 @@ target_link_libraries( ReactAndroid::jsi fbjni::fbjni ) + +if(${RNGH_USE_WORKLETS}) + target_link_libraries(${PACKAGE_NAME} react-native-worklets::worklets) +endif() diff --git a/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.cpp b/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.cpp index 476fe8dc3b..c7e1b42ed1 100644 --- a/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.cpp +++ b/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.cpp @@ -3,6 +3,10 @@ #include "RNGestureHandlerModule.h" +#ifdef RNGH_USE_WORKLETS +#include +#endif + namespace gesturehandler { using namespace facebook; using namespace facebook::react; @@ -21,6 +25,9 @@ void RNGestureHandlerModule::registerNatives() { RNGestureHandlerModule::getBindingsInstallerCxx), makeNativeMethod( "decorateUIRuntime", RNGestureHandlerModule::decorateUIRuntime), + makeNativeMethod( + "decorateUIRuntimeWithWorklets", + RNGestureHandlerModule::decorateUIRuntimeWithWorklets), makeNativeMethod( "invalidateNative", RNGestureHandlerModule::invalidateNative)}); } @@ -58,6 +65,39 @@ bool RNGestureHandlerModule::decorateUIRuntime() { }); } +bool RNGestureHandlerModule::decorateUIRuntimeWithWorklets( + jni::alias_ref workletsModule) { +#ifdef RNGH_USE_WORKLETS + if (!workletsModule) { + return false; + } + + const auto jWorkletsModule = + jni::static_ref_cast( + workletsModule); + const auto workletsModuleProxy = + jWorkletsModule->cthis()->getWorkletsModuleProxy(); + if (!workletsModuleProxy) { + return false; + } + + const auto uiWorkletRuntime = workletsModuleProxy->getUIWorkletRuntime(); + if (!uiWorkletRuntime) { + return false; + } + + RNGHRuntimeDecorator::decorateUIRuntime( + uiWorkletRuntime->getJSIRuntime(), [&](int handlerTag, int state) { + this->setGestureState(handlerTag, state); + }); + + return true; +#else + (void)workletsModule; + return false; +#endif +} + void RNGestureHandlerModule::invalidateNative() { // This is called when the module is being destroyed, so we need to clear // the reference to the java part to avoid memory leaks. diff --git a/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.h b/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.h index d8e96afa2e..1c93c791ed 100644 --- a/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.h +++ b/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.h @@ -27,6 +27,7 @@ class RNGestureHandlerModule : public jni::HybridClass { jni::local_ref getBindingsInstallerCxx(); void setGestureState(const int handlerTag, const int state); + bool decorateUIRuntimeWithWorklets(jni::alias_ref workletsModule); bool decorateUIRuntime(); void invalidateNative(); int getModuleId(); diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm index d848f228f0..0037a7ff37 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm @@ -16,6 +16,13 @@ #import "RNGHRuntimeDecorator.h" +#if __has_include() +#import +#define RNGH_HAS_WORKLETS 1 +#else +#define RNGH_HAS_WORKLETS 0 +#endif + #import "RNGestureHandler.h" #import "RNGestureHandlerDirection.h" #import "RNGestureHandlerState.h" @@ -108,6 +115,26 @@ - (bool)decorateUIRuntime { __weak RNGestureHandlerModule *weakSelf = self; +#if RNGH_HAS_WORKLETS + WorkletsModule *workletsModule = (WorkletsModule *)[self.moduleRegistry moduleForName:"WorkletsModule"]; + if (workletsModule != nil) { + auto workletsModuleProxy = [workletsModule getWorkletsModuleProxy]; + if (workletsModuleProxy != nullptr) { + auto uiWorkletRuntime = workletsModuleProxy->getUIWorkletRuntime(); + if (uiWorkletRuntime != nullptr) { + RNGHRuntimeDecorator::decorateUIRuntime( + uiWorkletRuntime->getJSIRuntime(), [weakSelf](int handlerTag, int state) { + RNGestureHandlerModule *strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf setGestureState:state forHandler:handlerTag]; + } + }); + return true; + } + } + } +#endif + return RNGHRuntimeDecorator::installUIRuntimeBindings(*_rnRuntime, _moduleId, [weakSelf](int handlerTag, int state) { RNGestureHandlerModule *strongSelf = weakSelf; if (strongSelf != nil) { diff --git a/packages/react-native-gesture-handler/scripts/gesture_handler_utils.rb b/packages/react-native-gesture-handler/scripts/gesture_handler_utils.rb index cfecb9f4dd..cba117a98b 100644 --- a/packages/react-native-gesture-handler/scripts/gesture_handler_utils.rb +++ b/packages/react-native-gesture-handler/scripts/gesture_handler_utils.rb @@ -28,4 +28,28 @@ def get_react_native_minor_version() return react_native_json['version'].split('.')[1].to_i end + + def node_package_dir(package_name) + package_json_path = `cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('#{package_name}/package.json')" 2>/dev/null`.strip + + if !$?.success? || package_json_path.empty? + return nil + end + + return File.dirname(package_json_path) + end + + def react_native_worklets_package_dir() + return node_package_dir('react-native-worklets') + end + + def react_native_worklets_podspec_exists() + package_dir = react_native_worklets_package_dir() + + if package_dir == nil + return false + end + + return File.exist?(File.join(package_dir, 'RNWorklets.podspec')) + end end diff --git a/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.cpp b/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.cpp index 4f08af9b23..4b30dec7bf 100644 --- a/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.cpp +++ b/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.cpp @@ -89,25 +89,9 @@ void RNGHRuntimeDecorator::installRNRuntimeBindings( rnRuntime, "_RNGH_MODULE_ID", std::move(moduleIdValue)); } -bool RNGHRuntimeDecorator::installUIRuntimeBindings( - jsi::Runtime &rnRuntime, - int moduleId, +void RNGHRuntimeDecorator::decorateUIRuntime( + jsi::Runtime &uiRuntime, std::function &&setGestureState) { - const auto runtimeHolder = - rnRuntime.global().getProperty(rnRuntime, "_WORKLET_RUNTIME"); - - if (runtimeHolder.isUndefined()) { - return false; - } - - const auto arrayBufferValue = - runtimeHolder.getObject(rnRuntime).getArrayBuffer(rnRuntime).data( - rnRuntime); - const auto uiRuntimeAddress = - reinterpret_cast(&arrayBufferValue[0]); - jsi::Runtime &uiRuntime = - *reinterpret_cast(*uiRuntimeAddress); - auto setGestureStateSync = jsi::Function::createFromHostFunction( uiRuntime, jsi::PropNameID::forAscii(uiRuntime, "_setGestureStateSync"), @@ -128,6 +112,28 @@ bool RNGHRuntimeDecorator::installUIRuntimeBindings( uiRuntime.global().setProperty( uiRuntime, "_setGestureStateSync", std::move(setGestureStateSync)); +} + +bool RNGHRuntimeDecorator::installUIRuntimeBindings( + jsi::Runtime &rnRuntime, + int moduleId, + std::function &&setGestureState) { + const auto runtimeHolder = + rnRuntime.global().getProperty(rnRuntime, "_WORKLET_RUNTIME"); + + if (runtimeHolder.isUndefined()) { + return false; + } + + const auto arrayBufferValue = + runtimeHolder.getObject(rnRuntime).getArrayBuffer(rnRuntime).data( + rnRuntime); + const auto uiRuntimeAddress = + reinterpret_cast(&arrayBufferValue[0]); + jsi::Runtime &uiRuntime = + *reinterpret_cast(*uiRuntimeAddress); + + decorateUIRuntime(uiRuntime, std::move(setGestureState)); auto moduleIdValue = jsi::Value(moduleId); rnRuntime.global().setProperty( diff --git a/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.h b/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.h index 146c403858..04548dbccb 100644 --- a/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.h +++ b/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.h @@ -10,6 +10,9 @@ class RNGHRuntimeDecorator { jsi::Runtime &rnRuntime, int moduleId, std::function &&setGestureState); + static void decorateUIRuntime( + jsi::Runtime &uiRuntime, + std::function &&setGestureState); static bool installUIRuntimeBindings( jsi::Runtime &rnRuntime, int moduleId, diff --git a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts index a107e9ea14..c93205e5e3 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts @@ -32,6 +32,10 @@ export type ReanimatedHandler = { context: ReanimatedContext; }; +type WorkletsModule = { + scheduleOnUI?: (worklet: () => void) => void; +}; + export type NativeEventsManager = new (component: { props: Record; _componentRef: React.Ref; @@ -82,12 +86,11 @@ let Reanimated: | undefined; try { - Reanimated = require('react-native-reanimated'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires - const Worklets = require('react-native-worklets'); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const Worklets = require('react-native-worklets') as WorkletsModule; // Make sure worklets are initialized before attempting to install UI runtime bindings - Worklets?.scheduleOnUI(() => { + Worklets.scheduleOnUI?.(() => { 'worklet'; }); @@ -102,6 +105,12 @@ try { ); } }); +} catch (e) { + // When 'react-native-worklets' is not available we want to quietly continue +} + +try { + Reanimated = require('react-native-reanimated'); } catch (e) { // When 'react-native-reanimated' is not available we want to quietly continue // @ts-ignore TS demands the variable to be initialized