Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/basic-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1844,6 +1844,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNWorklets
- Yoga
- RNReanimated (4.4.1):
- hermes-engine
Expand Down Expand Up @@ -2307,7 +2308,7 @@ SPEC CHECKSUMS:
ReactCodegen: 21807c5e7d6d0e334667f755e23063834d581e62
ReactCommon: d5c1bb4427bf51c443de5926aac332c89ddd9363
ReactNativeDependencies: fa0a54b3f5319ae0e3b9aff32bfee7a424b88e66
RNGestureHandler: f07cf8b0a45a4eee18163629f78413b0a534aece
RNGestureHandler: 2d8900683c602d861399d08bac02cb91b683d298
RNReanimated: d6e6865fa9d5ce60863a9e244de45de4f9890e46
RNWorklets: 04a35c45bd72d24914cbaf24bdfa4e30e1eab2df
Yoga: fe50ab299e578f397fef753cf309c6703a4db29b
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions packages/react-native-gesture-handler/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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' }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -120,7 +121,7 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
@ReactMethod
override fun installUIRuntimeBindings(): Boolean {
if (!uiRuntimeDecorated) {
uiRuntimeDecorated = decorateUIRuntime()
uiRuntimeDecorated = decorateUIRuntimeWithWorklets(getWorkletsModule()) || decorateUIRuntime()
}

return uiRuntimeDecorated
Expand Down Expand Up @@ -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<NativeModule>
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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,23 @@ 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}
ReactAndroid::reactnative
ReactAndroid::jsi
fbjni::fbjni
)

if(${RNGH_USE_WORKLETS})
target_link_libraries(${PACKAGE_NAME} react-native-worklets::worklets)
endif()
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

#include "RNGestureHandlerModule.h"

#ifdef RNGH_USE_WORKLETS
#include <worklets/android/WorkletsModule.h>
#endif

namespace gesturehandler {
using namespace facebook;
using namespace facebook::react;
Expand All @@ -21,6 +25,9 @@ void RNGestureHandlerModule::registerNatives() {
RNGestureHandlerModule::getBindingsInstallerCxx),
makeNativeMethod(
"decorateUIRuntime", RNGestureHandlerModule::decorateUIRuntime),
makeNativeMethod(
"decorateUIRuntimeWithWorklets",
RNGestureHandlerModule::decorateUIRuntimeWithWorklets),
makeNativeMethod(
"invalidateNative", RNGestureHandlerModule::invalidateNative)});
}
Expand Down Expand Up @@ -58,6 +65,39 @@ bool RNGestureHandlerModule::decorateUIRuntime() {
});
}

bool RNGestureHandlerModule::decorateUIRuntimeWithWorklets(
jni::alias_ref<jobject> workletsModule) {
#ifdef RNGH_USE_WORKLETS
if (!workletsModule) {
return false;
}

const auto jWorkletsModule =
jni::static_ref_cast<worklets::WorkletsModule::javaobject>(
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class RNGestureHandlerModule : public jni::HybridClass<RNGestureHandlerModule> {
jni::local_ref<BindingsInstallerHolder::javaobject> getBindingsInstallerCxx();

void setGestureState(const int handlerTag, const int state);
bool decorateUIRuntimeWithWorklets(jni::alias_ref<jobject> workletsModule);
bool decorateUIRuntime();
void invalidateNative();
int getModuleId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@

#import "RNGHRuntimeDecorator.h"

#if __has_include(<worklets/apple/WorkletsModule.h>)
#import <worklets/apple/WorkletsModule.h>
#define RNGH_HAS_WORKLETS 1
#else
#define RNGH_HAS_WORKLETS 0
#endif

#import "RNGestureHandler.h"
#import "RNGestureHandlerDirection.h"
#import "RNGestureHandlerState.h"
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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<void(int, int)> &&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<uintptr_t *>(&arrayBufferValue[0]);
jsi::Runtime &uiRuntime =
*reinterpret_cast<jsi::Runtime *>(*uiRuntimeAddress);

auto setGestureStateSync = jsi::Function::createFromHostFunction(
uiRuntime,
jsi::PropNameID::forAscii(uiRuntime, "_setGestureStateSync"),
Expand All @@ -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<void(int, int)> &&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<uintptr_t *>(&arrayBufferValue[0]);
jsi::Runtime &uiRuntime =
*reinterpret_cast<jsi::Runtime *>(*uiRuntimeAddress);

decorateUIRuntime(uiRuntime, std::move(setGestureState));

auto moduleIdValue = jsi::Value(moduleId);
rnRuntime.global().setProperty(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ class RNGHRuntimeDecorator {
jsi::Runtime &rnRuntime,
int moduleId,
std::function<void(int, int)> &&setGestureState);
static void decorateUIRuntime(
jsi::Runtime &uiRuntime,
std::function<void(int, int)> &&setGestureState);
static bool installUIRuntimeBindings(
jsi::Runtime &rnRuntime,
int moduleId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export type ReanimatedHandler<THandlerData> = {
context: ReanimatedContext<THandlerData>;
};

type WorkletsModule = {
scheduleOnUI?: (worklet: () => void) => void;
};

export type NativeEventsManager = new (component: {
props: Record<string, unknown>;
_componentRef: React.Ref<unknown>;
Expand Down Expand Up @@ -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';
});

Expand All @@ -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
Expand Down
Loading