Skip to content
Open
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
11 changes: 9 additions & 2 deletions RNRive.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,20 @@ Pod::Spec.new do |s|
s.platforms = { :ios => min_ios_version_supported }
s.source = { :git => "https://github.com/rive-app/rive-nitro-react-native.git", :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,swift}"
s.source_files = "ios/**/*.{h,m,mm,swift}", "cpp/**/*.{hpp,cpp}"

s.public_header_files = ['ios/RCTSwiftLog.h']
s.private_header_files = ['cpp/**/*.hpp']

# Set pod_target_xcconfig BEFORE add_nitrogen_files so it gets merged with Nitro's settings
s.pod_target_xcconfig = {
'HEADER_SEARCH_PATHS' => '"$(PODS_TARGET_SRCROOT)/cpp"'
}

load 'nitrogen/generated/ios/RNRive+autolinking.rb'
add_nitrogen_files(s)

s.dependency "RiveRuntime", rive_ios_version

install_modules_dependencies(s)
install_modules_dependencies(s)
end
5 changes: 4 additions & 1 deletion android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_STANDARD 20)

# Define C++ library and add all sources
add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp)
add_library(${PACKAGE_NAME} SHARED
src/main/cpp/cpp-adapter.cpp
src/main/cpp/JRiveWorkletDispatcher.cpp
)

# Add Nitrogen specs :)
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/rive+autolinking.cmake)
Expand Down
81 changes: 81 additions & 0 deletions android/src/main/cpp/JRiveWorkletDispatcher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "JRiveWorkletDispatcher.hpp"
#include <android/log.h>

namespace margelo::nitro::rive {

using namespace facebook;

JRiveWorkletDispatcher::JRiveWorkletDispatcher(
jni::alias_ref<JRiveWorkletDispatcher::jhybridobject> jThis)
: _javaPart(jni::make_global(jThis)) {}

jni::local_ref<JRiveWorkletDispatcher::jhybriddata> JRiveWorkletDispatcher::initHybrid(
jni::alias_ref<jhybridobject> jThis) {
return makeCxxInstance(jThis);
}

jni::local_ref<JRiveWorkletDispatcher::javaobject> JRiveWorkletDispatcher::create() {
return newObjectJavaArgs();
}

void JRiveWorkletDispatcher::trigger() {
std::unique_lock lock(_mutex);
while (!_jobs.empty()) {
auto job = std::move(_jobs.front());
_jobs.pop();
lock.unlock();
job();
lock.lock();
}
}

void JRiveWorkletDispatcher::scheduleTrigger() {
static const auto method = _javaPart->getClass()->getMethod<void()>("scheduleTrigger");
method(_javaPart.get());
}

void JRiveWorkletDispatcher::runAsync(std::function<void()>&& function) {
std::unique_lock lock(_mutex);
_jobs.push(std::move(function));
lock.unlock();
scheduleTrigger();
}

void JRiveWorkletDispatcher::runSync(std::function<void()>&& function) {
std::mutex mtx;
std::condition_variable cv;
bool done = false;

runAsync([&]() {
function();
{
std::lock_guard<std::mutex> lock(mtx);
done = true;
}
cv.notify_one();
});

std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&]{ return done; });
}

void JRiveWorkletDispatcher::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JRiveWorkletDispatcher::initHybrid),
makeNativeMethod("trigger", JRiveWorkletDispatcher::trigger),
});
}

AndroidMainThreadDispatcher::AndroidMainThreadDispatcher(
jni::local_ref<JRiveWorkletDispatcher::javaobject> javaDispatcher)
: _javaDispatcher(jni::make_global(javaDispatcher)) {}

void AndroidMainThreadDispatcher::runAsync(std::function<void()>&& function) {
_javaDispatcher->cthis()->runAsync(std::move(function));
}

void AndroidMainThreadDispatcher::runSync(std::function<void()>&& function) {
_javaDispatcher->cthis()->runSync(std::move(function));
}

} // namespace margelo::nitro::rive
49 changes: 49 additions & 0 deletions android/src/main/cpp/JRiveWorkletDispatcher.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma once

#include <fbjni/fbjni.h>
#include <NitroModules/Dispatcher.hpp>
#include <queue>
#include <mutex>
#include <condition_variable>

