Skip to content

Commit

Permalink
feat: Separate out JSIConverter files
Browse files Browse the repository at this point in the history
  • Loading branch information
mrousavy committed Aug 5, 2024
1 parent 401743c commit 9818b86
Show file tree
Hide file tree
Showing 8 changed files with 597 additions and 433 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Created by Marc Rousavy on 21.02.24.
//

#pragma once

// Forward declare a few of the common types that might have cyclic includes.
namespace margelo::nitro {
class AnyMap;

template <typename T, typename Enable>
struct JSIConverter;
} // namespace margelo::nitro

#include "JSIConverter.hpp"

#include "AnyMap.hpp"
#include <memory>

namespace margelo::nitro {

using namespace facebook;

// AnyValue <> Record<K, V>
template <>
struct JSIConverter<AnyValue> {
static inline AnyValue fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
return JSIConverter<AnyValue::variant>::fromJSI(runtime, arg);
}
static inline jsi::Value toJSI(jsi::Runtime& runtime, const AnyValue& value) {
return JSIConverter<std::variant<AnyValue::variant>>::toJSI(runtime, value);
}
};

// AnyMap <> Record<K, V>
template <>
struct JSIConverter<std::shared_ptr<AnyMap>> {
static inline std::shared_ptr<AnyMap> fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
jsi::Object object = arg.asObject(runtime);
jsi::Array propNames = object.getPropertyNames(runtime);
size_t size = propNames.size(runtime);
std::shared_ptr<AnyMap> map = AnyMap::make();
for (size_t i = 0; i < size; i++) {
jsi::String jsKey = propNames.getValueAtIndex(runtime, i).getString(runtime);
jsi::Value jsValue = object.getProperty(runtime, jsKey);
map->setAny(jsKey.utf8(runtime), JSIConverter<AnyValue>::fromJSI(runtime, jsValue));
}
return map;
}
static inline jsi::Value toJSI(jsi::Runtime& runtime, std::shared_ptr<AnyMap> map) {
jsi::Object object(runtime);
for (const auto& item : map->getMap()) {
jsi::String key = jsi::String::createFromUtf8(runtime, item.first);
jsi::Value value = JSIConverter<AnyValue>::toJSI(runtime, item.second);
object.setProperty(runtime, std::move(key), std::move(value));
}
return object;
}
};

} // namespace margelo::nitro
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Created by Marc Rousavy on 21.02.24.
//

#pragma once

// Forward declare a few of the common types that might have cyclic includes.
namespace margelo::nitro {
class ArrayBuffer;

template <typename T, typename Enable>
struct JSIConverter;
} // namespace margelo::nitro

#include "JSIConverter.hpp"

#include "ArrayBuffer.hpp"
#include <memory>

namespace margelo::nitro {

using namespace facebook;

// MutableBuffer <> ArrayBuffer
template <>
struct JSIConverter<std::shared_ptr<jsi::MutableBuffer>> {
static inline std::shared_ptr<ArrayBuffer> fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
jsi::Object object = arg.asObject(runtime);
if (!object.isArrayBuffer(runtime)) [[unlikely]] {
throw std::runtime_error("Object \"" + arg.toString(runtime).utf8(runtime) + "\" is not an ArrayBuffer!");
}
jsi::ArrayBuffer arrayBuffer = object.getArrayBuffer(runtime);
return std::make_shared<ArrayBuffer>(arrayBuffer.data(runtime), arrayBuffer.size(runtime), false);
}
static inline jsi::Value toJSI(jsi::Runtime& runtime, std::shared_ptr<jsi::MutableBuffer> buffer) {
return jsi::ArrayBuffer(runtime, buffer);
}
};

} // namespace margelo::nitro
123 changes: 123 additions & 0 deletions packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Function.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// Created by Marc Rousavy on 21.02.24.
//

#pragma once

// Forward declare a few of the common types that might have cyclic includes.
namespace margelo::nitro {
template <typename T>
class JSICache;

template <typename T, typename Enable>
struct JSIConverter;
} // namespace margelo::nitro

#include "JSIConverter.hpp"

#include "Dispatcher.hpp"
#include "FutureType.hpp"
#include "JSICache.hpp"
#include <functional>
#include <future>

