Skip to content

Commit 30cb78e

Browse files
appdenfacebook-github-bot
authored andcommitted
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
1 parent 6a9497d commit 30cb78e

18 files changed

+1469
-1
lines changed

ReactCommon/react/bridging/Array.h

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/bridging/Base.h>
11+
12+
#include <array>
13+
#include <deque>
14+
#include <initializer_list>
15+
#include <list>
16+
#include <tuple>
17+
#include <utility>
18+
#include <vector>
19+
20+
namespace facebook::react {
21+
22+
namespace array_detail {
23+
24+
template <typename T, size_t N>
25+
struct BridgingStatic {
26+
static jsi::Array toJs(
27+
jsi::Runtime &rt,
28+
const T &array,
29+
const std::shared_ptr<CallInvoker> &jsInvoker) {
30+
return toJs(rt, array, jsInvoker, std::make_index_sequence<N>{});
31+
}
32+
33+
private:
34+
template <size_t... Index>
35+
static jsi::Array toJs(
36+
facebook::jsi::Runtime &rt,
37+
const T &array,
38+
const std::shared_ptr<CallInvoker> &jsInvoker,
39+
std::index_sequence<Index...>) {
40+
return jsi::Array::createWithElements(
41+
rt, bridging::toJs(rt, std::get<Index>(array), jsInvoker)...);
42+
}
43+
};
44+
45+
template <typename T>
46+
struct BridgingDynamic {
47+
static jsi::Array toJs(
48+
jsi::Runtime &rt,
49+
const T &list,
50+
const std::shared_ptr<CallInvoker> &jsInvoker) {
51+
jsi::Array result(rt, list.size());
52+
size_t index = 0;
53+
54+
for (const auto &item : list) {
55+
result.setValueAtIndex(rt, index++, bridging::toJs(rt, item, jsInvoker));
56+
}
57+
58+
return result;
59+
}
60+
};
61+
62+
} // namespace array_detail
63+
64+
template <typename T, size_t N>
65+
struct Bridging<std::array<T, N>>
66+
: array_detail::BridgingStatic<std::array<T, N>, N> {};
67+
68+
template <typename T1, typename T2>
69+
struct Bridging<std::pair<T1, T2>>
70+
: array_detail::BridgingStatic<std::pair<T1, T2>, 2> {};
71+
72+
template <typename... Types>
73+
struct Bridging<std::tuple<Types...>>
74+
: array_detail::BridgingStatic<std::tuple<Types...>, sizeof...(Types)> {};
75+
76+
template <typename T>
77+
struct Bridging<std::deque<T>> : array_detail::BridgingDynamic<std::deque<T>> {
78+
};
79+
80+
template <typename T>
81+
struct Bridging<std::initializer_list<T>>
82+
: array_detail::BridgingDynamic<std::initializer_list<T>> {};
83+
84+
template <typename T>
85+
struct Bridging<std::list<T>> : array_detail::BridgingDynamic<std::list<T>> {};
86+
87+
template <typename T>
88+
struct Bridging<std::vector<T>>
89+
: array_detail::BridgingDynamic<std::vector<T>> {
90+
static std::vector<T> fromJs(
91+
facebook::jsi::Runtime &rt,
92+
const jsi::Array &array,
93+
const std::shared_ptr<CallInvoker> &jsInvoker) {
94+
size_t length = array.length(rt);
95+
96+
std::vector<T> vector;
97+
vector.reserve(length);
98+
99+
for (size_t i = 0; i < length; i++) {
100+
vector.push_back(
101+
bridging::fromJs<T>(rt, array.getValueAtIndex(rt, i), jsInvoker));
102+
}
103+
104+
return vector;
105+
}
106+
};
107+
108+
} // namespace facebook::react

