From acc2ed2488761cd3d63839d56cbf95079c8cd551 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Wed, 26 Dec 2018 17:12:53 -0800 Subject: [PATCH] Moved TurboModule C++ core to github Summary: This is only the core C++ part of TurboModule - moving to github to make integration with existing NativeModules system slightly easier. Other bindings for iOS/Android are not yet ready to move. Notes: * TurboModules is not ready to use at the moment. * Build configuration is not yet provided (cocoapods/.xcodeproj/gradle), just like Fabric. * No effort was done to make this lib C++17 strictly compliant yet (there will be in the future). Reviewed By: RSNara Differential Revision: D13551211 fbshipit-source-id: cd3b458e6746ee9218451962ca65b1ad641a32db --- ReactCommon/turbomodule/core/BUCK | 46 ++++++ .../turbomodule/core/JSCallInvoker.cpp | 27 ++++ ReactCommon/turbomodule/core/JSCallInvoker.h | 38 +++++ .../turbomodule/core/LongLivedObject.cpp | 49 ++++++ .../turbomodule/core/LongLivedObject.h | 52 +++++++ .../turbomodule/core/TurboCxxModule.cpp | 147 ++++++++++++++++++ ReactCommon/turbomodule/core/TurboCxxModule.h | 44 ++++++ ReactCommon/turbomodule/core/TurboModule.cpp | 51 ++++++ ReactCommon/turbomodule/core/TurboModule.h | 84 ++++++++++ .../turbomodule/core/TurboModuleBinding.cpp | 72 +++++++++ .../turbomodule/core/TurboModuleBinding.h | 61 ++++++++ .../turbomodule/core/TurboModuleUtils.cpp | 98 ++++++++++++ .../turbomodule/core/TurboModuleUtils.h | 50 ++++++ 13 files changed, 819 insertions(+) create mode 100644 ReactCommon/turbomodule/core/BUCK create mode 100644 ReactCommon/turbomodule/core/JSCallInvoker.cpp create mode 100644 ReactCommon/turbomodule/core/JSCallInvoker.h create mode 100644 ReactCommon/turbomodule/core/LongLivedObject.cpp create mode 100644 ReactCommon/turbomodule/core/LongLivedObject.h create mode 100644 ReactCommon/turbomodule/core/TurboCxxModule.cpp create mode 100644 ReactCommon/turbomodule/core/TurboCxxModule.h create mode 100644 ReactCommon/turbomodule/core/TurboModule.cpp create mode 100644 ReactCommon/turbomodule/core/TurboModule.h create mode 100644 ReactCommon/turbomodule/core/TurboModuleBinding.cpp create mode 100644 ReactCommon/turbomodule/core/TurboModuleBinding.h create mode 100644 ReactCommon/turbomodule/core/TurboModuleUtils.cpp create mode 100644 ReactCommon/turbomodule/core/TurboModuleUtils.h diff --git a/ReactCommon/turbomodule/core/BUCK b/ReactCommon/turbomodule/core/BUCK new file mode 100644 index 000000000..b45099674 --- /dev/null +++ b/ReactCommon/turbomodule/core/BUCK @@ -0,0 +1,46 @@ +load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_debug_preprocessor_flags") +load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "get_apple_compiler_flags", "get_apple_inspector_flags", "react_native_xplat_target", "rn_xplat_cxx_library", "subdir_glob") + +APPLE_COMPILER_FLAGS = get_apple_compiler_flags() + +rn_xplat_cxx_library( + name = "core", + srcs = glob(["**/*.cpp"]), + header_namespace = "", + exported_headers = subdir_glob( + [ + ("", "**/*.h"), + ], + prefix = "jsireact", + ), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, + fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(), + force_static = True, + platforms = (ANDROID, APPLE), + preprocessor_flags = [ + "-DLOG_TAG=\"ReactNative\"", + "-DWITH_FBSYSTRACE=1", + ], + visibility = [ + "PUBLIC", + ], + deps = [ + "xplat//fbsystrace:fbsystrace", + "xplat//folly:headers_only", + "xplat//folly:memory", + "xplat//folly:molly", + "xplat//jsi:JSIDynamic", + "xplat//third-party/glog:glog", + react_native_xplat_target("cxxreact:bridge"), + react_native_xplat_target("cxxreact:module"), + ], + exported_deps = [ + "xplat//jsi:jsi", + ], +) diff --git a/ReactCommon/turbomodule/core/JSCallInvoker.cpp b/ReactCommon/turbomodule/core/JSCallInvoker.cpp new file mode 100644 index 000000000..ec28ce1c3 --- /dev/null +++ b/ReactCommon/turbomodule/core/JSCallInvoker.cpp @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "JSCallInvoker.h" + +#include + +namespace facebook { +namespace react { + +JSCallInvoker::JSCallInvoker(std::shared_ptr jsThread) + : jsThread_(jsThread) {} + +void JSCallInvoker::invokeAsync(std::function&& func) { + jsThread_->runOnQueue(std::move(func)); +} + +void JSCallInvoker::invokeSync(std::function&& func) { + jsThread_->runOnQueueSync(std::move(func)); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/JSCallInvoker.h b/ReactCommon/turbomodule/core/JSCallInvoker.h new file mode 100644 index 000000000..1f84bbc02 --- /dev/null +++ b/ReactCommon/turbomodule/core/JSCallInvoker.h @@ -0,0 +1,38 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +class MessageQueueThread; + +/** + * A generic native-to-JS call invoker. It guarantees that any calls from any + * thread are queued on the right JS thread. + * + * For now, this is a thin-wrapper around existing MessageQueueThread. Eventually, + * it should be consolidated with Fabric implementation so there's only one + * API to call JS from native, whether synchronously or asynchronously. + */ +class JSCallInvoker { +public: + JSCallInvoker(std::shared_ptr jsThread); + + void invokeAsync(std::function&& func); + void invokeSync(std::function&& func); + +private: + std::shared_ptr jsThread_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/LongLivedObject.cpp b/ReactCommon/turbomodule/core/LongLivedObject.cpp new file mode 100644 index 000000000..c320af72d --- /dev/null +++ b/ReactCommon/turbomodule/core/LongLivedObject.cpp @@ -0,0 +1,49 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "LongLivedObject.h" + +namespace facebook { +namespace react { + +// LongLivedObjectCollection +LongLivedObjectCollection& LongLivedObjectCollection::get() { + static LongLivedObjectCollection instance; + return instance; +} + +LongLivedObjectCollection::LongLivedObjectCollection() {} + +void LongLivedObjectCollection::add(std::shared_ptr so) { + collection_.insert(so); +} + +void LongLivedObjectCollection::remove(const LongLivedObject *o) { + auto p = collection_.begin(); + for (; p != collection_.end(); p++) { + if (p->get() == o) { + break; + } + } + if (p != collection_.end()) { + collection_.erase(p); + } +} + +void LongLivedObjectCollection::clear() { + collection_.clear(); +} + +// LongLivedObject +LongLivedObject::LongLivedObject() {} + +void LongLivedObject::allowRelease() { + LongLivedObjectCollection::get().remove(this); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/LongLivedObject.h b/ReactCommon/turbomodule/core/LongLivedObject.h new file mode 100644 index 000000000..be29af90b --- /dev/null +++ b/ReactCommon/turbomodule/core/LongLivedObject.h @@ -0,0 +1,52 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +/** + * A simple wrapper class that can be registered to a collection that keep it alive for extended period of time. + * This object can be removed from the collection when needed. + * + * The subclass of this class must be created using std::make_shared(). + * After creation, add it to the `LongLivedObjectCollection`. + * When done with the object, call `allowRelease()` to allow the OS to release it. + */ +class LongLivedObject { +public: + void allowRelease(); + +protected: + LongLivedObject(); +}; + +/** + * A singleton collection for the `LongLivedObject`s. + */ +class LongLivedObjectCollection { +public: + static LongLivedObjectCollection& get(); + + LongLivedObjectCollection(LongLivedObjectCollection const&) = delete; + void operator=(LongLivedObjectCollection const&) = delete; + + void add(std::shared_ptr o); + void remove(const LongLivedObject *o); + void clear(); + +private: + LongLivedObjectCollection(); + std::unordered_set> collection_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/TurboCxxModule.cpp b/ReactCommon/turbomodule/core/TurboCxxModule.cpp new file mode 100644 index 000000000..266fa2b07 --- /dev/null +++ b/ReactCommon/turbomodule/core/TurboCxxModule.cpp @@ -0,0 +1,147 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "TurboCxxModule.h" + +#include + +#include +#include + +using namespace facebook; +using namespace facebook::xplat::module; + +namespace facebook { +namespace react { + +static CxxModule::Callback makeTurboCxxModuleCallback( + jsi::Runtime &runtime, + std::shared_ptr callbackWrapper) { + return [callbackWrapper](std::vector args) { + callbackWrapper->jsInvoker->invokeAsync([callbackWrapper, args]() { + std::vector innerArgs; + for (auto &a : args) { + innerArgs.push_back(jsi::valueFromDynamic(callbackWrapper->runtime, a)); + } + callbackWrapper->callback.call(callbackWrapper->runtime, (const jsi::Value *)innerArgs.data(), innerArgs.size()); + }); + }; +} + +TurboCxxModule::TurboCxxModule(std::unique_ptr cxxModule, std::shared_ptr jsInvoker) + : TurboModule(cxxModule->getName(), jsInvoker), + cxxMethods_(cxxModule->getMethods()), + cxxModule_(std::move(cxxModule)) {} + +jsi::Value TurboCxxModule::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) { + std::string propNameUtf8 = propName.utf8(runtime); + + if (propNameUtf8 == "getConstants") { + // This is special cased because `getConstants()` is already a part of CxxModule. + return jsi::Function::createFromHostFunction( + runtime, + propName, + 0, + [this](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) { + jsi::Object result(rt); + auto constants = cxxModule_->getConstants(); + for (auto &pair : constants) { + result.setProperty(rt, pair.first.c_str(), jsi::valueFromDynamic(rt, pair.second)); + } + return result; + }); + } + + for (auto &method : cxxMethods_) { + if (method.name == propNameUtf8) { + return jsi::Function::createFromHostFunction( + runtime, + propName, + 0, + [this, &propNameUtf8](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) { + return invokeMethod(rt, VoidKind, propNameUtf8, args, count); + }); + } + } + + throw std::runtime_error("Function '" + propNameUtf8 + "' cannot be found on cxxmodule: " + name_); +} + +jsi::Value TurboCxxModule::invokeMethod( + jsi::Runtime &runtime, + TurboModuleMethodValueKind valueKind, + const std::string &methodName, + const jsi::Value *args, + size_t count) { + + auto it = cxxMethods_.begin(); + for (; it != cxxMethods_.end(); it++) { + auto method = *it; + if (method.name == methodName) { + break; + } + } + + if (it == cxxMethods_.end()) { + throw std::runtime_error("Function '" + methodName + "' cannot be found on cxxmodule: " + name_); + } + + auto method = *it; + + if (method.syncFunc) { + auto innerArgs = folly::dynamic::array(); + for (size_t i = 0; i < count; i++) { + innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i])); + } + return jsi::valueFromDynamic(runtime, method.syncFunc(std::move(innerArgs))); + } else if (method.func && !method.isPromise) { + // Async method. + CxxModule::Callback first; + CxxModule::Callback second; + + if (count < method.callbacks) { + throw std::invalid_argument(folly::to("Expected ", method.callbacks, + " callbacks, but only ", count, " parameters provided")); + } + + if (method.callbacks == 1) { + auto wrapper = std::make_shared(args[count - 1].getObject(runtime).getFunction(runtime), runtime, jsInvoker_); + first = makeTurboCxxModuleCallback(runtime, wrapper); + } else if (method.callbacks == 2) { + auto wrapper1 = std::make_shared(args[count - 2].getObject(runtime).getFunction(runtime), runtime, jsInvoker_); + auto wrapper2 = std::make_shared(args[count - 1].getObject(runtime).getFunction(runtime), runtime, jsInvoker_); + first = makeTurboCxxModuleCallback(runtime, wrapper1); + second = makeTurboCxxModuleCallback(runtime, wrapper2); + } + + auto innerArgs = folly::dynamic::array(); + for (size_t i = 0; i < count - method.callbacks; i++) { + innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i])); + } + + method.func(std::move(innerArgs), first, second); + } else if (method.isPromise) { + return createPromiseAsJSIValue(runtime, [method, args, count, this](jsi::Runtime &rt, std::shared_ptr promise) { + auto resolveWrapper = std::make_shared(promise->resolve_.getFunction(rt), rt, jsInvoker_); + auto rejectWrapper = std::make_shared(promise->reject_.getFunction(rt), rt, jsInvoker_); + CxxModule::Callback resolve = makeTurboCxxModuleCallback(rt, resolveWrapper); + CxxModule::Callback reject = makeTurboCxxModuleCallback(rt, rejectWrapper); + + auto innerArgs = folly::dynamic::array(); + for (size_t i = 0; i < count; i++) { + innerArgs.push_back(jsi::dynamicFromValue(rt, args[i])); + } + + method.func(std::move(innerArgs), resolve, reject); + }); + } + + return jsi::Value::undefined(); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/TurboCxxModule.h b/ReactCommon/turbomodule/core/TurboCxxModule.h new file mode 100644 index 000000000..c44ba2a9d --- /dev/null +++ b/ReactCommon/turbomodule/core/TurboCxxModule.h @@ -0,0 +1,44 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include + +#include "TurboModule.h" + +namespace facebook { +namespace react { + +/** + * A helper class to convert the legacy CxxModule instance to a TurboModule instance. + * This should be used only for migration purpose (to TurboModule), since it's not very performant + * due to a lot of back-and-forth value conversions between folly::dynamic and jsi::Value. + */ +class JSI_EXPORT TurboCxxModule : public TurboModule { +public: + TurboCxxModule(std::unique_ptr cxxModule, std::shared_ptr jsInvoker); + + virtual facebook::jsi::Value get(facebook::jsi::Runtime& runtime, const facebook::jsi::PropNameID& propName) override; + + virtual jsi::Value invokeMethod( + jsi::Runtime &runtime, + TurboModuleMethodValueKind valueKind, + const std::string &methodName, + const jsi::Value *args, + size_t count) override; + +private: + std::vector cxxMethods_; + std::unique_ptr cxxModule_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/TurboModule.cpp b/ReactCommon/turbomodule/core/TurboModule.cpp new file mode 100644 index 000000000..06dbdaa6a --- /dev/null +++ b/ReactCommon/turbomodule/core/TurboModule.cpp @@ -0,0 +1,51 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "TurboModule.h" + +using namespace facebook; + +namespace facebook { +namespace react { + +TurboModule::TurboModule(const std::string &name, std::shared_ptr jsInvoker) + : name_(name), + jsInvoker_(jsInvoker) {} + +TurboModule::~TurboModule() { + invalidate(); +} + +void TurboModule::invalidate() {} + +jsi::Value TurboModule::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) { + std::string propNameUtf8 = propName.utf8(runtime); + auto p = methodMap_.find(propNameUtf8); + if (p == methodMap_.end()) { + throw std::runtime_error("Function '" + propNameUtf8 + "' cannot be found on module: " + name_); + } + MethodMetadata meta = p->second; + return jsi::Function::createFromHostFunction( + runtime, + propName, + meta.argCount, + [this, meta](facebook::jsi::Runtime &rt, const facebook::jsi::Value &thisVal, const facebook::jsi::Value *args, size_t count) { + return meta.invoker(rt, *this, args, count); + }); +} + +jsi::Value TurboModule::invokeMethod( + jsi::Runtime &runtime, + TurboModuleMethodValueKind valueKind, + const std::string &methodName, + const jsi::Value *args, + size_t count) { + return jsi::Value::undefined(); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/TurboModule.h b/ReactCommon/turbomodule/core/TurboModule.h new file mode 100644 index 000000000..d1691edfd --- /dev/null +++ b/ReactCommon/turbomodule/core/TurboModule.h @@ -0,0 +1,84 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include + +#include "JSCallInvoker.h" + +namespace facebook { +namespace react { + +/** + * For now, support the same set of return types as existing impl. + * This can be improved to support richer typed objects. + */ +enum TurboModuleMethodValueKind { + VoidKind, + BooleanKind, + NumberKind, + StringKind, + ObjectKind, + ArrayKind, + FunctionKind, + PromiseKind, +}; + +/** + * Base HostObject class for every module to be exposed to JS + */ +class JSI_EXPORT TurboModule : public facebook::jsi::HostObject { +public: + TurboModule(const std::string &name, std::shared_ptr jsInvoker); + virtual ~TurboModule(); + + /** + * Instruct this module to invalidate itself. + */ + virtual void invalidate(); + + virtual facebook::jsi::Value get(facebook::jsi::Runtime& runtime, const facebook::jsi::PropNameID& propName) override; + + /** + * General method invocation mechanism. + * Each subclass decides how the invocation should be, and whether it should be platform-specific. + */ + virtual jsi::Value invokeMethod( + jsi::Runtime &runtime, + TurboModuleMethodValueKind valueKind, + const std::string &methodName, + const jsi::Value *args, + size_t count); + + const std::string name_; + std::shared_ptr jsInvoker_; + +protected: + struct MethodMetadata { + size_t argCount; + facebook::jsi::Value (*invoker)( + facebook::jsi::Runtime& rt, + TurboModule &turboModule, + const facebook::jsi::Value* args, + size_t count); + }; + + std::unordered_map methodMap_; +}; + +/** + * An app/platform-specific provider function to get an instance of a module given a name. + */ +using TurboModuleProviderFunctionType = std::function( + const std::string &name)>; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/TurboModuleBinding.cpp b/ReactCommon/turbomodule/core/TurboModuleBinding.cpp new file mode 100644 index 000000000..4e93736b5 --- /dev/null +++ b/ReactCommon/turbomodule/core/TurboModuleBinding.cpp @@ -0,0 +1,72 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "TurboModuleBinding.h" + +#include + +#include + +using namespace facebook; + +namespace facebook { +namespace react { + +/** + * Public API to install the TurboModule system. + */ +TurboModuleBinding::TurboModuleBinding(const TurboModuleProviderFunctionType &moduleProvider) + : moduleProvider_(moduleProvider) {} + +void TurboModuleBinding::install( + jsi::Runtime &runtime, + std::shared_ptr binding) { + runtime.global().setProperty( + runtime, + "__turboModuleProxy", + jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii(runtime, "__turboModuleProxy"), + 1, + [binding](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) { + return binding->jsProxy(rt, thisVal, args, count); + })); +} + +void TurboModuleBinding::invalidate() const { + // Nothing for now. +} + +std::shared_ptr TurboModuleBinding::getModule(const std::string &name) { + std::shared_ptr module = nullptr; + { + SystraceSection s("TurboModuleBinding::getModule", "module", name); + module = moduleProvider_(name); + } + return module; +} + +jsi::Value TurboModuleBinding::jsProxy( + jsi::Runtime& runtime, + const jsi::Value& thisVal, + const jsi::Value* args, + size_t count) { + if (count != 1) { + throw std::invalid_argument("TurboModuleBinding::jsProxy arg count must be 1"); + } + std::string moduleName = args[0].getString(runtime).utf8(runtime); + std::shared_ptr module = getModule(moduleName); + + if (module == nullptr) { + return jsi::Value::null(); + } + + return jsi::Object::createFromHostObject(runtime, std::move(module)); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/TurboModuleBinding.h b/ReactCommon/turbomodule/core/TurboModuleBinding.h new file mode 100644 index 000000000..6195dcf39 --- /dev/null +++ b/ReactCommon/turbomodule/core/TurboModuleBinding.h @@ -0,0 +1,61 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include + +namespace facebook { +namespace react { + +class JSCallInvoker; + +/** + * Represents the JavaScript binding for the TurboModule system. + */ +class TurboModuleBinding { +public: + /* + * Installs TurboModuleBinding into JavaScript runtime. + * Thread synchronization must be enforced externally. + */ + static void install( + jsi::Runtime &runtime, + std::shared_ptr binding); + + TurboModuleBinding(const TurboModuleProviderFunctionType &moduleProvider); + + /* + * Invalidates the binding. + * Can be called in any thread. + */ + void invalidate() const; + + /** + * Get an TurboModule instance for the given module name. + */ + std::shared_ptr getModule(const std::string &name); + +private: + /** + * A lookup function exposed to JS to get an instance of a TurboModule + * for the given name. + */ + jsi::Value jsProxy( + jsi::Runtime& runtime, + const jsi::Value& thisVal, + const jsi::Value* args, + size_t count); + + TurboModuleProviderFunctionType moduleProvider_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/TurboModuleUtils.cpp b/ReactCommon/turbomodule/core/TurboModuleUtils.cpp new file mode 100644 index 000000000..f7f53b3b8 --- /dev/null +++ b/ReactCommon/turbomodule/core/TurboModuleUtils.cpp @@ -0,0 +1,98 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "TurboModuleUtils.h" + +namespace facebook { +namespace react { + +static jsi::Value deepCopyJSIValue(jsi::Runtime &rt, const jsi::Value &value) { + if (value.isNull()) { + return jsi::Value::null(); + } + + if (value.isBool()) { + return jsi::Value(value.getBool()); + } + + if (value.isNumber()) { + return jsi::Value(value.getNumber()); + } + + if (value.isString()) { + return value.getString(rt); + } + + if (value.isObject()) { + jsi::Object o = value.getObject(rt); + if (o.isArray(rt)) { + return deepCopyJSIArray(rt, o.getArray(rt)); + } + if (o.isFunction(rt)) { + return o.getFunction(rt); + } + return deepCopyJSIObject(rt, o); + } + + return jsi::Value::undefined(); +} + +jsi::Object deepCopyJSIObject(jsi::Runtime &rt, const jsi::Object &obj) { + jsi::Object copy(rt); + jsi::Array propertyNames = obj.getPropertyNames(rt); + size_t size = propertyNames.size(rt); + for (size_t i = 0; i < size; i++) { + jsi::String name = propertyNames.getValueAtIndex(rt, i).getString(rt); + jsi::Value value = obj.getProperty(rt, name); + copy.setProperty(rt, name, deepCopyJSIValue(rt, value)); + } + return copy; +} + +jsi::Array deepCopyJSIArray(jsi::Runtime &rt, const jsi::Array &arr) { + size_t size = arr.size(rt); + jsi::Array copy(rt, size); + for (size_t i = 0; i < size; i++) { + copy.setValueAtIndex(rt, i, deepCopyJSIValue(rt, arr.getValueAtIndex(rt, i))); + } + return copy; +} + +Promise::Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject) + : runtime_(rt), + resolve_(std::move(resolve)), + reject_(std::move(reject)) {} + +void Promise::resolve(const jsi::Value &result) { + resolve_.call(runtime_, result); +} + +void Promise::reject(const std::string &message) { + jsi::Object error(runtime_); + error.setProperty(runtime_, "message", jsi::String::createFromUtf8(runtime_, message)); + reject_.call(runtime_, error); +} + +jsi::Value createPromiseAsJSIValue(jsi::Runtime &rt, const PromiseSetupFunctionType func) { + jsi::Function JSPromise = rt.global().getPropertyAsFunction(rt, "Promise"); + jsi::Function fn = jsi::Function::createFromHostFunction( + rt, + jsi::PropNameID::forAscii(rt, "fn"), + 2, + [func](jsi::Runtime &rt2, const jsi::Value &thisVal, const jsi::Value *args, size_t count) { + jsi::Function resolve = args[0].getObject(rt2).getFunction(rt2); + jsi::Function reject = args[1].getObject(rt2).getFunction(rt2); + auto wrapper = std::make_shared(rt2, std::move(resolve), std::move(reject)); + func(rt2, wrapper); + return jsi::Value::undefined(); + }); + + return JSPromise.callAsConstructor(rt, fn); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/TurboModuleUtils.h b/ReactCommon/turbomodule/core/TurboModuleUtils.h new file mode 100644 index 000000000..4a71f8e73 --- /dev/null +++ b/ReactCommon/turbomodule/core/TurboModuleUtils.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include + +#include "JSCallInvoker.h" + +using namespace facebook; + +namespace facebook { +namespace react { + +jsi::Object deepCopyJSIObject(jsi::Runtime &rt, const jsi::Object &obj); +jsi::Array deepCopyJSIArray(jsi::Runtime &rt, const jsi::Array &arr); + +struct Promise { + Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject); + + void resolve(const jsi::Value &result); + void reject(const std::string &error); + + jsi::Runtime &runtime_; + jsi::Function resolve_; + jsi::Function reject_; +}; + +using PromiseSetupFunctionType = std::function)>; +jsi::Value createPromiseAsJSIValue(jsi::Runtime &rt, const PromiseSetupFunctionType func); + +// Helper for passing jsi::Function arg to other methods. +struct CallbackWrapper { + CallbackWrapper(jsi::Function callback, jsi::Runtime &runtime, std::shared_ptr jsInvoker) + : callback(std::move(callback)), + runtime(runtime), + jsInvoker(jsInvoker) {} + jsi::Function callback; + jsi::Runtime &runtime; + std::shared_ptr jsInvoker; +}; + +} // namespace react +} // namespace facebook