Files
react-native/ReactCommon/react/bridging/Function.h
T
Scott Kyle 30cb78e709 New bridging API for JSI <-> C++
Summary:
This adds  `bridging::toJs` and `bridging::fromJs` functions that will safely cast to and from JSI values and C++ types. This is extensible by specializing `Bridging<T>` with `toJs` and/or `fromJs` static methods. There are specializations for most common C++ and JSI types along with tests for those.

C++ functions and lambdas will effortlessly bridge into JS, and bridging JS functions back into C++ require you to choose `SyncCallback<R(Args...)>` or `AsyncCallback<Args...>` types. The sync version allows for having a return value and is strictly not movable to prevent accidentally moving onto another thread. The async version will move its args onto the JS thread and safely call the callback there, but hence always has a `void` return value.

For promises, you can construct a `AsyncPromise<T>` that has `resolve` and `reject` methods that can be called from any thread, and will bridge into JS as a regular `Promise`.

Changelog:
[General][Added] - New bridging API for JSI <-> C++

Reviewed By: christophpurrer

Differential Revision: D34607143

fbshipit-source-id: d832ac24cf84b4c1672a7b544d82e324d5fca3ef
2022-03-11 12:47:51 -08:00

215 lines
5.9 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/bridging/Base.h>
#include <react/bridging/CallbackWrapper.h>
#include <folly/Function.h>
namespace facebook::react {
template <typename F>
class SyncCallback;
template <typename... Args>
class AsyncCallback {
public:
AsyncCallback(
jsi::Runtime &runtime,
jsi::Function function,
std::shared_ptr<CallInvoker> jsInvoker)
: callback_(std::make_shared<SyncCallback<void(Args...)>>(
runtime,
std::move(function),
std::move(jsInvoker))) {}
AsyncCallback(const AsyncCallback &) = default;
AsyncCallback &operator=(const AsyncCallback &) = default;
void operator()(Args... args) const {
call(std::forward<Args>(args)...);
}
void call(Args... args) const {
auto wrapper = callback_->wrapper_.lock();
if (!wrapper) {
throw std::runtime_error("Failed to call invalidated async callback");
}
auto argsTuple = std::make_tuple(std::forward<Args>(args)...);
wrapper->jsInvoker().invokeAsync(
[callback = callback_,
argsPtr = std::make_shared<decltype(argsTuple)>(
std::move(argsTuple))] { callback->apply(std::move(*argsPtr)); });
}
private:
friend Bridging<AsyncCallback>;
std::shared_ptr<SyncCallback<void(Args...)>> callback_;
};
template <typename R, typename... Args>
class SyncCallback<R(Args...)> {
public:
SyncCallback(
jsi::Runtime &rt,
jsi::Function function,
std::shared_ptr<CallInvoker> jsInvoker)
: wrapper_(CallbackWrapper::createWeak(
std::move(function),
rt,
std::move(jsInvoker))) {}
// Disallow moving to prevent function from get called on another thread.
SyncCallback(SyncCallback &&) = delete;
SyncCallback &operator=(SyncCallback &&) = delete;
~SyncCallback() {
if (auto wrapper = wrapper_.lock()) {
wrapper->destroy();
}
}
R operator()(Args... args) const {
return call(std::forward<Args>(args)...);
}
R call(Args... args) const {
auto wrapper = wrapper_.lock();
if (!wrapper) {
throw std::runtime_error("Failed to call invalidated sync callback");
}
auto &callback = wrapper->callback();
auto &rt = wrapper->runtime();
auto jsInvoker = wrapper->jsInvokerPtr();
if constexpr (std::is_void_v<R>) {
callback.call(
rt, bridging::toJs(rt, std::forward<Args>(args), jsInvoker)...);
} else {
return bridging::fromJs<R>(
rt,
callback.call(
rt, bridging::toJs(rt, std::forward<Args>(args), jsInvoker)...),
jsInvoker);
}
}
private:
friend AsyncCallback<Args...>;
friend Bridging<SyncCallback>;
R apply(std::tuple<Args...> &&args) const {
return apply(std::move(args), std::index_sequence_for<Args...>{});
}
template <size_t... Index>
R apply(std::tuple<Args...> &&args, std::index_sequence<Index...>) const {
return call(std::move(std::get<Index>(args))...);
}
// Held weakly so lifetime is managed by LongLivedObjectCollection.
std::weak_ptr<CallbackWrapper> wrapper_;
};
template <typename... Args>
struct Bridging<AsyncCallback<Args...>> {
static AsyncCallback<Args...> fromJs(
jsi::Runtime &rt,
jsi::Function &&value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
return AsyncCallback<Args...>(rt, std::move(value), jsInvoker);
}
static jsi::Function toJs(
jsi::Runtime &rt,
const AsyncCallback<Args...> &value) {
return value.callback_->function_.getFunction(rt);
}
};
template <typename R, typename... Args>
struct Bridging<SyncCallback<R(Args...)>> {
static SyncCallback<R(Args...)> fromJs(
jsi::Runtime &rt,
jsi::Function &&value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
return SyncCallback<R(Args...)>(rt, std::move(value), jsInvoker);
}
static jsi::Function toJs(
jsi::Runtime &rt,
const SyncCallback<R(Args...)> &value) {
return value.function_.getFunction(rt);
}
};
template <typename R, typename... Args>
struct Bridging<folly::Function<R(Args...)>> {
using Func = folly::Function<R(Args...)>;
using IndexSequence = std::index_sequence_for<Args...>;
static constexpr size_t kArgumentCount = sizeof...(Args);
static jsi::Function toJs(
jsi::Runtime &rt,
Func fn,
const std::shared_ptr<CallInvoker> &jsInvoker) {
return jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "BridgedFunction"),
kArgumentCount,
[fn = std::make_shared<Func>(std::move(fn)), jsInvoker](
jsi::Runtime &rt,
const jsi::Value &,
const jsi::Value *args,
size_t count) -> jsi::Value {
if (count < kArgumentCount) {
throw jsi::JSError(rt, "Incorrect number of arguments");
}
if constexpr (std::is_void_v<R>) {
callFromJs(*fn, rt, args, jsInvoker, IndexSequence{});
return jsi::Value();
} else {
return bridging::toJs(
rt,
callFromJs(*fn, rt, args, jsInvoker, IndexSequence{}),
jsInvoker);
}
});
}
private:
template <size_t... Index>
static R callFromJs(
Func &fn,
jsi::Runtime &rt,
const jsi::Value *args,
const std::shared_ptr<CallInvoker> &jsInvoker,
std::index_sequence<Index...>) {
return fn(bridging::fromJs<Args>(rt, args[Index], jsInvoker)...);
}
};
template <typename R, typename... Args>
struct Bridging<std::function<R(Args...)>>
: Bridging<folly::Function<R(Args...)>> {};
template <typename R, typename... Args>
struct Bridging<R(Args...)> : Bridging<folly::Function<R(Args...)>> {};
template <typename R, typename... Args>
struct Bridging<R (*)(Args...)> : Bridging<folly::Function<R(Args...)>> {};
} // namespace facebook::react