namespace margelo::nitro {

using namespace facebook;

// [](Args...) -> T {} <> (Args...) => T
template <typename ReturnType, typename... Args>
struct JSIConverter<std::function<ReturnType(Args...)>> {
// std::future<T> -> T
using ResultingType = future_type_v<ReturnType>;

static inline std::function<ReturnType(Args...)> fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
// Make function global - it'll be managed by the Runtime's memory, and we only have a weak_ref to it.
auto cache = JSICache<jsi::Function>::getOrCreateCache(runtime);
jsi::Function function = arg.asObject(runtime).asFunction(runtime);
OwningReference<jsi::Function> sharedFunction = cache.makeGlobal(std::move(function));

std::shared_ptr<Dispatcher> strongDispatcher = Dispatcher::getRuntimeGlobalDispatcher(runtime);
std::weak_ptr<Dispatcher> weakDispatcher = strongDispatcher;

// Create a C++ function that can be called by the consumer.
// This will call the jsi::Function if it is still alive.
return [&runtime, weakDispatcher, sharedFunction = std::move(sharedFunction)](Args... args) -> ReturnType {
// Try to get the JS Dispatcher if the Runtime is still alive
std::shared_ptr<Dispatcher> dispatcher = weakDispatcher.lock();
if (!dispatcher) {
if constexpr (std::is_void_v<ResultingType>) {
Logger::log("JSIConverter", "Tried calling void(..) function, but the JS Dispatcher has already been deleted by JS!");
return;
} else {
throw std::runtime_error("Cannot call the given Function - the JS Dispatcher has already been destroyed by the JS Runtime!");
}
}

if constexpr (std::is_void_v<ResultingType>) {
dispatcher->runAsync([&runtime, sharedFunction = std::move(sharedFunction), ... args = std::move(args)]() {
callJSFunction(runtime, sharedFunction, args...);
});
} else {
return dispatcher->runAsyncAwaitable<ResultingType>(
[&runtime, sharedFunction = std::move(sharedFunction), ... args = std::move(args)]() -> ResultingType {
return callJSFunction(runtime, sharedFunction, args...);
});
}
};
}

static inline jsi::Value toJSI(jsi::Runtime& runtime, std::function<ReturnType(Args...)>&& function) {
jsi::HostFunctionType jsFunction = [function = std::move(function)](jsi::Runtime& runtime, const jsi::Value& thisValue,
const jsi::Value* args, size_t count) -> jsi::Value {
if (count != sizeof...(Args)) [[unlikely]] {
throw jsi::JSError(runtime, "Function expected " + std::to_string(sizeof...(Args)) + " arguments, but received " +
std::to_string(count) + "!");
}
return callHybridFunction(function, runtime, args, std::index_sequence_for<Args...>{});
};
return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "hostFunction"), sizeof...(Args), jsFunction);
}

private:
static inline ResultingType callJSFunction(jsi::Runtime& runtime, const OwningReference<jsi::Function>& function, const Args&... args) {
// Throw a lock on the OwningReference<T> so we can guarantee safe access (Hermes GC cannot delete it while `lock` is alive)
OwningLock<jsi::Function> lock = function.lock();

if (!function) {
if constexpr (std::is_void_v<ResultingType>) {
// runtime has already been deleted. since this returns void, we can just ignore it being deleted.
Logger::log("JSIConverter", "Tried calling void(..) function, but it has already been deleted by JS!");
return;
} else {
// runtime has already been deleted, but we are expecting a return value - throw an error in this case.
throw std::runtime_error("Cannot call the given Function - the JS Dispatcher has already been destroyed by the JS Runtime!");
}
}

if constexpr (std::is_void_v<ResultingType>) {
// It returns void. Just call the function
function->call(runtime, JSIConverter<std::decay_t<Args>>::toJSI(runtime, args)...);
} else {
// It returns some kind of value - call the function, and convert the return value.
jsi::Value result = function->call(runtime, JSIConverter<std::decay_t<Args>>::toJSI(runtime, args)...);
return JSIConverter<ResultingType>::fromJSI(runtime, std::move(result));
}
}