ReactCommon/react/bridging/BUCK

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "CXX", "react_native_xplat_shared_library_target", "react_native_xplat_target", "rn_xplat_cxx_library")
1+
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "CXX", "IOS", "MACOSX", "fb_xplat_cxx_test", "react_native_xplat_shared_library_target", "react_native_xplat_target", "rn_xplat_cxx_library")
22

33
rn_xplat_cxx_library(
44
name = "bridging",
55
srcs = glob(["*.cpp"]),
66
header_namespace = "react/bridging",
77
exported_headers = glob(["*.h"]),
8+
compiler_flags_enable_exceptions = True,
9+
compiler_flags_enable_rtti = True,
810
labels = ["supermodule:xplat/default/public.react_native.infra"],
911
platforms = (ANDROID, APPLE, CXX),
12+
tests = [":tests"],
1013
visibility = ["PUBLIC"],
1114
deps = [
1215
"//xplat/folly:headers_only",
@@ -17,3 +20,34 @@ rn_xplat_cxx_library(
1720
react_native_xplat_shared_library_target("jsi:jsi"),
1821
],
1922
)
23+
24+
rn_xplat_cxx_library(
25+
name = "testlib",
26+
header_namespace = "react/bridging",
27+
exported_headers = glob(["tests/*.h"]),
28+
platforms = (ANDROID, APPLE, CXX),
29+
visibility = ["PUBLIC"],
30+
exported_deps = [
31+
"//xplat/third-party/gmock:gtest",
32+
],
33+
)
34+
35+
fb_xplat_cxx_test(
36+
name = "tests",
37+
srcs = glob(["tests/*.cpp"]),
38+
headers = glob(["tests/*.h"]),
39+
apple_sdks = (IOS, MACOSX),
40+
compiler_flags = [
41+
"-fexceptions",
42+
"-frtti",
43+
"-std=c++17",
44+
"-Wall",
45+
],
46+
contacts = ["[email protected]"],
47+
platforms = (ANDROID, APPLE, CXX),
48+
deps = [
49+
":bridging",
50+
"//xplat/hermes/API:HermesAPI",
51+
"//xplat/third-party/gmock:gtest",
52+
],
53+
)

ReactCommon/react/bridging/Base.h

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/bridging/Convert.h>
11+
12+
#include <ReactCommon/CallInvoker.h>
13+
#include <folly/Function.h>
14+
#include <jsi/jsi.h>
15+
16+
#include <cstdint>
17+
#include <memory>
18+
#include <type_traits>
19+
20+
namespace facebook::react {
21+
22+
template <typename T, typename = void>
23+
struct Bridging;
24+
25+
template <>
26+
struct Bridging<void> {
27+
// Highly generic code may result in "casting" to void.
28+
static void fromJs(jsi::Runtime &, const jsi::Value &) {}
29+
};
30+
31+
namespace bridging {
32+
namespace detail {
33+
34+
template <typename F>
35+
struct function_wrapper;
36+
37+
template <typename C, typename R, typename... Args>
38+
struct function_wrapper<R (C::*)(Args...)> {
39+
using type = folly::Function<R(Args...)>;
40+
};
41+
42+
template <typename C, typename R, typename... Args>
43+
struct function_wrapper<R (C::*)(Args...) const> {
44+
using type = folly::Function<R(Args...)>;
45+
};
46+
47+
template <typename T, typename = void>
48+
struct bridging_wrapper {
49+
using type = remove_cvref_t<T>;
50+
};
51+
52+
// Convert lambda types to move-only function types since we can't specialize
53+
// Bridging templates for arbitrary lambdas.
54+
template <typename T>
55+
struct bridging_wrapper<
56+
T,
57+
std::void_t<decltype(&remove_cvref_t<T>::operator())>>
58+
: function_wrapper<decltype(&remove_cvref_t<T>::operator())> {};
59+
60+
} // namespace detail
61+
62+
template <typename T>
63+
using bridging_t = typename detail::bridging_wrapper<T>::type;
64+
65+
template <typename R, typename T, std::enable_if_t<is_jsi_v<T>, int> = 0>
66+
auto fromJs(jsi::Runtime &rt, T &&value, const std::shared_ptr<CallInvoker> &)
67+
-> decltype(static_cast<R>(convert(rt, std::forward<T>(value)))) {
68+
return convert(rt, std::forward<T>(value));
69+
}
70+
71+
template <typename R, typename T>
72+
auto fromJs(jsi::Runtime &rt, T &&value, const std::shared_ptr<CallInvoker> &)
73+
-> decltype(Bridging<remove_cvref_t<R>>::fromJs(
74+
rt,
75+
convert(rt, std::forward<T>(value)))) {
76+
return Bridging<remove_cvref_t<R>>::fromJs(
77+
rt, convert(rt, std::forward<T>(value)));
78+
}
79+
80+
template <typename R, typename T>
81+
auto fromJs(
82+
jsi::Runtime &rt,
83+
T &&value,
84+
const std::shared_ptr<CallInvoker> &jsInvoker)
85+
-> decltype(Bridging<remove_cvref_t<R>>::fromJs(
86+
rt,
87+
convert(rt, std::forward<T>(value)),
88+
jsInvoker)) {
89+
return Bridging<remove_cvref_t<R>>::fromJs(
90+
rt, convert(rt, std::forward<T>(value)), jsInvoker);
91+
}
92+
93+
template <typename T, std::enable_if_t<is_jsi_v<T>, int> = 0>
94+
auto toJs(
95+
jsi::Runtime &rt,
96+
T &&value,
97+
const std::shared_ptr<CallInvoker> & = nullptr)
98+
-> decltype(convert(rt, std::forward<T>(value))) {
99+
return convert(rt, std::forward<T>(value));
100+
}
101+
102+
template <typename T>
103+
auto toJs(
104+
jsi::Runtime &rt,
105+
T &&value,
106+
const std::shared_ptr<CallInvoker> & = nullptr)
107+
-> decltype(Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value))) {
108+
return Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value));
109+
}
110+
111+
template <typename T>
112+
auto toJs(
113+
jsi::Runtime &rt,
114+
T &&value,
115+
const std::shared_ptr<CallInvoker> &jsInvoker)
116+
-> decltype(Bridging<bridging_t<T>>::toJs(
117+
rt,
118+
std::forward<T>(value),
119+
jsInvoker)) {
120+
return Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value), jsInvoker);
121+
}
122+
123+
} // namespace bridging
124+
} // namespace facebook::react

