Skip to content

Commit

Permalink
feat: Create Choreographer (#11)
Browse files Browse the repository at this point in the history
Creates `Choreographer`, a platform specific instance that fires
`onFrame` callbacks at the screen's refresh rate.

The `Choreographer` instance runs on the Thread that created it. We
could create such instances on the UI Thread or any other arbitrary
Thread using either react-native-reanimated or
react-native-worklets-core.

Currently you can create a `Choreographer` using the `FilamentProxy`:

```ts
const choreographer = FilamentProxy.createChoreographer()
choreographer.addOnFrameCallback((timestamp: number) => {
  console.log(`onFrame at ${timestamp}`)
})
choreographer.start()
setTimeout(() => {
  choreographer.stop()
}, 1500)
```

Create on the UI Thread:

```ts
runOnUI(() => {
  'worklet'
  const choreographer = FilamentProxy.createChoreographer()
  choreographer.addOnFrameCallback((timestamp: number) => {
    'worklet'
    // TODO: This is currently not possible since we don't have a Worklets integration. This func cannot be called probably.
    // Reanimated did work on something pretty interesting where they abstract all of this on the JS side, meaning there's
    // no change required from our end - we just receive a jsi::Function as normal - but it's not yet public.
    // So to make that work, we need to integrate react-native-reanimated or react-native-worklets-core in RNF for now.
  })
})()
```

Note: You need to keep a strong reference on the `Choreographer`,
otherwise it might get deleted by GC.
  • Loading branch information
mrousavy authored Feb 23, 2024
1 parent 7aaf536 commit da82108
Show file tree
Hide file tree
Showing 31 changed files with 418 additions and 23 deletions.
2 changes: 2 additions & 0 deletions package/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ add_library(
../cpp/FilamentProxy.cpp
../cpp/Surface.cpp
../cpp/SurfaceProvider.cpp
../cpp/Choreographer.cpp
../cpp/Listener.cpp
../cpp/jsi/HybridObject.cpp
../cpp/jsi/Promise.cpp
Expand All @@ -34,6 +35,7 @@ add_library(
src/main/cpp/JNISharedPtr.cpp
src/main/cpp/FilamentInstaller.cpp
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/JSurfaceProvider.cpp
)
Expand Down
4 changes: 4 additions & 0 deletions package/android/src/main/cpp/AndroidFilamentProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ std::shared_ptr<FilamentView> AndroidFilamentProxy::findFilamentView(int id) {
return _proxy->cthis()->findFilamentView(id);
}

std::shared_ptr<Choreographer> AndroidFilamentProxy::createChoreographer() {
return _proxy->cthis()->createChoreographer();
}

} // namespace margelo
1 change: 1 addition & 0 deletions package/android/src/main/cpp/AndroidFilamentProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class AndroidFilamentProxy : public FilamentProxy {
// TODO(hanno): implement
int loadModel(std::string path) override;
std::shared_ptr<FilamentView> findFilamentView(int id) override;
std::shared_ptr<Choreographer> createChoreographer() override;

private:
jni::global_ref<JFilamentProxy::javaobject> _proxy;
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,4 +1,5 @@
#include "FilamentInstaller.h"
#include "JChoreographer.h"
#include "JFilamentProxy.h"
#include "JFilamentView.h"
#include "JSurfaceProvider.h"
Expand All @@ -11,5 +12,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
margelo::JFilamentProxy::registerNatives();
margelo::JSurfaceProvider::registerNatives();
margelo::JFilamentView::registerNatives();
margelo::JChoreographer::registerNatives();
});
}
37 changes: 37 additions & 0 deletions package/android/src/main/cpp/java-bindings/JChoreographer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// Created by Marc Rousavy on 23.02.24.
//

#include "JChoreographer.h"
#include <android/choreographer.h>

namespace margelo {

void JChoreographer::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JChoreographer::initHybrid),
makeNativeMethod("onFrame", JChoreographer::onFrameLong),
});
}

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

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

void JChoreographer::onFrameLong(jlong timestamp) {
onFrame(static_cast<double>(timestamp));
}

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

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

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

#pragma once

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

