diff --git a/packages/react-native-nitro-modules/NitroModules.podspec b/packages/react-native-nitro-modules/NitroModules.podspec index 34626abae..6a83619f1 100644 --- a/packages/react-native-nitro-modules/NitroModules.podspec +++ b/packages/react-native-nitro-modules/NitroModules.podspec @@ -28,6 +28,8 @@ Pod::Spec.new do |s| ] s.pod_target_xcconfig = { + # Use C++ 17 + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", # Enables C++ <-> Swift interop (by default it's only C) "SWIFT_OBJC_INTEROP_MODE" => "objcxx", } diff --git a/packages/react-native-nitro-modules/cpp/core/HybridObject.cpp b/packages/react-native-nitro-modules/cpp/core/HybridObject.cpp index db44065c6..526a4da86 100644 --- a/packages/react-native-nitro-modules/cpp/core/HybridObject.cpp +++ b/packages/react-native-nitro-modules/cpp/core/HybridObject.cpp @@ -72,18 +72,19 @@ jsi::Value HybridObject::get(facebook::jsi::Runtime& runtime, const facebook::js std::string name = propName.utf8(runtime); auto& functionCache = _functionCache[&runtime]; - - if (_getters.count(name) > 0) { - // it's a property getter - return _getters[name](runtime, jsi::Value::undefined(), nullptr, 0); - } - + + if (functionCache.count(name) > 0) { [[likely]]; // cache hit return jsi::Value(runtime, *functionCache[name]); } + if (_getters.count(name) > 0) { + // it's a property getter + return _getters[name](runtime, jsi::Value::undefined(), nullptr, 0); + } + if (_methods.count(name) > 0) { // cache miss - create jsi::Function and cache it. HybridFunction& hybridFunction = _methods.at(name); @@ -124,16 +125,10 @@ void HybridObject::set(facebook::jsi::Runtime& runtime, const facebook::jsi::Pro void HybridObject::ensureInitialized(facebook::jsi::Runtime& runtime) { if (!_didLoadMethods) { [[unlikely]]; - _creationRuntime = &runtime; // lazy-load all exposed methods loadHybridMethods(); _didLoadMethods = true; } } -bool HybridObject::isRuntimeAlive() { - // TODO: Fix this. We shouldn't do this. This is a bad idea. - return true; -} - } // namespace margelo diff --git a/packages/react-native-nitro-modules/cpp/core/HybridObject.hpp b/packages/react-native-nitro-modules/cpp/core/HybridObject.hpp index 9fe7b7936..29066c05b 100644 --- a/packages/react-native-nitro-modules/cpp/core/HybridObject.hpp +++ b/packages/react-native-nitro-modules/cpp/core/HybridObject.hpp @@ -70,9 +70,7 @@ class HybridObject : public jsi::HostObject, public std::enable_shared_from_this std::unordered_map _methods; std::unordered_map _getters; std::unordered_map _setters; - std::unordered_map>> _functionCache; - // Store a pointer to the runtime. Needed for checking if the runtime is still active, see WorkletRuntimeRegistry. - jsi::Runtime* _creationRuntime = nullptr; + std::unordered_map>> _functionCache; private: inline void ensureInitialized(facebook::jsi::Runtime& runtime); @@ -149,8 +147,6 @@ class HybridObject : public jsi::HostObject, public std::enable_shared_from_this _setters[name] = createHybridMethod(method, derivedInstance); } - - bool isRuntimeAlive(); }; } // namespace margelo diff --git a/packages/react-native-nitro-modules/cpp/jsi/FunctionCache.cpp b/packages/react-native-nitro-modules/cpp/jsi/FunctionCache.cpp index 33bc2f62c..c3031d4bd 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/FunctionCache.cpp +++ b/packages/react-native-nitro-modules/cpp/jsi/FunctionCache.cpp @@ -16,21 +16,24 @@ static constexpr auto CACHE_PROP_NAME = "__nitroModulesFunctionCache"; FunctionCache::FunctionCache(jsi::Runtime* runtime): _runtime(runtime) {} std::weak_ptr FunctionCache::getOrCreateCache(jsi::Runtime &runtime) { - if (runtime.global().hasProperty(runtime, CACHE_PROP_NAME)) { - // A cache already exists for the given runtime - get it and return a weak ref to it. - Logger::log(TAG, "Runtime %i already has a cache, getting it..", getRuntimeId(runtime)); - auto cache = runtime.global().getPropertyAsObject(runtime, CACHE_PROP_NAME); - auto nativeState = cache.getNativeState(runtime); - return std::weak_ptr(nativeState); + if (_globalCache.contains(&runtime)) { + // Fast path: get weak_ptr to FunctionCache from our global list. + return _globalCache[&runtime]; } - // Cache doesn't exist yet - create one, inject it into global, and return a weak ref. + // Cache doesn't exist yet. Logger::log(TAG, "Creating new FunctionCache for runtime %i..", getRuntimeId(runtime)); + // Create new cache auto nativeState = std::make_shared(&runtime); + // Wrap it in a jsi::Value using NativeState jsi::Object cache(runtime); cache.setNativeState(runtime, nativeState); + // Inject it into the jsi::Runtime's global so it's memory is managed by it runtime.global().setProperty(runtime, CACHE_PROP_NAME, std::move(cache)); - return std::weak_ptr(nativeState); + // Add it to our map of caches + _globalCache[&runtime] = nativeState; + // Return it + return nativeState; } std::weak_ptr FunctionCache::makeGlobal(jsi::Function&& function) { diff --git a/packages/react-native-nitro-modules/cpp/jsi/FunctionCache.hpp b/packages/react-native-nitro-modules/cpp/jsi/FunctionCache.hpp index baf17a4f9..42013ffdb 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/FunctionCache.hpp +++ b/packages/react-native-nitro-modules/cpp/jsi/FunctionCache.hpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace margelo { @@ -47,6 +48,11 @@ class FunctionCache: public jsi::NativeState { private: jsi::Runtime* _runtime; std::vector> _cache; + +private: + static std::unordered_map> _globalCache; + +private: static constexpr auto TAG = "FunctionCache"; }; diff --git a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter.hpp b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter.hpp index 3b849495a..b3451c43a 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter.hpp +++ b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter.hpp @@ -7,7 +7,6 @@ #include "EnumMapper.hpp" #include "HybridObject.hpp" #include "Promise.hpp" -#include "PromiseFactory.hpp" #include "Dispatcher.hpp" #include "FunctionCache.hpp" #include diff --git a/packages/react-native-nitro-modules/cpp/react-native-nitro.cpp b/packages/react-native-nitro-modules/cpp/react-native-nitro.cpp index 68a174174..a1c6042da 100644 --- a/packages/react-native-nitro-modules/cpp/react-native-nitro.cpp +++ b/packages/react-native-nitro-modules/cpp/react-native-nitro.cpp @@ -1,12 +1,9 @@ #include "react-native-nitro.hpp" -#include -#include "NitroModules-Swift.h" - namespace nitro { - double multiply(double a, double b) { - auto exampleClass = NitroModules::HybridObject::init("Heyo!"); - return a * b; - } +std::shared_ptr createTestHybridObject() { + return std::make_shared(); +} + } diff --git a/packages/react-native-nitro-modules/cpp/react-native-nitro.hpp b/packages/react-native-nitro-modules/cpp/react-native-nitro.hpp index 4d150e3c5..a337b0429 100644 --- a/packages/react-native-nitro-modules/cpp/react-native-nitro.hpp +++ b/packages/react-native-nitro-modules/cpp/react-native-nitro.hpp @@ -1,8 +1,11 @@ #ifndef NITRO_H #define NITRO_H +#include "test-object/TestHybridObject.hpp" + namespace nitro { - double multiply(double a, double b); + + std::shared_ptr createTestHybridObject(); } #endif /* NITRO_H */ diff --git a/packages/react-native-nitro-modules/cpp/test-object/TestHybridObject.cpp b/packages/react-native-nitro-modules/cpp/test-object/TestHybridObject.cpp new file mode 100644 index 000000000..b202b2541 --- /dev/null +++ b/packages/react-native-nitro-modules/cpp/test-object/TestHybridObject.cpp @@ -0,0 +1,34 @@ +// +// Created by Marc Rousavy on 20.02.24. +// + +#include "TestHybridObject.hpp" + +namespace margelo { + +void TestHybridObject::loadHybridMethods() { + // this.int get & set + registerHybridGetter("int", &TestHybridObject::getInt, this); + registerHybridSetter("int", &TestHybridObject::setInt, this); + // this.string get & set + registerHybridGetter("string", &TestHybridObject::getString, this); + registerHybridSetter("string", &TestHybridObject::setString, this); + // this.nullableString get & set + registerHybridGetter("nullableString", &TestHybridObject::getNullableString, this); + registerHybridSetter("nullableString", &TestHybridObject::setNullableString, this); + // this.enum + registerHybridGetter("enum", &TestHybridObject::getEnum, this); + registerHybridSetter("enum", &TestHybridObject::setEnum, this); + // methods + registerHybridMethod("multipleArguments", &TestHybridObject::multipleArguments, this); + // callbacks + registerHybridMethod("getIntGetter", &TestHybridObject::getIntGetter, this); + registerHybridMethod("sayHelloCallback", &TestHybridObject::sayHelloCallback, this); + // custom types + registerHybridMethod("createNewHybridObject", &TestHybridObject::createNewHybridObject, this); + // Promises + registerHybridMethod("calculateFibonacci", &TestHybridObject::calculateFibonacci, this); + registerHybridMethod("calculateFibonacciAsync", &TestHybridObject::calculateFibonacciAsync, this); +} + +} // namespace margelo diff --git a/packages/react-native-nitro-modules/cpp/test-object/TestHybridObject.hpp b/packages/react-native-nitro-modules/cpp/test-object/TestHybridObject.hpp new file mode 100644 index 000000000..61b5772ec --- /dev/null +++ b/packages/react-native-nitro-modules/cpp/test-object/TestHybridObject.hpp @@ -0,0 +1,92 @@ +// +// Created by Marc Rousavy on 22.02.24. +// + +#pragma once + +#include "HybridObject.hpp" +#include +#include +#include + +namespace margelo { + +class TestHybridObject : public HybridObject { +public: + explicit TestHybridObject() : HybridObject("TestHybridObject") {} + +public: + int getInt() { + return _int; + } + void setInt(int newValue) { + _int = newValue; + } + std::string getString() { + return _string; + } + void setString(const std::string& newValue) { + _string = newValue; + } + void setEnum(TestEnum testEnum) { + _enum = testEnum; + } + TestEnum getEnum() { + return _enum; + } + std::optional getNullableString() { + return _nullableString; + } + void setNullableString(std::optional string) { + _nullableString = string; + } + + std::unordered_map multipleArguments(int first, bool second, std::string third) { + return std::unordered_map{{"first", 5312}, {"second", 532233}, {"third", 2786}}; + } + + std::function getIntGetter() { + return [this]() -> int { return this->_int; }; + } + void sayHelloCallback(std::function callback) { + callback("Test Hybrid"); + } + std::shared_ptr createNewHybridObject() { + return std::make_shared(); + } + + uint64_t calculateFibonacci(int count) { + if (count < 0) + throw std::invalid_argument("Cannot calculate fibonacci for " + std::to_string(count) + " - it needs to be at least 0!"); + if (count == 0) + return 0; + if (count == 1) + return 1; + if (count >= 94) + throw std::invalid_argument("Cannot calculate fibonacci for " + std::to_string(count) + + " - it needs to be 94 at max, the number will overflow!"); + + uint64_t prev = 0; + uint64_t current = 1; + for (unsigned int i = 2; i <= count; ++i) { + uint64_t next = prev + current; + prev = current; + current = next; + } + return current; + } + + std::future calculateFibonacciAsync(int count) { + return std::async(std::launch::async, [count, this]() { return this->calculateFibonacci(count); }); + } + +private: + int _int; + std::string _string; + TestEnum _enum; + std::optional _nullableString; + + void loadHybridMethods() override; +}; + +} // namespace margelo