ReactCommon/react/bridging/Bool.h

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/bridging/Base.h>
11+
12+
namespace facebook::react {
13+
14+
template <>
15+
struct Bridging<bool> {
16+
static bool fromJs(jsi::Runtime &rt, const jsi::Value &value) {
17+
return value.asBool();
18+
}
19+
20+
static jsi::Value toJs(jsi::Runtime &, bool value) {
21+
return value;
22+
}
23+
};
24+
25+
} // namespace facebook::react

ReactCommon/react/bridging/Bridging.h

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/bridging/Array.h>
11+
#include <react/bridging/Bool.h>
12+
#include <react/bridging/Error.h>
13+
#include <react/bridging/Function.h>
14+
#include <react/bridging/Number.h>
15+
#include <react/bridging/Object.h>
16+
#include <react/bridging/Promise.h>
17+
#include <react/bridging/String.h>
18+
#include <react/bridging/Value.h>

ReactCommon/react/bridging/CallbackWrapper.h

+4
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ class CallbackWrapper : public LongLivedObject {
8484
return *(jsInvoker_);
8585
}
8686

87+
std::shared_ptr<CallInvoker> jsInvokerPtr() {
88+
return jsInvoker_;
89+
}
90+
8791
void allowRelease() override {
8892
if (auto longLivedObjectCollection = longLivedObjectCollection_.lock()) {
8993
if (longLivedObjectCollection != nullptr) {

0 commit comments

Comments
 (0)