Skip to content

Commit

Permalink
feat: Create Dispatcher - a Platform specific interface for awaitab…
Browse files Browse the repository at this point in the history
…le UI or Background Threading dispatches (#45)
  • Loading branch information
mrousavy authored Mar 12, 2024
1 parent 24c4225 commit dd577b2
Show file tree
Hide file tree
Showing 20 changed files with 300 additions and 14 deletions.
1 change: 1 addition & 0 deletions package/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ add_library(
src/main/cpp/java-bindings/JFilamentProxy.cpp
src/main/cpp/java-bindings/JChoreographer.cpp
src/main/cpp/java-bindings/JFilamentView.cpp
src/main/cpp/java-bindings/JDispatcher.cpp
src/main/cpp/java-bindings/JSurfaceProvider.cpp
)

Expand Down
12 changes: 10 additions & 2 deletions package/android/src/main/cpp/AndroidFilamentProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ AndroidFilamentProxy::~AndroidFilamentProxy() {
}

std::shared_ptr<FilamentBuffer> AndroidFilamentProxy::loadAsset(std::string path) {
return jni::WithJNIScope<std::shared_ptr<FilamentBuffer>>([=] { return _proxy->cthis()->loadAsset(path); });
return _proxy->cthis()->loadAsset(path);
}

std::shared_ptr<FilamentView> AndroidFilamentProxy::findFilamentView(int id) {
return jni::WithJNIScope<std::shared_ptr<FilamentView>>([=] { return _proxy->cthis()->findFilamentView(id); });
return _proxy->cthis()->findFilamentView(id);
}

std::shared_ptr<Choreographer> AndroidFilamentProxy::createChoreographer() {
Expand All @@ -34,6 +34,14 @@ std::shared_ptr<react::CallInvoker> AndroidFilamentProxy::getCallInvoker() {
return _proxy->cthis()->getCallInvoker();
}

std::shared_ptr<Dispatcher> AndroidFilamentProxy::getUIDispatcher() {
return _proxy->cthis()->getUIDispatcher();
}

std::shared_ptr<Dispatcher> AndroidFilamentProxy::getBackgroundDispatcher() {
return _proxy->cthis()->getBackgroundDispatcher();
}

jsi::Runtime& AndroidFilamentProxy::getRuntime() {
return _proxy->cthis()->getRuntime();
}
Expand Down
2 changes: 2 additions & 0 deletions package/android/src/main/cpp/AndroidFilamentProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class AndroidFilamentProxy : public FilamentProxy {
std::shared_ptr<FilamentBuffer> loadAsset(std::string path) override;
std::shared_ptr<FilamentView> findFilamentView(int id) override;
std::shared_ptr<Choreographer> createChoreographer() override;
std::shared_ptr<Dispatcher> getUIDispatcher() override;
std::shared_ptr<Dispatcher> getBackgroundDispatcher() override;

public:
jsi::Runtime& getRuntime() override;
Expand Down
2 changes: 2 additions & 0 deletions package/android/src/main/cpp/Filament.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "FilamentInstaller.h"
#include "JChoreographer.h"
#include "JDispatcher.h"
#include "JFilamentProxy.h"
#include "JFilamentView.h"
#include "JSurfaceProvider.h"
Expand All @@ -13,5 +14,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
margelo::JSurfaceProvider::registerNatives();
margelo::JFilamentView::registerNatives();
margelo::JChoreographer::registerNatives();
margelo::JDispatcher::registerNatives();
});
}
30 changes: 30 additions & 0 deletions package/android/src/main/cpp/java-bindings/JDispatcher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// Created by Marc Rousavy on 12.03.24.
//

#include "JDispatcher.h"

namespace margelo {

JDispatcher::~JDispatcher() {}

JDispatcher::JDispatcher(const jni::alias_ref<jhybridobject>& javaThis) : _javaPart(jni::make_global(javaThis)) {}

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

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

void JDispatcher::triggerParent() {
trigger();
}

void JDispatcher::scheduleTrigger() {
static const auto method = javaClassLocal()->getMethod<void()>("scheduleTrigger");
method(_javaPart);
}

} // namespace margelo
38 changes: 38 additions & 0 deletions package/android/src/main/cpp/java-bindings/JDispatcher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Created by Marc Rousavy on 12.03.24.
//

#pragma once

#include "Dispatcher.h"
#include <fbjni/fbjni.h>

