feat: Replace *NativeMap and *NativeArray with Map<K,V> and List<T> for faster JSI -> JNI calls (#1720)

Replaces `ReadableNativeMap`/`WritableNativeMap` with `Map<String, Object>` and `ReadableNativeArray`/`WritableNativeArray` with `List<Object>`, making the JSI -> JNI conversion a bit faster and more logical.
Also, we could now convert Array Buffers or HostObjects if we wanted to.
This commit is contained in:
Marc Rousavy 2023-08-25 12:22:44 +02:00 committed by GitHub
parent f87bc74de1
commit dfb86e174b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 125 additions and 167 deletions

View File

@ -24,18 +24,19 @@ add_library(
${PACKAGE_NAME}
SHARED
../cpp/JSITypedArray.cpp
src/main/cpp/FrameHostObject.cpp
src/main/cpp/FrameProcessorPluginHostObject.cpp
src/main/cpp/JSIJNIConversion.cpp
src/main/cpp/VisionCamera.cpp
src/main/cpp/VisionCameraProxy.cpp
# Frame Processor
src/main/cpp/frameprocessor/FrameHostObject.cpp
src/main/cpp/frameprocessor/FrameProcessorPluginHostObject.cpp
src/main/cpp/frameprocessor/JSIJNIConversion.cpp
src/main/cpp/frameprocessor/VisionCameraProxy.cpp
src/main/cpp/frameprocessor/java-bindings/JFrame.cpp
src/main/cpp/frameprocessor/java-bindings/JFrameProcessor.cpp
src/main/cpp/frameprocessor/java-bindings/JFrameProcessorPlugin.cpp
src/main/cpp/frameprocessor/java-bindings/JVisionCameraProxy.cpp
src/main/cpp/frameprocessor/java-bindings/JVisionCameraScheduler.cpp
# Skia Frame Processor
src/main/cpp/skia/SkiaRenderer.cpp
src/main/cpp/java-bindings/JFrame.cpp
src/main/cpp/java-bindings/JFrameProcessor.cpp
src/main/cpp/java-bindings/JFrameProcessorPlugin.cpp
src/main/cpp/java-bindings/JHashMap.cpp
src/main/cpp/java-bindings/JVisionCameraProxy.cpp
src/main/cpp/java-bindings/JVisionCameraScheduler.cpp
)
# Header Search Paths (includes)
@ -44,6 +45,9 @@ target_include_directories(
PRIVATE
"../cpp"
"src/main/cpp"
"src/main/cpp/frameprocessor"
"src/main/cpp/frameprocessor/java-bindings"
"src/main/cpp/skia"
"${NODE_MODULES_DIR}/react-native/ReactCommon"
"${NODE_MODULES_DIR}/react-native/ReactCommon/callinvoker"
"${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/react/turbomodule" # <-- CallInvokerHolder JNI wrapper

View File

@ -1,10 +1,10 @@
#include <jni.h>
#include <fbjni/fbjni.h>
#include "java-bindings/JVisionCameraScheduler.h"
#include "java-bindings/JFrameProcessor.h"
#include "java-bindings/JVisionCameraProxy.h"
#include "JVisionCameraScheduler.h"
#include "JFrameProcessor.h"
#include "JVisionCameraProxy.h"
#include "VisionCameraProxy.h"
#include "skia/SkiaRenderer.h"
#include "SkiaRenderer.h"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
return facebook::jni::initialize(vm, [] {

View File

@ -10,7 +10,7 @@
#include <vector>
#include <string>
#include "java-bindings/JFrame.h"
#include "JFrame.h"
namespace vision {

View File

@ -34,7 +34,7 @@ jsi::Value FrameProcessorPluginHostObject::get(jsi::Runtime& runtime, const jsi:
auto frame = frameHostObject->frame;
// Options are second argument (possibly undefined)
local_ref<react::ReadableNativeMap::javaobject> options = nullptr;
local_ref<JMap<jstring, jobject>> options = nullptr;
if (count > 1) {
options = JSIJNIConversion::convertJSIObjectToJNIMap(runtime, arguments[1].asObject(runtime));
}

View File

@ -5,7 +5,7 @@
#pragma once
#include <jsi/jsi.h>
#include "java-bindings/JFrameProcessorPlugin.h"
#include "JFrameProcessorPlugin.h"
#include <memory>
#include <fbjni/fbjni.h>
#include <vector>

View File

@ -13,25 +13,71 @@
#include <utility>
#include <memory>
#include <react/jni/NativeMap.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/jni/WritableNativeMap.h>
#include <jsi/JSIDynamic.h>
#include <folly/dynamic.h>
#include "FrameHostObject.h"
#include "java-bindings/JFrame.h"
#include "java-bindings/JArrayList.h"
#include "java-bindings/JHashMap.h"
#include "JFrame.h"
namespace vision {
using namespace facebook;
jni::local_ref<react::ReadableNativeMap::javaobject> JSIJNIConversion::convertJSIObjectToJNIMap(jsi::Runtime& runtime, const jsi::Object& object) {
auto dynamic = jsi::dynamicFromValue(runtime, jsi::Value(runtime, object));
return react::ReadableNativeMap::createWithContents(std::move(dynamic));
jni::local_ref<jni::JMap<jstring, jobject>> JSIJNIConversion::convertJSIObjectToJNIMap(jsi::Runtime& runtime, const jsi::Object& object) {
auto propertyNames = object.getPropertyNames(runtime);
auto size = propertyNames.size(runtime);
auto hashMap = jni::JHashMap<jstring, jobject>::create();
for (size_t i = 0; i < size; i++) {
auto propName = propertyNames.getValueAtIndex(runtime, i).asString(runtime);
auto key = jni::make_jstring(propName.utf8(runtime));
auto value = object.getProperty(runtime, propName);
if (value.isNull() || value.isUndefined()) {
// null
hashMap->put(key, nullptr);
} else if (value.isBool()) {
// Boolean
auto boolean = value.getBool();
hashMap->put(key, jni::JBoolean::valueOf(boolean));
} else if (value.isNumber()) {
// Double
auto number = value.getNumber();
hashMap->put(key, jni::JDouble::valueOf(number));
} else if (value.isString()) {
// String
auto str = value.getString(runtime).utf8(runtime);
hashMap->put(key, jni::make_jstring(str));
} else if (value.isObject()) {
// Object
auto valueAsObject = value.getObject(runtime);
if (valueAsObject.isArray(runtime)) {
// List<Object>
} else if (valueAsObject.isHostObject(runtime)) {
throw std::runtime_error("You can't pass HostObjects here.");
} else {
// Map<String, Object>
auto map = convertJSIObjectToJNIMap(runtime, valueAsObject);
hashMap->put(key, map);
}
} else {
auto stringRepresentation = value.toString(runtime).utf8(runtime);
throw std::runtime_error("Failed to convert jsi::Value to JNI value - unsupported type!" + stringRepresentation);
}
}
return hashMap;
}
jsi::Value JSIJNIConversion::convertJNIObjectToJSIValue(jsi::Runtime &runtime, const jni::local_ref<jobject>& object) {
@ -66,10 +112,10 @@ jsi::Value JSIJNIConversion::convertJNIObjectToJSIValue(jsi::Runtime &runtime, c
return jsi::String::createFromUtf8(runtime, object->toString());
} else if (object->isInstanceOf(JArrayList<jobject>::javaClassStatic())) {
// ArrayList<E>
} else if (object->isInstanceOf(JList<jobject>::javaClassStatic())) {
// List<E>
auto arrayList = static_ref_cast<JArrayList<jobject>>(object);
auto arrayList = static_ref_cast<JList<jobject>>(object);
auto size = arrayList->size();
auto result = jsi::Array(runtime, size);
@ -80,19 +126,10 @@ jsi::Value JSIJNIConversion::convertJNIObjectToJSIValue(jsi::Runtime &runtime, c
}
return result;
} else if (object->isInstanceOf(react::ReadableArray::javaClassStatic())) {
// ReadableArray
} else if (object->isInstanceOf(JMap<jstring, jobject>::javaClassStatic())) {
// Map<K, V>
static const auto toArrayListFunc = react::ReadableArray::javaClassLocal()->getMethod<JArrayList<jobject>()>("toArrayList");
// call recursive, this time ArrayList<E>
auto array = toArrayListFunc(object.get());
return convertJNIObjectToJSIValue(runtime, array);
} else if (object->isInstanceOf(JHashMap<jstring, jobject>::javaClassStatic())) {
// HashMap<K, V>
auto map = static_ref_cast<JHashMap<jstring, jobject>>(object);
auto map = static_ref_cast<JMap<jstring, jobject>>(object);
auto result = jsi::Object(runtime);
for (const auto& entry : *map) {
@ -103,16 +140,7 @@ jsi::Value JSIJNIConversion::convertJNIObjectToJSIValue(jsi::Runtime &runtime, c
}
return result;
} else if (object->isInstanceOf(react::ReadableMap::javaClassStatic())) {
// ReadableMap
static const auto toHashMapFunc = react::ReadableMap::javaClassLocal()->getMethod<JHashMap<jstring, jobject>()>("toHashMap");
// call recursive, this time HashMap<K, V>
auto hashMap = toHashMapFunc(object.get());
return convertJNIObjectToJSIValue(runtime, hashMap);
} else if (object->isInstanceOf(JFrame::javaClassStatic())) {
} if (object->isInstanceOf(JFrame::javaClassStatic())) {
// Frame
auto frame = static_ref_cast<JFrame>(object);

View File

@ -7,7 +7,6 @@
#include <jsi/jsi.h>
#include <jni.h>
#include <fbjni/fbjni.h>
#include <react/jni/ReadableNativeMap.h>
namespace vision {
@ -15,7 +14,7 @@ namespace JSIJNIConversion {
using namespace facebook;
jni::local_ref<react::ReadableNativeMap::javaobject> convertJSIObjectToJNIMap(jsi::Runtime& runtime, const jsi::Object& object);
jni::local_ref<jni::JMap<jstring, jobject>> convertJSIObjectToJNIMap(jsi::Runtime& runtime, const jsi::Object& object);
jsi::Value convertJNIObjectToJSIValue(jsi::Runtime& runtime, const jni::local_ref<jobject>& object);

View File

@ -5,8 +5,8 @@
#include "VisionCameraProxy.h"
#include <jsi/jsi.h>
#include "java-bindings/JFrameProcessor.h"
#include "java-bindings/JFrameProcessorPlugin.h"
#include "JFrameProcessor.h"
#include "JFrameProcessorPlugin.h"
#include "JSIJNIConversion.h"
#include <android/log.h>

View File

@ -6,8 +6,8 @@
#include <jsi/jsi.h>
#include "java-bindings/JVisionCameraScheduler.h"
#include "java-bindings/JVisionCameraProxy.h"
#include "JVisionCameraScheduler.h"
#include "JVisionCameraProxy.h"
#include <vector>
#include <string>

View File

@ -12,10 +12,10 @@ namespace vision {
using namespace facebook;
using namespace jni;
using TCallback = jobject(alias_ref<JFrame::javaobject>, alias_ref<react::ReadableNativeMap::javaobject> params);
using TCallback = jobject(alias_ref<JFrame::javaobject>, alias_ref<JMap<jstring, jobject>> params);
local_ref<jobject> JFrameProcessorPlugin::callback(const alias_ref<JFrame::javaobject>& frame,
const alias_ref<react::ReadableNativeMap::javaobject>& params) const {
const alias_ref<JMap<jstring, jobject>>& params) const {
auto callbackMethod = getClass()->getMethod<TCallback>("callback");
auto result = callbackMethod(self(), frame, params);

View File

@ -7,7 +7,6 @@
#include <jni.h>
#include <fbjni/fbjni.h>
#include <string>
#include <react/jni/ReadableNativeMap.h>
#include "JFrame.h"
@ -24,7 +23,7 @@ struct JFrameProcessorPlugin : public JavaClass<JFrameProcessorPlugin> {
* Call the plugin.
*/
local_ref<jobject> callback(const alias_ref<JFrame::javaobject>& frame,
const alias_ref<react::ReadableNativeMap::javaobject>& params) const;
const alias_ref<JMap<jstring, jobject>>& params) const;
};
} // namespace vision

View File

@ -9,7 +9,6 @@
#include <string>
#include <jsi/jsi.h>
#include <react/jni/ReadableNativeMap.h>
#include "FrameProcessorPluginHostObject.h"
#include "JSITypedArray.h"
@ -24,7 +23,7 @@ namespace vision {
using TSelf = local_ref<HybridClass<JVisionCameraProxy>::jhybriddata>;
using TJSCallInvokerHolder = jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>;
using TScheduler = jni::alias_ref<JVisionCameraScheduler::javaobject>;
using TOptions = jni::local_ref<react::ReadableNativeMap::javaobject>;
using TOptions = jni::local_ref<JMap<jstring, jobject>>;
JVisionCameraProxy::JVisionCameraProxy(const jni::alias_ref<JVisionCameraProxy::jhybridobject>& javaThis,
jsi::Runtime* runtime,

View File

@ -6,7 +6,6 @@
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <react/jni/ReadableNativeMap.h>
#include <ReactCommon/CallInvokerHolder.h>
#include "JFrameProcessorPlugin.h"
@ -34,7 +33,7 @@ class JVisionCameraProxy : public jni::HybridClass<JVisionCameraProxy> {
const jsi::Object& frameProcessor);
void removeFrameProcessor(int viewTag);
jni::local_ref<JFrameProcessorPlugin::javaobject> getFrameProcessorPlugin(const std::string& name,
jni::local_ref<react::ReadableNativeMap::javaobject> options);
jni::local_ref<JMap<jstring, jobject>> options);
jsi::Runtime* getJSRuntime() { return _runtime; }

View File

@ -1,21 +0,0 @@
//
// Created by Marc Rousavy on 24.06.21.
//
#pragma once
#include <jni.h>
#include <fbjni/fbjni.h>
namespace vision {
using namespace facebook;
using namespace jni;
// TODO: Remove when fbjni 0.2.3 releases.
template <typename E = jobject>
struct JArrayList : JavaClass<JArrayList<E>, JList<E>> {
constexpr static auto kJavaDescriptor = "Ljava/util/ArrayList;";
};
} // namespace vision

View File

@ -1,20 +0,0 @@
//
// Created by Marc Rousavy on 25.06.21.
//
#include "JHashMap.h"
#include <jni.h>
#include <fbjni/fbjni.h>
namespace vision {
using namespace facebook;
using namespace jni;
template <typename K, typename V>
local_ref<JHashMap<K, V>> JHashMap<K, V>::create() {
return JHashMap<K, V>::newInstance();
}
} // namespace vision

View File

@ -1,23 +0,0 @@
//
// Created by Marc Rousavy on 25.06.21.
//
#pragma once
#include <jni.h>
#include <fbjni/fbjni.h>
namespace vision {
using namespace facebook;
using namespace jni;
// TODO: Remove when fbjni 0.2.3 releases.
template <typename K = jobject, typename V = jobject>
struct JHashMap : JavaClass<JHashMap<K, V>, JMap<K, V>> {
constexpr static auto kJavaDescriptor = "Ljava/util/HashMap;";
static local_ref<JHashMap<K, V>> create();
};
} // namespace vision

View File

@ -4,7 +4,7 @@ import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ReadableNativeMap;
import java.util.Map;
/**
* Declares a Frame Processor Plugin.
@ -21,5 +21,5 @@ public abstract class FrameProcessorPlugin {
*/
@DoNotStrip
@Keep
public abstract @Nullable Object callback(@NonNull Frame frame, @Nullable ReadableNativeMap params);
public abstract @Nullable Object callback(@NonNull Frame frame, @Nullable Map<String, Object> params);
}

View File

@ -2,12 +2,9 @@ package com.mrousavy.camera.frameprocessor;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ReadableNativeMap;
import java.util.HashMap;
import java.util.Map;
import java.util.HashMap;
@DoNotStrip
@Keep
@ -24,7 +21,7 @@ public class FrameProcessorPluginRegistry {
@DoNotStrip
@Keep
public static FrameProcessorPlugin getPlugin(String name, ReadableNativeMap options) {
public static FrameProcessorPlugin getPlugin(String name, Map<String, Object> options) {
PluginInitializer initializer = Plugins.get(name);
if (initializer == null) {
return null;
@ -33,6 +30,6 @@ public class FrameProcessorPluginRegistry {
}
public interface PluginInitializer {
FrameProcessorPlugin initializePlugin(@Nullable ReadableNativeMap options);
FrameProcessorPlugin initializePlugin(@Nullable Map<String, Object> options);
}
}

View File

@ -6,7 +6,6 @@ import androidx.annotation.UiThread
import com.facebook.jni.HybridData
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableNativeMap
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
import com.facebook.react.uimanager.UIManagerHelper
@ -14,7 +13,6 @@ import com.mrousavy.camera.CameraView
import com.mrousavy.camera.ViewNotFoundError
import java.lang.ref.WeakReference
@Suppress("KotlinJniMissingFunction") // we use fbjni.
class VisionCameraProxy(context: ReactApplicationContext) {
companion object {
@ -71,7 +69,7 @@ class VisionCameraProxy(context: ReactApplicationContext) {
@DoNotStrip
@Keep
fun getFrameProcessorPlugin(name: String, options: ReadableNativeMap): FrameProcessorPlugin {
fun getFrameProcessorPlugin(name: String, options: Map<String, Any>): FrameProcessorPlugin {
return FrameProcessorPluginRegistry.getPlugin(name, options)
}

View File

@ -39,8 +39,8 @@ Similar to a TurboModule, the Frame Processor Plugin Registry API automatically
| `number` | `NSNumber*` (double) | `Double` |
| `boolean` | `NSNumber*` (boolean) | `Boolean` |
| `string` | `NSString*` | `String` |
| `[]` | `NSArray*` | `ReadableNativeArray` |
| `{}` | `NSDictionary*` | `ReadableNativeMap` |
| `[]` | `NSArray*` | `List<Object>` |
| `{}` | `NSDictionary*` | `Map<String, Object>` |
| `undefined` / `null` | `nil` | `null` |
| `(any, any) => void` | [`RCTResponseSenderBlock`][4] | `(Object, Object) -> void` |
| [`Frame`][1] | [`Frame*`][2] | [`Frame`][3] |

View File

@ -61,7 +61,7 @@ import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin;
public class FaceDetectorFrameProcessorPlugin extends FrameProcessorPlugin {
@Override
public Object callback(Frame frame, ReadableNativeMap arguments) {
public Object callback(Frame frame, Map<String, Object> arguments) {
// code goes here
return null;
}
@ -126,7 +126,7 @@ import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin
class FaceDetectorFrameProcessorPlugin: FrameProcessorPlugin() {
override fun callback(frame: Frame, arguments: ReadableNativeMap): Any? {
override fun callback(frame: Frame, arguments: Map<String, Object>): Any? {
// code goes here
return null
}

View File

@ -3,40 +3,40 @@ package com.mrousavy.camera.example;
import android.media.Image;
import android.util.Log;
import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.mrousavy.camera.frameprocessor.Frame;
import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ExampleFrameProcessorPlugin extends FrameProcessorPlugin {
@Override
public Object callback(@NotNull Frame frame, @Nullable ReadableNativeMap params) {
HashMap<String, Object> hashMap = params != null ? params.toHashMap() : new HashMap<>();
public Object callback(@NotNull Frame frame, @Nullable Map<String, Object> params) {
if (params == null) return null;
Image image = frame.getImage();
Log.d("ExamplePlugin", image.getWidth() + " x " + image.getHeight() + " Image with format #" + image.getFormat() + ". Logging " + hashMap.size() + " parameters:");
Log.d("ExamplePlugin", image.getWidth() + " x " + image.getHeight() + " Image with format #" + image.getFormat() + ". Logging " + params.size() + " parameters:");
for (String key : hashMap.keySet()) {
Object value = hashMap.get(key);
Log.d("ExamplePlugin", " -> " + (value == null ? "(null)" : value.toString() + " (" + value.getClass().getName() + ")"));
for (String key : params.keySet()) {
Object value = params.get(key);
Log.d("ExamplePlugin", " -> " + (value == null ? "(null)" : value + " (" + value.getClass().getName() + ")"));
}
WritableNativeMap map = new WritableNativeMap();
map.putString("example_str", "Test");
map.putBoolean("example_bool", true);
map.putDouble("example_double", 5.3);
Map<String, Object> map = new HashMap<>();
map.put("example_str", "Test");
map.put("example_bool", true);
map.put("example_double", 5.3);
WritableNativeArray array = new WritableNativeArray();
array.pushString("Hello!");
array.pushBoolean(true);
array.pushDouble(17.38);
List<Object> array = new ArrayList<>();
array.add("Hello!");
array.add(true);
array.add(17.38);
map.putArray("example_array", array);
map.put("example_array", array);
return map;
}

View File

@ -8,7 +8,6 @@ import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.soloader.SoLoader;
import java.util.List;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;