Skip to content
Closed
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
8 changes: 7 additions & 1 deletion RNRive.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ 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}"

if use_legacy
s.exclude_files = ["ios/new/**"]
Expand All @@ -58,6 +58,12 @@ Pod::Spec.new do |s|
end

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

s.pod_target_xcconfig = {
'HEADER_SEARCH_PATHS' => '"$(PODS_TARGET_SRCROOT)/cpp"'
}

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

Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class HybridViewModel(private val viewModel: ViewModel) : HybridViewModelSpec()
}
}

override fun getPropertiesAsync(): Promise<Array<ViewModelPropertyInfo>> {
return Promise.rejected(UnsupportedOperationException("getPropertiesAsync is not supported on the legacy backend"))
}

override fun getPropertyCountAsync(): Promise<Double> {
return Promise.async { propertyCount }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ class HybridViewModelInstance(val viewModelInstance: ViewModelInstance) : Hybrid
override val instanceName: String
get() = viewModelInstance.name

override fun getPropertiesAsync(): Promise<Array<ViewModelPropertyInfo>> {
return Promise.rejected(UnsupportedOperationException("getPropertiesAsync is not supported on the legacy backend"))
}

// Returns null if ViewModelException is thrown for iOS parity
// (iOS SDK returns nil when property not found, Android SDK throws)
private inline fun <T> getPropertyOrNull(block: () -> T): T? {
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")
}
}
}
28 changes: 28 additions & 0 deletions android/src/new/java/com/margelo/nitro/rive/HybridViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import app.rive.RiveFile
import app.rive.ViewModelInstance
import app.rive.ViewModelSource
import app.rive.core.CommandQueue
import app.rive.runtime.kotlin.core.ViewModel
import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.Promise
import kotlinx.coroutines.runBlocking
Expand All @@ -22,6 +23,17 @@ class HybridViewModel(
private const val TAG = "HybridViewModel"
}

override fun getPropertiesAsync(): Promise<Array<ViewModelPropertyInfo>> {
val name = viewModelName ?: return Promise.resolved(emptyArray())
return Promise.async {
riveFile
.getViewModelProperties(name)
.map { prop ->
ViewModelPropertyInfo(name = prop.name, type = mapPropertyType(prop.type))
}.toTypedArray()
}
}

override val propertyCount: Double
get() {
DeprecationWarning.warn("propertyCount", "getPropertyCountAsync")
Expand Down Expand Up @@ -147,3 +159,19 @@ class HybridViewModel(
}
}
}

internal fun mapPropertyType(type: ViewModel.PropertyDataType): ViewModelPropertyType = when (type) {
ViewModel.PropertyDataType.NONE -> ViewModelPropertyType.NONE
ViewModel.PropertyDataType.STRING -> ViewModelPropertyType.STRING
ViewModel.PropertyDataType.NUMBER -> ViewModelPropertyType.NUMBER
ViewModel.PropertyDataType.BOOLEAN -> ViewModelPropertyType.BOOLEAN
ViewModel.PropertyDataType.COLOR -> ViewModelPropertyType.COLOR
ViewModel.PropertyDataType.LIST -> ViewModelPropertyType.LIST
ViewModel.PropertyDataType.ENUM -> ViewModelPropertyType.ENUM
ViewModel.PropertyDataType.TRIGGER -> ViewModelPropertyType.TRIGGER
ViewModel.PropertyDataType.VIEW_MODEL -> ViewModelPropertyType.VIEWMODEL
ViewModel.PropertyDataType.INTEGER -> ViewModelPropertyType.INTEGER
ViewModel.PropertyDataType.SYMBOL_LIST_INDEX -> ViewModelPropertyType.SYMBOLLISTINDEX
ViewModel.PropertyDataType.ASSET_IMAGE -> ViewModelPropertyType.ASSETIMAGE
ViewModel.PropertyDataType.ARTBOARD -> ViewModelPropertyType.ARTBOARD
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ class HybridViewModelInstance(
override val instanceName: String
get() = _instanceName ?: ""

override fun getPropertiesAsync(): Promise<Array<ViewModelPropertyInfo>> {
val name = viewModelName ?: return Promise.resolved(emptyArray())
val file = parentFile.riveFile ?: return Promise.resolved(emptyArray())
return Promise.async {
file
.getViewModelProperties(name)
.map { prop ->
ViewModelPropertyInfo(name = prop.name, type = mapPropertyType(prop.type))
}.toTypedArray()
}
}

override fun numberProperty(path: String): HybridViewModelNumberPropertySpec? {
return try {
runBlocking { viewModelInstance.getNumberFlow(path).first() }
Expand Down
73 changes: 73 additions & 0 deletions cpp/HybridRiveWorkletBridge.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#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__

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__
auto javaDispatcher = JRiveWorkletDispatcher::create();
auto dispatcher = std::make_shared<AndroidMainThreadDispatcher>(javaDispatcher);
Dispatcher::installRuntimeGlobalDispatcher(runtime, dispatcher);
#endif
return jsi::Value::undefined();
}
};

} // namespace margelo::nitro::rive
Loading
Loading