namespace margelo {

using namespace facebook;

class JDispatcher : public jni::HybridClass<JDispatcher>, public Dispatcher {
public:
~JDispatcher();
static void registerNatives();

public:
void scheduleTrigger() override;

private:
void triggerParent();

private:
friend HybridBase;
jni::global_ref<JDispatcher::javaobject> _javaPart;

private:
static auto constexpr TAG = "JDispatcher";
static auto constexpr kJavaDescriptor = "Lcom/margelo/filament/Dispatcher;";

private:
explicit JDispatcher(const jni::alias_ref<jhybridobject>& javaThis);
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> javaThis);
};

} // namespace margelo
21 changes: 21 additions & 0 deletions package/android/src/main/cpp/java-bindings/JFilamentProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "JFilamentProxy.h"
#include "AndroidManagedBuffer.h"
#include "JChoreographer.h"
#include "JDispatcher.h"
#include "JFilamentView.h"
#include "JNISharedPtr.h"
#include <fbjni/ByteBuffer.h>
Expand Down Expand Up @@ -45,6 +46,26 @@ std::shared_ptr<Choreographer> JFilamentProxy::createChoreographer() {
return std::static_pointer_cast<Choreographer>(sharedRef);
}

std::shared_ptr<Dispatcher> JFilamentProxy::getUIDispatcher() {
if (_uiDispatcher == nullptr) {
static const auto method = javaClassLocal()->getMethod<jni::alias_ref<JDispatcher::javaobject>()>("getUIDispatcher");
jni::local_ref<JDispatcher::javaobject> dispatcher = method(_javaPart);
jni::global_ref<JDispatcher::javaobject> globalRef = jni::make_global(dispatcher);
_uiDispatcher = JNISharedPtr::make_shared_from_jni<JDispatcher>(globalRef);
}
return _uiDispatcher;
}

std::shared_ptr<Dispatcher> JFilamentProxy::getBackgroundDispatcher() {
if (_backgroundDispatcher == nullptr) {
static const auto method = javaClassLocal()->getMethod<jni::alias_ref<JDispatcher::javaobject>()>("getBackgroundDispatcher");
jni::local_ref<JDispatcher::javaobject> dispatcher = method(_javaPart);
jni::global_ref<JDispatcher::javaobject> globalRef = jni::make_global(dispatcher);
_backgroundDispatcher = JNISharedPtr::make_shared_from_jni<JDispatcher>(globalRef);
}
return _backgroundDispatcher;
}