namespace margelo {

using namespace facebook;

class JChoreographer : public jni::HybridClass<JChoreographer>, public Choreographer {
public:
static void registerNatives();

void start() override;
void stop() override;
void onFrameLong(jlong timestamp);

private:
friend HybridBase;
jni::global_ref<JChoreographer::javaobject> _javaPart;
static auto constexpr TAG = "JChoreographer";
static auto constexpr kJavaDescriptor = "Lcom/margelo/filament/FilamentChoreographer;";

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

} // namespace margelo
9 changes: 9 additions & 0 deletions package/android/src/main/cpp/java-bindings/JFilamentProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//

#include "JFilamentProxy.h"
#include "JChoreographer.h"
#include "JFilamentView.h"
#include "JNISharedPtr.h"
#include <fbjni/fbjni.h>
Expand Down Expand Up @@ -32,6 +33,14 @@ std::shared_ptr<FilamentView> JFilamentProxy::findFilamentView(int id) {
return std::static_pointer_cast<FilamentView>(sharedRef);
}

std::shared_ptr<Choreographer> JFilamentProxy::createChoreographer() {
static const auto method = javaClassLocal()->getMethod<jni::alias_ref<JChoreographer::javaobject>()>("createChoreographer");
jni::local_ref<JChoreographer::javaobject> choreographer = method(_javaPart);
jni::global_ref<JChoreographer::javaobject> globalRef = jni::make_global(choreographer);
std::shared_ptr<JChoreographer> sharedRef = JNISharedPtr::make_shared_from_jni<JChoreographer>(globalRef);
return std::static_pointer_cast<Choreographer>(sharedRef);
}

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

#pragma once

#include "Choreographer.h"
#include "FilamentView.h"
#include <ReactCommon/CallInvokerHolder.h>
#include <fbjni/fbjni.h>
Expand All @@ -25,6 +26,7 @@ class JFilamentProxy : public jni::HybridClass<JFilamentProxy> {
// TODO(hanno): implement
int loadModel(const std::string& path);
std::shared_ptr<FilamentView> findFilamentView(int id);
std::shared_ptr<Choreographer> createChoreographer();

jsi::Runtime& getRuntime();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.margelo.filament;

import android.view.Choreographer;

import androidx.annotation.Keep;

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

/** @noinspection JavaJniMissingFunction*/
public class FilamentChoreographer {
/** @noinspection unused, FieldCanBeLocal */
@DoNotStrip
@Keep
private final HybridData mHybridData;
private final Choreographer choreographer;
private boolean isRunning;

public FilamentChoreographer() {
mHybridData = initHybrid();
choreographer = Choreographer.getInstance();
}

private void onFrameCallback(long timestamp) {
if (!isRunning) return;
onFrame(timestamp);
choreographer.postFrameCallback(this::onFrameCallback);
}

/** @noinspection unused */
@DoNotStrip
@Keep
private synchronized void start() {
if (!isRunning) {
isRunning = true;
choreographer.postFrameCallback(this::onFrameCallback);
}
}

/** @noinspection unused */
@DoNotStrip
@Keep
private synchronized void stop() {
if (isRunning) {
isRunning = false;
choreographer.removeFrameCallback(this::onFrameCallback);
}
}

private native HybridData initHybrid();
private native void onFrame(long timestamp);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ class FilamentProxy {
reactContext = context;
}

/** @noinspection unused*/
@DoNotStrip
@Keep
FilamentChoreographer createChoreographer() {
return new FilamentChoreographer();
}

/** @noinspection unused*/
@DoNotStrip
@Keep
Expand Down
28 changes: 28 additions & 0 deletions package/cpp/Choreographer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Created by Marc Rousavy on 23.02.24.
//

#include "Choreographer.h"

namespace margelo {

void Choreographer::loadHybridMethods() {
registerHybridMethod("addOnFrameListener", &Choreographer::addOnFrameListener, this);
registerHybridMethod("start", &Choreographer::start, this);
registerHybridMethod("stop", &Choreographer::stop, this);
}

std::shared_ptr<Listener> Choreographer::addOnFrameListener(Choreographer::OnFrameCallback onFrameCallback) {
_callbacks.push_back(std::move(onFrameCallback));
return std::make_shared<Listener>([]() {
// TODO: Find a safe way to remove this listener from the vector.
});
}

void Choreographer::onFrame(double timestamp) {
for (const auto& callback : _callbacks) {
callback(timestamp);
}
}

} // namespace margelo
31 changes: 31 additions & 0 deletions package/cpp/Choreographer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Created by Marc Rousavy on 23.02.24.
//

#pragma once

#include "Listener.h"
#include "jsi/HybridObject.h"
#include <functional>

namespace margelo {

class Choreographer : public HybridObject {
public:
using OnFrameCallback = std::function<void(double timestamp)>;

std::shared_ptr<Listener> addOnFrameListener(OnFrameCallback onFrameCallback);

virtual void start() = 0;
virtual void stop() = 0;

protected:
void onFrame(double timestamp);

void loadHybridMethods() override;

private:
std::vector<OnFrameCallback> _callbacks;
};

} // namespace margelo
1 change: 1 addition & 0 deletions package/cpp/FilamentProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void FilamentProxy::loadHybridMethods() {
registerHybridMethod("loadModel", &FilamentProxy::loadModel, this);
registerHybridMethod("findFilamentView", &FilamentProxy::findFilamentView, this);
registerHybridMethod("createTestObject", &FilamentProxy::createTestObject, this);
registerHybridMethod("createChoreographer", &FilamentProxy::createChoreographer, this);
}

} // namespace margelo
4 changes: 2 additions & 2 deletions package/cpp/FilamentProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@

#pragma once

#include <jsi/jsi.h>

#include <string>
#include <vector>

#include "Choreographer.h"
#include "FilamentView.h"
#include "jsi/HybridObject.h"
#include "test/TestHybridObject.h"
Expand All @@ -21,6 +20,7 @@ class FilamentProxy : public HybridObject {
private:
virtual int loadModel(std::string path) = 0;
virtual std::shared_ptr<FilamentView> findFilamentView(int id) = 0;
virtual std::shared_ptr<Choreographer> createChoreographer() = 0;

std::shared_ptr<TestHybridObject> createTestObject();

Expand Down
4 changes: 4 additions & 0 deletions package/cpp/Listener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ void Listener::remove() {
_isRemoved = true;
}

void Listener::loadHybridMethods() {
registerHybridMethod("remove", &Listener::remove, this);
}

} // namespace margelo
6 changes: 5 additions & 1 deletion package/cpp/Listener.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@

#pragma once

#include "jsi/HybridObject.h"
#include <functional>

namespace margelo {

class Listener {
class Listener : public HybridObject {
public:
Listener(Listener&& listener) : _remove(std::move(listener._remove)), _isRemoved(listener._isRemoved) {}
explicit Listener(std::function<void()> remove);
~Listener();
void remove();

void loadHybridMethods() override;

private:
std::function<void()> _remove;
bool _isRemoved;
Expand Down
9 changes: 2 additions & 7 deletions package/cpp/SurfaceProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,8 @@ Listener SurfaceProvider::addOnSurfaceChangedListener(margelo::SurfaceProvider::
std::unique_lock lock(_mutex);

_callbacks.push_back(std::move(callback));
size_t index = _callbacks.size();

return Listener([weakThis = this, index]() {
if (weakThis != nullptr) {
std::unique_lock lock(weakThis->_mutex);
weakThis->_callbacks.erase(weakThis->_callbacks.begin() + index);
}
return Listener([]() {
// TODO: Find a safe way to remove this listener from the vector.
});
}

Expand Down
2 changes: 1 addition & 1 deletion package/cpp/core/EngineWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ void EngineWrapper::setSurfaceProvider(std::shared_ptr<SurfaceProvider> surfaceP
Listener listener = surfaceProvider->addOnSurfaceChangedListener(
SurfaceProvider::Callback{.onSurfaceCreated = [=](std::shared_ptr<Surface> surface) { this->setSurface(surface); },
.onSurfaceDestroyed = [=](std::shared_ptr<Surface> surface) { this->destroySurface(); }});
_listener = std::make_unique<Listener>(std::move(listener));
_listener = std::make_shared<Listener>(std::move(listener));
}

void EngineWrapper::setSurface(std::shared_ptr<Surface> surface) {
Expand Down
2 changes: 1 addition & 1 deletion package/cpp/core/EngineWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class EngineWrapper : public HybridObject {
private:
std::shared_ptr<Engine> _engine;
std::shared_ptr<SurfaceProvider> _surfaceProvider;
std::unique_ptr<Listener> _listener;
std::shared_ptr<Listener> _listener;
};

} // namespace margelo
Loading

0 comments on commit da82108

Please sign in to comment.