template <size_t... Is>
static inline jsi::Value callHybridFunction(const std::function<ReturnType(Args...)>& function, jsi::Runtime& runtime,
const jsi::Value* args, std::index_sequence<Is...>) {
if constexpr (std::is_void_v<ReturnType>) {
// it is a void function (will return undefined in JS)
function(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, args[Is])...);
return jsi::Value::undefined();
} else {
// it is a custom type, parse it to a JS value
ReturnType result = function(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, args[Is])...);
return JSIConverter<ReturnType>::toJSI(runtime, result);
}
}
};

} // namespace margelo::nitro
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//
// Created by Marc Rousavy on 21.02.24.
//

#pragma once

// Forward declare a few of the common types that might have cyclic includes.
namespace margelo::nitro {
class HybridObject;

template <typename T, typename Enable>
struct JSIConverter;
} // namespace margelo::nitro

#include "JSIConverter.hpp"

#include "HybridObject.hpp"
#include "IsHostObject.hpp"
#include "IsNativeState.hpp"
#include "TypeInfo.hpp"
#include <memory>
#include <type_traits>

#define DO_NULL_CHECKS true

namespace margelo::nitro {

using namespace facebook;

// HybridObject <> {}
template <typename T>
struct JSIConverter<T, std::enable_if_t<is_shared_ptr_to_host_object_v<T>>> {
using TPointee = typename T::element_type;

static inline T fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
#if DO_NULL_CHECKS
if (arg.isUndefined()) [[unlikely]] {
throw jsi::JSError(runtime, invalidTypeErrorMessage("undefined", "It is undefined!"));
}
if (!arg.isObject()) [[unlikely]] {
std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is not an object!"));
}
#endif
jsi::Object object = arg.asObject(runtime);
#if DO_NULL_CHECKS
if (!object.isHostObject<TPointee>(runtime)) [[unlikely]] {
std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is a different HostObject<T>!"));
}
#endif
return object.asHostObject<TPointee>(runtime);
}

static inline jsi::Value toJSI(jsi::Runtime& runtime, const T& arg) {
#if DO_NULL_CHECKS
if (arg == nullptr) [[unlikely]] {
std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
throw jsi::JSError(runtime, "Cannot convert nullptr to HostObject<" + typeName + ">!");
}
#endif
if constexpr (std::is_base_of_v<HybridObject, TPointee>) {
// It's a HybridObject - use it's internal constructor which caches jsi::Objects for proper memory management!
return arg->toObject(runtime);
} else {
// It's any other kind of jsi::HostObject - just create it as normal.
return jsi::Object::createFromHostObject(runtime, arg);
}
}

private:
static inline std::string invalidTypeErrorMessage(const std::string& typeDescription, const std::string& reason) {
std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
return "Cannot convert \"" + typeDescription + "\" to HostObject<" + typeName + ">! " + reason;
}
};

// NativeState <> {}
template <typename T>
struct JSIConverter<T, std::enable_if_t<is_shared_ptr_to_native_state_v<T>>> {
using TPointee = typename T::element_type;

static inline T fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
#if DO_NULL_CHECKS
if (arg.isUndefined()) [[unlikely]] {
throw jsi::JSError(runtime, invalidTypeErrorMessage("undefined", "It is undefined!"));
}
if (!arg.isObject()) [[unlikely]] {
std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is not an object!"));
}
#endif
jsi::Object object = arg.asObject(runtime);
#if DO_NULL_CHECKS
if (!object.hasNativeState<TPointee>(runtime)) [[unlikely]] {
std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is a different NativeState<T>!"));
}
#endif
return object.getNativeState<TPointee>(runtime);
}

static inline jsi::Value toJSI(jsi::Runtime& runtime, const T& arg) {
#if DO_NULL_CHECKS
if (arg == nullptr) [[unlikely]] {
std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
throw jsi::JSError(runtime, "Cannot convert nullptr to NativeState<" + typeName + ">!");
}
#endif
jsi::Object object(runtime);
object.setNativeState(runtime, arg);
return object;
}

private:
static inline std::string invalidTypeErrorMessage(const std::string& typeDescription, const std::string& reason) {
std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
return "Cannot convert \"" + typeDescription + "\" to NativeState<" + typeName + ">! " + reason;
}
};

} // namespace margelo::nitro
Loading

0 comments on commit 9818b86

Please sign in to comment.