namespace margelo::nitro::rive {

using namespace facebook;

class JRiveWorkletDispatcher : public jni::HybridClass<JRiveWorkletDispatcher> {
public:
static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/RiveWorkletDispatcher;";

static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
static void registerNatives();

static jni::local_ref<javaobject> create();

void runAsync(std::function<void()>&& function);
void runSync(std::function<void()>&& function);

private:
friend HybridBase;

void trigger();
void scheduleTrigger();

jni::global_ref<JRiveWorkletDispatcher::javaobject> _javaPart;
std::queue<std::function<void()>> _jobs;
std::recursive_mutex _mutex;

explicit JRiveWorkletDispatcher(jni::alias_ref<JRiveWorkletDispatcher::jhybridobject> jThis);
};

class AndroidMainThreadDispatcher : public Dispatcher {
public:
explicit AndroidMainThreadDispatcher(jni::local_ref<JRiveWorkletDispatcher::javaobject> javaDispatcher);

void runAsync(std::function<void()>&& function) override;
void runSync(std::function<void()>&& function) override;

private:
jni::global_ref<JRiveWorkletDispatcher::javaobject> _javaDispatcher;
};

} // namespace margelo::nitro::rive
5 changes: 4 additions & 1 deletion android/src/main/cpp/cpp-adapter.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include <jni.h>
#include "riveOnLoad.hpp"
#include "JRiveWorkletDispatcher.hpp"

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
return margelo::nitro::rive::initialize(vm);
auto result = margelo::nitro::rive::initialize(vm);
margelo::nitro::rive::JRiveWorkletDispatcher::registerNatives();
return result;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.margelo.nitro.rive

import android.os.Handler
import android.os.Looper
import androidx.annotation.Keep
import com.facebook.jni.HybridData
import com.facebook.proguard.annotations.DoNotStrip
import java.util.concurrent.atomic.AtomicBoolean

@Suppress("JavaJniMissingFunction")
@Keep
@DoNotStrip
class RiveWorkletDispatcher {
@DoNotStrip
@Suppress("unused")
private val mHybridData: HybridData = initHybrid()

private val mainHandler = Handler(Looper.getMainLooper())
private val active = AtomicBoolean(true)

private val triggerRunnable = Runnable {
synchronized(active) {
if (active.get()) {
trigger()
}
}
}

private external fun initHybrid(): HybridData
private external fun trigger()

@DoNotStrip
@Suppress("unused")
private fun scheduleTrigger() {
mainHandler.post(triggerRunnable)
}

fun deactivate() {
synchronized(active) {
active.set(false)
}
}

companion object {
init {
System.loadLibrary("rive")
}
}
}
77 changes: 77 additions & 0 deletions cpp/HybridRiveWorkletBridge.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#pragma once

#include "HybridRiveWorkletBridgeSpec.hpp"
#include <NitroModules/Dispatcher.hpp>

#if __APPLE__
#include <dispatch/dispatch.h>
#include <pthread.h>
#elif __ANDROID__
#include "JRiveWorkletDispatcher.hpp"
#endif

namespace margelo::nitro::rive {

#if __APPLE__

/**
* iOS: A dispatcher that runs work on the main thread using GCD.
*/
class MainThreadDispatcher : public Dispatcher {
public:
void runAsync(std::function<void()>&& function) override {
__block auto func = std::move(function);
dispatch_async(dispatch_get_main_queue(), ^{
func();
});
}

void runSync(std::function<void()>&& function) override {
if (pthread_main_np() != 0) {
function();
} else {
__block auto func = std::move(function);
dispatch_sync(dispatch_get_main_queue(), ^{
func();
});
}
}
};

#endif

class HybridRiveWorkletBridge : public HybridRiveWorkletBridgeSpec {
public:
HybridRiveWorkletBridge() : HybridObject(TAG) {}

void install() override {
throw std::runtime_error("install() requires runtime access - use raw method");
}

protected:
void loadHybridMethods() override {
HybridObject::loadHybridMethods();
registerHybrids(this, [](Prototype& prototype) {
prototype.registerRawHybridMethod("install", 0, &HybridRiveWorkletBridge::installRaw);
});
}

private:
jsi::Value installRaw(jsi::Runtime& runtime,
const jsi::Value& thisValue,
const jsi::Value* args,
size_t count) {
#if __APPLE__
auto dispatcher = std::make_shared<MainThreadDispatcher>();
Dispatcher::installRuntimeGlobalDispatcher(runtime, dispatcher);
#elif __ANDROID__
// Create the Java dispatcher instance and wrap it in the C++ dispatcher
auto javaDispatcher = JRiveWorkletDispatcher::create();
auto dispatcher = std::make_shared<AndroidMainThreadDispatcher>(javaDispatcher);
Dispatcher::installRuntimeGlobalDispatcher(runtime, dispatcher);
#endif
return jsi::Value::undefined();
}
};

} // namespace margelo::nitro::rive
Binary file added example/assets/rive/bouncing_ball.riv
Binary file not shown.
5 changes: 5 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import {
} from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { runOnUI } from 'react-native-reanimated';
import { installWorkletDispatcher } from '@rive-app/react-native';
import { PagesList } from './PagesList';

// Install dispatcher on Reanimated's UI runtime for worklet-based listeners
installWorkletDispatcher(runOnUI);

type RootStackParamList = {
Home: undefined;
} & {
Expand Down
Loading
Loading