Skip to content

Commit 5febb51

Browse files
committed
Enable support for C++ TurboModules
1 parent ca231e4 commit 5febb51

17 files changed

+907
-49
lines changed

vnext/Microsoft.ReactNative.Cxx/JSI/JsiApi.h renamed to vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#pragma once
55
#ifndef MICROSOFT_REACTNATIVE_JSI_JSIAPI
66
#define MICROSOFT_REACTNATIVE_JSI_JSIAPI
7+
78
#include "../ReactContext.h"
89
#include "JsiAbiApi.h"
910

@@ -16,7 +17,7 @@ namespace winrt::Microsoft::ReactNative {
1617
// Get JSI Runtime from the current JS dispatcher thread.
1718
// If it is not found, then create it and store it in the context.Properties().
1819
// Make sure that the JSI runtime holder is removed when the instance is unloaded.
19-
facebook::jsi::Runtime &GetOrCreateContextRuntime(ReactContext const &context) noexcept {
20+
inline facebook::jsi::Runtime &GetOrCreateContextRuntime(ReactContext const &context) noexcept {
2021
ReactDispatcher jsDispatcher = context.JSDispatcher();
2122
VerifyElseCrashSz(jsDispatcher.HasThreadAccess(), "Must be in JS thread");
2223

@@ -62,7 +63,7 @@ facebook::jsi::Runtime &GetOrCreateContextRuntime(ReactContext const &context) n
6263
// For example: ExecuteJsi(context, [](facebook::jsi::Runtime& runtime){...})
6364
// The code is executed synchronously if it is already in JSDispatcher, or asynchronously otherwise.
6465
template <class TCodeWithRuntime>
65-
void ExecuteJsi(ReactContext const &context, TCodeWithRuntime const &code) {
66+
inline void ExecuteJsi(ReactContext const &context, TCodeWithRuntime const &code) {
6667
ReactDispatcher jsDispatcher = context.JSDispatcher();
6768
if (jsDispatcher.HasThreadAccess()) {
6869
// Execute immediately if we are in JS thread.

vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,15 @@
2424
<ClInclude Include="$(JSI_SourcePath)\jsi\instrumentation.h" />
2525
<ClInclude Include="$(JSI_SourcePath)\jsi\jsi-inl.h" />
2626
<ClInclude Include="$(JSI_SourcePath)\jsi\jsi.h" />
27+
<ClInclude Include="$(MSBuildThisFileDirectory)ReactCommon\TurboModuleUtils.h" />
28+
<ClInclude Include="$(MSBuildThisFileDirectory)TurboModuleProvider.h" />
29+
<ClInclude Include="$(ReactNativeDir)\ReactCommon\callinvoker\ReactCommon\CallInvoker.h" />
30+
<ClInclude Include="$(ReactNativeDir)\ReactCommon\react\nativemodule\core\ReactCommon\LongLivedObject.h" />
31+
<ClInclude Include="$(ReactNativeDir)\ReactCommon\react\nativemodule\core\ReactCommon\TurboModule.h" />
2732
<ClInclude Include="$(MSBuildThisFileDirectory)CppWinRTIncludes.h" />
2833
<ClInclude Include="$(MSBuildThisFileDirectory)Crash.h" />
2934
<ClInclude Include="$(MSBuildThisFileDirectory)JSI\JsiAbiApi.h" />
30-
<ClInclude Include="$(MSBuildThisFileDirectory)JSI\JsiApi.h" />
35+
<ClInclude Include="$(MSBuildThisFileDirectory)JSI\JsiApiContext.h" />
3136
<ClInclude Include="$(MSBuildThisFileDirectory)ReactHandleHelper.h" />
3237
<ClInclude Include="$(MSBuildThisFileDirectory)JSValue.h" />
3338
<ClInclude Include="$(MSBuildThisFileDirectory)JSValueReader.h" />
@@ -78,4 +83,16 @@
7883
<ClCompile Include="$(MSBuildThisFileDirectory)ReactPromise.cpp" />
7984
<ClCompile Include="$(MSBuildThisFileDirectory)JSI\JsiAbiApi.cpp" />
8085
</ItemGroup>
86+
<ItemGroup>
87+
<None Include="$(MSBuildThisFileDirectory)README.md" />
88+
</ItemGroup>
89+
<ItemGroup>
90+
<ClCompile Include="$(MSBuildThisFileDirectory)ReactCommon\TurboModuleUtils.cpp">
91+
<DisableSpecificWarnings>4100;%(DisableSpecificWarnings)</DisableSpecificWarnings>
92+
</ClCompile>
93+
<ClCompile Include="$(ReactNativeDir)\ReactCommon\react\nativemodule\core\ReactCommon\LongLivedObject.cpp" />
94+
<ClCompile Include="$(ReactNativeDir)\ReactCommon\react\nativemodule\core\ReactCommon\TurboModule.cpp">
95+
<DisableSpecificWarnings>4100;4267;%(DisableSpecificWarnings)</DisableSpecificWarnings>
96+
</ClCompile>
97+
</ItemGroup>
8198
</Project>

vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
<ClCompile Include="$(MSBuildThisFileDirectory)JSI\JsiAbiApi.cpp">
1313
<Filter>JSI</Filter>
1414
</ClCompile>
15+
<ClCompile Include="$(ReactNativeDir)\ReactCommon\react\nativemodule\core\ReactCommon\TurboModule.cpp">
16+
<Filter>TurboModule</Filter>
17+
</ClCompile>
18+
<ClCompile Include="$(ReactNativeDir)\ReactCommon\react\nativemodule\core\ReactCommon\LongLivedObject.cpp">
19+
<Filter>TurboModule</Filter>
20+
</ClCompile>
21+
<ClCompile Include="$(MSBuildThisFileDirectory)ReactCommon\TurboModuleUtils.cpp">
22+
<Filter>TurboModule</Filter>
23+
</ClCompile>
1524
</ItemGroup>
1625
<ItemGroup>
1726
<ClInclude Include="$(MSBuildThisFileDirectory)Crash.h" />
@@ -112,7 +121,22 @@
112121
<ClInclude Include="$(JSI_SourcePath)\jsi\instrumentation.h">
113122
<Filter>JSI</Filter>
114123
</ClInclude>
115-
<ClInclude Include="$(MSBuildThisFileDirectory)JSI\JsiApi.h">
124+
<ClInclude Include="$(ReactNativeDir)\ReactCommon\react\nativemodule\core\ReactCommon\TurboModule.h">
125+
<Filter>TurboModule</Filter>
126+
</ClInclude>
127+
<ClInclude Include="$(ReactNativeDir)\ReactCommon\callinvoker\ReactCommon\CallInvoker.h">
128+
<Filter>TurboModule</Filter>
129+
</ClInclude>
130+
<ClInclude Include="$(ReactNativeDir)\ReactCommon\react\nativemodule\core\ReactCommon\LongLivedObject.h">
131+
<Filter>TurboModule</Filter>
132+
</ClInclude>
133+
<ClInclude Include="$(MSBuildThisFileDirectory)TurboModuleProvider.h">
134+
<Filter>TurboModule</Filter>
135+
</ClInclude>
136+
<ClInclude Include="$(MSBuildThisFileDirectory)ReactCommon\TurboModuleUtils.h">
137+
<Filter>TurboModule</Filter>
138+
</ClInclude>
139+
<ClInclude Include="$(MSBuildThisFileDirectory)JSI\JsiApiContext.h">
116140
<Filter>JSI</Filter>
117141
</ClInclude>
118142
</ItemGroup>
@@ -123,5 +147,11 @@
123147
<Filter Include="UI">
124148
<UniqueIdentifier>{acac5f8a-4b6a-4139-9b74-6e3c36aceb20}</UniqueIdentifier>
125149
</Filter>
150+
<Filter Include="TurboModule">
151+
<UniqueIdentifier>{b5c0294c-d72f-44fa-8509-369f7d3e4a56}</UniqueIdentifier>
152+
</Filter>
153+
</ItemGroup>
154+
<ItemGroup>
155+
<None Include="$(MSBuildThisFileDirectory)README.md" />
126156
</ItemGroup>
127157
</Project>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// This file is a copy from react-native NPM package to avoid dependency on folly.
2+
3+
/*
4+
* Copyright (c) Facebook, Inc. and its affiliates.
5+
*
6+
* This source code is licensed under the MIT license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*/
9+
10+
#include "TurboModuleUtils.h"
11+
12+
namespace facebook {
13+
namespace react {
14+
15+
static jsi::Value deepCopyJSIValue(jsi::Runtime &rt, const jsi::Value &value) {
16+
if (value.isNull()) {
17+
return jsi::Value::null();
18+
}
19+
20+
if (value.isBool()) {
21+
return jsi::Value(value.getBool());
22+
}
23+
24+
if (value.isNumber()) {
25+
return jsi::Value(value.getNumber());
26+
}
27+
28+
if (value.isString()) {
29+
return value.getString(rt);
30+
}
31+
32+
if (value.isObject()) {
33+
jsi::Object o = value.getObject(rt);
34+
if (o.isArray(rt)) {
35+
return deepCopyJSIArray(rt, o.getArray(rt));
36+
}
37+
if (o.isFunction(rt)) {
38+
return o.getFunction(rt);
39+
}
40+
return deepCopyJSIObject(rt, o);
41+
}
42+
43+
return jsi::Value::undefined();
44+
}
45+
46+
jsi::Object deepCopyJSIObject(jsi::Runtime &rt, const jsi::Object &obj) {
47+
jsi::Object copy(rt);
48+
jsi::Array propertyNames = obj.getPropertyNames(rt);
49+
size_t size = propertyNames.size(rt);
50+
for (size_t i = 0; i < size; i++) {
51+
jsi::String name = propertyNames.getValueAtIndex(rt, i).getString(rt);
52+
jsi::Value value = obj.getProperty(rt, name);
53+
copy.setProperty(rt, name, deepCopyJSIValue(rt, value));
54+
}
55+
return copy;
56+
}
57+
58+
jsi::Array deepCopyJSIArray(jsi::Runtime &rt, const jsi::Array &arr) {
59+
size_t size = arr.size(rt);
60+
jsi::Array copy(rt, size);
61+
for (size_t i = 0; i < size; i++) {
62+
copy.setValueAtIndex(rt, i, deepCopyJSIValue(rt, arr.getValueAtIndex(rt, i)));
63+
}
64+
return copy;
65+
}
66+
67+
Promise::Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject)
68+
: runtime_(rt), resolve_(std::move(resolve)), reject_(std::move(reject)) {}
69+
70+
void Promise::resolve(const jsi::Value &result) {
71+
resolve_.call(runtime_, result);
72+
}
73+
74+
void Promise::reject(const std::string &message) {
75+
jsi::Object error(runtime_);
76+
error.setProperty(runtime_, "message", jsi::String::createFromUtf8(runtime_, message));
77+
reject_.call(runtime_, error);
78+
}
79+
80+
jsi::Value createPromiseAsJSIValue(jsi::Runtime &rt, const PromiseSetupFunctionType func) {
81+
jsi::Function JSPromise = rt.global().getPropertyAsFunction(rt, "Promise");
82+
jsi::Function fn = jsi::Function::createFromHostFunction(
83+
rt,
84+
jsi::PropNameID::forAscii(rt, "fn"),
85+
2,
86+
[func](jsi::Runtime &rt2, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
87+
jsi::Function resolve = args[0].getObject(rt2).getFunction(rt2);
88+
jsi::Function reject = args[1].getObject(rt2).getFunction(rt2);
89+
auto wrapper = std::make_shared<Promise>(rt2, std::move(resolve), std::move(reject));
90+
func(rt2, wrapper);
91+
return jsi::Value::undefined();
92+
});
93+
94+
return JSPromise.callAsConstructor(rt, fn);
95+
}
96+
97+
} // namespace react
98+
} // namespace facebook
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// This file is a copy from react-native NPM package to avoid dependency on folly.
2+
3+
/*
4+
* Copyright (c) Facebook, Inc. and its affiliates.
5+
*
6+
* This source code is licensed under the MIT license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*/
9+
10+
#pragma once
11+
12+
#include <cassert>
13+
#include <string>
14+
15+
// RNW changes: commended out the unused include that creates unnecessary dependency on folly
16+
//#include <folly/Optional.h>
17+
#include <jsi/jsi.h>
18+
19+
#include <ReactCommon/CallInvoker.h>
20+
#include <ReactCommon/LongLivedObject.h>
21+
22+
using namespace facebook;
23+
24+
namespace facebook {
25+
namespace react {
26+
27+
jsi::Object deepCopyJSIObject(jsi::Runtime &rt, const jsi::Object &obj);
28+
jsi::Array deepCopyJSIArray(jsi::Runtime &rt, const jsi::Array &arr);
29+
30+
struct Promise : public LongLivedObject {
31+
Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject);
32+
33+
void resolve(const jsi::Value &result);
34+
void reject(const std::string &error);
35+
36+
jsi::Runtime &runtime_;
37+
jsi::Function resolve_;
38+
jsi::Function reject_;
39+
};
40+
41+
using PromiseSetupFunctionType = std::function<void(jsi::Runtime &rt, std::shared_ptr<Promise>)>;
42+
jsi::Value createPromiseAsJSIValue(jsi::Runtime &rt, const PromiseSetupFunctionType func);
43+
44+
// Helper for passing jsi::Function arg to other methods.
45+
class CallbackWrapper : public LongLivedObject {
46+
private:
47+
CallbackWrapper(jsi::Function &&callback, jsi::Runtime &runtime, std::shared_ptr<CallInvoker> jsInvoker)
48+
: callback_(std::move(callback)), runtime_(runtime), jsInvoker_(std::move(jsInvoker)) {}
49+
50+
jsi::Function callback_;
51+
jsi::Runtime &runtime_;
52+
std::shared_ptr<CallInvoker> jsInvoker_;
53+
54+
public:
55+
static std::weak_ptr<CallbackWrapper>
56+
createWeak(jsi::Function &&callback, jsi::Runtime &runtime, std::shared_ptr<CallInvoker> jsInvoker) {
57+
auto wrapper = std::shared_ptr<CallbackWrapper>(new CallbackWrapper(std::move(callback), runtime, jsInvoker));
58+
LongLivedObjectCollection::get().add(wrapper);
59+
return wrapper;
60+
}
61+
62+
// Delete the enclosed jsi::Function
63+
void destroy() {
64+
allowRelease();
65+
}
66+
67+
jsi::Function &callback() {
68+
return callback_;
69+
}
70+
71+
jsi::Runtime &runtime() {
72+
return runtime_;
73+
}
74+
75+
CallInvoker &jsInvoker() {
76+
return *(jsInvoker_);
77+
}
78+
};
79+
80+
} // namespace react
81+
} // namespace facebook
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
#pragma once
5+
#include <JSI/JsiAbiApi.h>
6+
#include <JSI/JsiApiContext.h>
7+
#include <ReactCommon/TurboModule.h>
8+
#include <winrt/Microsoft.ReactNative.h>
9+
#include <winrt/Windows.Foundation.h>
10+
11+
namespace winrt::Microsoft::ReactNative {
12+
13+
// CallInvoker on top of JSDispatcher.
14+
struct AbiCallInvoker : facebook::react::CallInvoker {
15+
AbiCallInvoker(IReactDispatcher const &jsDispatcher) : m_jsDispatcher(jsDispatcher) {}
16+
17+
void invokeAsync(std::function<void()> &&func) override {
18+
m_jsDispatcher.Post([func = std::move(func)]() { func(); });
19+
}
20+
21+
void invokeSync(std::function<void()> &&func) override {
22+
if (m_jsDispatcher.HasThreadAccess()) {
23+
func();
24+
} else {
25+
std::mutex mutex;
26+
std::condition_variable cv;
27+
bool completed{false};
28+
auto lock = std::unique_lock{mutex};
29+
m_jsDispatcher.Post([&func, &mutex, &cv, &completed]() {
30+
func();
31+
auto lock = std::unique_lock{mutex};
32+
completed = true;
33+
cv.notify_all();
34+
});
35+
cv.wait(lock, [&] { return completed; });
36+
}
37+
func = nullptr;
38+
}
39+
40+
private:
41+
IReactDispatcher m_jsDispatcher{nullptr};
42+
};
43+
44+
// ABI-safe wrapper for Turbo module
45+
template <typename TTurboModule>
46+
struct AbiTurboModule : implements<AbiTurboModule<TTurboModule>, IJsiHostObject> {
47+
AbiTurboModule() {}
48+
void Initialize(IReactContext const &context) {
49+
m_callInvoker = std::make_shared<AbiCallInvoker>(context.JSDispatcher());
50+
auto turboModule = std::make_shared<TTurboModule>(m_callInvoker);
51+
m_module = winrt::make<JsiHostObjectWrapper>(std::move(turboModule));
52+
}
53+
54+
JsiValueRef GetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name) {
55+
return m_module.GetProperty(runtime, name);
56+
}
57+
58+
void SetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name, JsiValueRef const &value) {
59+
m_module.SetProperty(runtime, name, value);
60+
}
61+
62+
Windows::Foundation::Collections::IVector<JsiPropertyIdRef> GetPropertyIds(JsiRuntime const &runtime) {
63+
return m_module.GetPropertyIds(runtime);
64+
}
65+
66+
private:
67+
IJsiHostObject m_module{nullptr};
68+
std::shared_ptr<facebook::react::CallInvoker> m_callInvoker;
69+
};
70+
71+
// Create a module provider for TModule type.
72+
template <
73+
typename TTurboModule,
74+
std::enable_if_t<std::is_base_of_v<::facebook::react::TurboModule, TTurboModule>, int> = 0>
75+
inline ReactModuleProvider MakeJsiTurboModuleProvider() noexcept {
76+
return [](IReactModuleBuilder const &moduleBuilder) noexcept->winrt::Windows::Foundation::IInspectable {
77+
auto abiTurboModule = winrt::make_self<AbiTurboModule<TTurboModule>>();
78+
moduleBuilder.AddInitializer([abiTurboModule](IReactContext const &context) mutable {
79+
ReactContext ctx{context};
80+
GetOrCreateContextRuntime(ctx);
81+
abiTurboModule->Initialize(context);
82+
abiTurboModule = nullptr;
83+
});
84+
return abiTurboModule.as<winrt::Windows::Foundation::IInspectable>();
85+
};
86+
}
87+
88+
template <typename TTurboModule>
89+
void AddTurboModuleProvider(IReactPackageBuilder const &packageBuilder, std::wstring_view moduleName) {
90+
auto experimental = packageBuilder.as<IReactPackageBuilderExperimental>();
91+
experimental.AddTurboModule(moduleName, MakeJsiTurboModuleProvider<TTurboModule>());
92+
}
93+
94+
} // namespace winrt::Microsoft::ReactNative

0 commit comments

Comments
 (0)