jsi::Runtime& JFilamentProxy::getRuntime() {
if (_runtime == nullptr) {
[[unlikely]];
Expand Down
5 changes: 5 additions & 0 deletions package/android/src/main/cpp/java-bindings/JFilamentProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include "Choreographer.h"
#include "Dispatcher.h"
#include "FilamentBuffer.h"
#include "FilamentView.h"
#include <ReactCommon/CallInvokerHolder.h>
Expand All @@ -27,6 +28,8 @@ class JFilamentProxy : public jni::HybridClass<JFilamentProxy> {
std::shared_ptr<FilamentBuffer> loadAsset(const std::string& path);
std::shared_ptr<FilamentView> findFilamentView(int id);
std::shared_ptr<Choreographer> createChoreographer();
std::shared_ptr<Dispatcher> getUIDispatcher();
std::shared_ptr<Dispatcher> getBackgroundDispatcher();

jsi::Runtime& getRuntime();
std::shared_ptr<react::CallInvoker> getCallInvoker();
Expand All @@ -36,6 +39,8 @@ class JFilamentProxy : public jni::HybridClass<JFilamentProxy> {
jni::global_ref<JFilamentProxy::javaobject> _javaPart;
jsi::Runtime* _runtime;
std::shared_ptr<facebook::react::CallInvoker> _callInvoker;
std::shared_ptr<Dispatcher> _uiDispatcher;
std::shared_ptr<Dispatcher> _backgroundDispatcher;

private:
static auto constexpr TAG = "FilamentProxy";
Expand Down
33 changes: 33 additions & 0 deletions package/android/src/main/java/com/margelo/filament/Dispatcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.margelo.filament;

import androidx.annotation.Keep;
import androidx.annotation.NonNull;

import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;

import java.util.concurrent.Executor;

/** @noinspection JavaJniMissingFunction*/
public class Dispatcher {
private final Executor executor;
/** @noinspection unused, FieldCanBeLocal */
@DoNotStrip
@Keep
private final HybridData mHybridData;

public Dispatcher(@NonNull Executor executor) {
this.executor = executor;
this.mHybridData = initHybrid();
}

/** @noinspection unused */
@DoNotStrip
@Keep
private void scheduleTrigger() {
executor.execute(this::trigger);
}

private native void trigger();
private native HybridData initHybrid();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;

import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
Expand All @@ -18,8 +19,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.concurrent.Executors;

/** @noinspection JavaJniMissingFunction*/
class FilamentProxy {
Expand All @@ -28,6 +28,8 @@ class FilamentProxy {
@Keep
private final HybridData mHybridData;
private final ReactApplicationContext reactContext;
private final Dispatcher uiThreadDispatcher;
private final Dispatcher backgroundThreadDispatcher;

FilamentProxy(@NonNull ReactApplicationContext context) {
JavaScriptContextHolder jsRuntimeHolder = context.getJavaScriptContextHolder();
Expand All @@ -41,6 +43,9 @@ class FilamentProxy {
}
mHybridData = initHybrid(runtimePointer, (CallInvokerHolderImpl) callInvokerHolder);
reactContext = context;

uiThreadDispatcher = new Dispatcher(ContextCompat.getMainExecutor(context));
backgroundThreadDispatcher = new Dispatcher(Executors.newCachedThreadPool());
}

/** @noinspection unused*/
Expand Down Expand Up @@ -93,5 +98,19 @@ FilamentView findFilamentView(int id) {
return (FilamentView) view;
}

/** @noinspection unused*/
@DoNotStrip
@Keep
Dispatcher getUIDispatcher() {
return uiThreadDispatcher;
}

/** @noinspection unused*/
@DoNotStrip
@Keep
Dispatcher getBackgroundDispatcher() {
return backgroundThreadDispatcher;
}

private native HybridData initHybrid(long jsRuntimePointer, CallInvokerHolderImpl callInvokerHolder);
}
56 changes: 56 additions & 0 deletions package/cpp/Dispatcher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// Created by Marc Rousavy on 12.03.24.
//

#pragma once

#include <functional>
#include <future>
#include <queue>

namespace margelo {

class Dispatcher {
private:
std::queue<std::function<void()>> _jobs;
std::mutex _mutex;

private:
// Schedule trigger will be implemented by Android/iOS to schedule a call to `trigger()` on the target Thread.
virtual void scheduleTrigger() = 0;

protected:
// Trigger will run the latest job that has been added to the queue.
void trigger() {
std::unique_lock lock(_mutex);
auto job = _jobs.front();
job();
_jobs.pop();
}

public:
template <typename T> std::future<T> runAsync(std::function<T()>&& function) {
// 1. Create Promise that can be shared between this and dispatcher thread
auto promise = std::make_shared<std::promise<T>>();
std::future<T> future = promise->get_future();

std::unique_lock lock(_mutex);
_jobs.push([function = std::move(function), promise]() {
try {
// 4. Call the actual function on the new Thread
T result = function();
// 5.a. Resolve the Promise if we succeeded
promise->set_value(std::move(result));
} catch (...) {
// 5.b. Reject the Promise if the call failed
promise->set_exception(std::current_exception());
}
});
scheduleTrigger();

// 3. Return an open future that gets resolved later by the dispatcher Thread
return future;
}
};

} // namespace margelo
18 changes: 16 additions & 2 deletions package/cpp/FilamentProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ using namespace facebook;

void FilamentProxy::loadHybridMethods() {
registerHybridMethod("loadAsset", &FilamentProxy::loadAssetAsync, this);
registerHybridMethod("findFilamentView", &FilamentProxy::findFilamentView, this);
registerHybridMethod("findFilamentView", &FilamentProxy::findFilamentViewAsync, this);
registerHybridMethod("createTestObject", &FilamentProxy::createTestObject, this);
registerHybridMethod("createEngine", &FilamentProxy::createEngine, this);
}

std::future<std::shared_ptr<FilamentBuffer>> FilamentProxy::loadAssetAsync(std::string path) {
auto weakThis = std::weak_ptr<FilamentProxy>(shared<FilamentProxy>());
return std::async(std::launch::async, [=]() {
auto dispatcher = getBackgroundDispatcher();
return dispatcher->runAsync<std::shared_ptr<FilamentBuffer>>([=]() {
auto sharedThis = weakThis.lock();
if (sharedThis != nullptr) {
return this->loadAsset(path);
Expand All @@ -35,6 +36,19 @@ std::future<std::shared_ptr<FilamentBuffer>> FilamentProxy::loadAssetAsync(std::
});
}

std::future<std::shared_ptr<FilamentView>> FilamentProxy::findFilamentViewAsync(int id) {
auto weakThis = std::weak_ptr<FilamentProxy>(shared<FilamentProxy>());
auto dispatcher = getUIDispatcher();
return dispatcher->runAsync<std::shared_ptr<FilamentView>>([=]() {
auto sharedThis = weakThis.lock();
if (sharedThis != nullptr) {
return this->findFilamentView(id);
} else {
throw std::runtime_error("Failed to find Filament View, FilamentProxy has already been destroyed!");
}
});
}

std::shared_ptr<TestHybridObject> FilamentProxy::createTestObject() {
return std::make_shared<TestHybridObject>();
}
Expand Down
Loading

0 comments on commit dd577b2

Please sign in to comment.