Get CallInvokers from the bridge

Summary:
## Context
For now, assume TurboModules doesn't exist.

**What happens when we call an async NativeModule method?**
Everytime JS calls an async NativeModule method, we don't immediately execute it. The legacy infra pushes the call into some queue managed by `MessageQueue.js`. This queue is "flushed" or "emptied" by the following events:
- **Flushed:** A C++ -> JS call. NativeModule async methods can called with an `onSuccess` and/or `onFail` callback(s). Calling `NativeToJsBridge::invokeCallback` to invoke one of these callbacks is one way for ObjC++/C++/Java to call into JS. Another way is via JSModule method calls, which are initiated by `NativeToJsBridge::callFunction`.
- **Flushed:** When `JSIExecutor::flush` is called. Since TurboModules don't exist, this only happens when we call `JSIExecutor::loadApplicationScript`.
- **Emptied:** When more than 5 ms have passed, and the queue hasn't been flushed/emptied, on the next async NativeModule method call, we add to the queue. Afterwards, we empty it, and invoke all the NativeModule method calls.

**So, what's the difference between flushed and emptied?**
> Note: These are two terms I just made up, but the distinction is important.

If the queue was "flushed", and it contained at least one NativeModule method call, `JsToNativeBridge` dispatches the `onBatchComplete` event. On Android, the UIManager module is the only module that listens to this event. This `onBatchComplete` event doesn't fire if the queue was "emptied".

**Why does any of this matter?**
1. TurboModules exist.
2. We need the TurboModules infra to have `JsToNativeBridge` dispatch `onBatchComplete`, which depends on:
   - **Problem 1:** The queue being flushed on calls into JS from Java/C++/ObjC++.
   - **Problem 2:** There being queued up NativeModule async method calls when the queue is flushed.

In D14656466, fkgozali fixed Problem 1 by making every C++/Java/Obj -> JS call from TurboModules also execute `JSIExecutor::flush()`. This means that, with TurboModules, we flush the NativeModule async method call queue as often as we do without TurboModules. So far, so good. However, we still have one big problem: As we convert more NativeModules to TurboModules, the average size of the queue of NativeModule method calls will become smaller and smaller, because more NativeModule method calls will be TurboModule method calls. This queue will more often be empty than not. Therefore, we'll end up dispatching the `onBatchComplete` event less often with TurboModules enabled. So, somehow, when we're about to flush the NativeModule method call queue, we need `JsToNativeBridge` to understand that we've executed TurboModule method calls in the batch. These calls would have normally been queued, which would have led the queue size to be non-zero. So if, during a batch, some TurboModule async method calls were executed, `JsToNativeBridge` should dispatch `onBatchComplete`.

**So, what does this diff do?**
1. Make `Instance` responsible for creating the JS `CallInvoker`.
2. Make `NativeToJsBridge` responsible for creating the native `CallInvoker`. `Instance` calls into `NativeToJsBridge` to get  the native `CallInvoker`.
3. Hook up `CatalystInstanceImpl`, the Android bridge, with the new JS `CallInvoker`, and the new native `CallInvoker`. This fixes `onBatchComplete` on Android. iOS work is pending.

Changelog:
[Android][Fixed] - Ensure `onBatchComplete` is dispatched correctly with TurboModules

Reviewed By: mdvacca

Differential Revision: D20717931

fbshipit-source-id: bc3ccbd6c135b7f084edbc6ddb4d1e3c0c7e0875
This commit is contained in:
Ramanpreet Nara 2020-04-01 11:36:50 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 3591b7a956
Коммит 9b94a541d8
22 изменённых файлов: 266 добавлений и 212 удалений

Просмотреть файл

@ -1,6 +1,6 @@
PODS:
- boost-for-react-native (1.63.0)
- CocoaAsyncSocket (7.6.3)
- CocoaAsyncSocket (7.6.4)
- CocoaLibEvent (1.0.0)
- DoubleConversion (1.1.6)
- FBLazyVector (1000.0.0)
@ -11,7 +11,7 @@ PODS:
- React-Core (= 1000.0.0)
- React-jsi (= 1000.0.0)
- ReactCommon/turbomodule/core (= 1000.0.0)
- Flipper (0.30.2):
- Flipper (0.33.1):
- Flipper-Folly (~> 2.1)
- Flipper-RSocket (~> 1.0)
- Flipper-DoubleConversion (1.1.7)
@ -25,36 +25,36 @@ PODS:
- Flipper-PeerTalk (0.0.4)
- Flipper-RSocket (1.0.0):
- Flipper-Folly (~> 2.0)
- FlipperKit (0.30.2):
- FlipperKit/Core (= 0.30.2)
- FlipperKit/Core (0.30.2):
- Flipper (~> 0.30.2)
- FlipperKit (0.33.1):
- FlipperKit/Core (= 0.33.1)
- FlipperKit/Core (0.33.1):
- Flipper (~> 0.33.1)
- FlipperKit/CppBridge
- FlipperKit/FBCxxFollyDynamicConvert
- FlipperKit/FBDefines
- FlipperKit/FKPortForwarding
- FlipperKit/CppBridge (0.30.2):
- Flipper (~> 0.30.2)
- FlipperKit/FBCxxFollyDynamicConvert (0.30.2):
- FlipperKit/CppBridge (0.33.1):
- Flipper (~> 0.33.1)
- FlipperKit/FBCxxFollyDynamicConvert (0.33.1):
- Flipper-Folly (~> 2.1)
- FlipperKit/FBDefines (0.30.2)
- FlipperKit/FKPortForwarding (0.30.2):
- FlipperKit/FBDefines (0.33.1)
- FlipperKit/FKPortForwarding (0.33.1):
- CocoaAsyncSocket (~> 7.6)
- Flipper-PeerTalk (~> 0.0.4)
- FlipperKit/FlipperKitHighlightOverlay (0.30.2)
- FlipperKit/FlipperKitLayoutPlugin (0.30.2):
- FlipperKit/FlipperKitHighlightOverlay (0.33.1)
- FlipperKit/FlipperKitLayoutPlugin (0.33.1):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutTextSearchable
- YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutTextSearchable (0.30.2)
- FlipperKit/FlipperKitNetworkPlugin (0.30.2):
- FlipperKit/FlipperKitLayoutTextSearchable (0.33.1)
- FlipperKit/FlipperKitNetworkPlugin (0.33.1):
- FlipperKit/Core
- FlipperKit/FlipperKitReactPlugin (0.30.2):
- FlipperKit/FlipperKitReactPlugin (0.33.1):
- FlipperKit/Core
- FlipperKit/FlipperKitUserDefaultsPlugin (0.30.2):
- FlipperKit/FlipperKitUserDefaultsPlugin (0.33.1):
- FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.30.2):
- FlipperKit/SKIOSNetworkPlugin (0.33.1):
- FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin
- Folly (2020.01.13.00):
@ -91,6 +91,7 @@ PODS:
- React-RCTVibration (= 1000.0.0)
- React-ART (1000.0.0):
- React-Core/ARTHeaders (= 1000.0.0)
- React-callinvoker (1000.0.0)
- React-Core (1000.0.0):
- Folly (= 2020.01.13.00)
- glog
@ -232,6 +233,7 @@ PODS:
- DoubleConversion
- Folly (= 2020.01.13.00)
- glog
- React-callinvoker (= 1000.0.0)
- React-jsinspector (= 1000.0.0)
- React-jsi (1000.0.0):
- boost-for-react-native (= 1.63.0)
@ -308,27 +310,22 @@ PODS:
- Folly (= 2020.01.13.00)
- React-Core/RCTVibrationHeaders (= 1000.0.0)
- ReactCommon/turbomodule/core (= 1000.0.0)
- ReactCommon/callinvoker (1000.0.0):
- DoubleConversion
- Folly (= 2020.01.13.00)
- glog
- React-cxxreact (= 1000.0.0)
- ReactCommon/turbomodule/core (1000.0.0):
- DoubleConversion
- Folly (= 2020.01.13.00)
- glog
- React-callinvoker (= 1000.0.0)
- React-Core (= 1000.0.0)
- React-cxxreact (= 1000.0.0)
- React-jsi (= 1000.0.0)
- ReactCommon/callinvoker (= 1000.0.0)
- ReactCommon/turbomodule/samples (1000.0.0):
- DoubleConversion
- Folly (= 2020.01.13.00)
- glog
- React-callinvoker (= 1000.0.0)
- React-Core (= 1000.0.0)
- React-cxxreact (= 1000.0.0)
- React-jsi (= 1000.0.0)
- ReactCommon/callinvoker (= 1000.0.0)
- ReactCommon/turbomodule/core (= 1000.0.0)
- Yoga (1.14.0)
- YogaKit (1.18.1):
@ -338,17 +335,18 @@ DEPENDENCIES:
- DoubleConversion (from `../third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../Libraries/FBReactNativeSpec`)
- FlipperKit (~> 0.30.2)
- FlipperKit/FlipperKitLayoutPlugin (~> 0.30.2)
- FlipperKit/FlipperKitReactPlugin (~> 0.30.2)
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.30.2)
- FlipperKit/SKIOSNetworkPlugin (~> 0.30.2)
- FlipperKit (~> 0.33.1)
- FlipperKit/FlipperKitLayoutPlugin (~> 0.33.1)
- FlipperKit/FlipperKitReactPlugin (~> 0.33.1)
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.33.1)
- FlipperKit/SKIOSNetworkPlugin (~> 0.33.1)
- Folly (from `../third-party-podspecs/Folly.podspec`)
- glog (from `../third-party-podspecs/glog.podspec`)
- RCTRequired (from `../Libraries/RCTRequired`)
- RCTTypeSafety (from `../Libraries/TypeSafety`)
- React (from `../`)
- React-ART (from `../Libraries/ART`)
- React-callinvoker (from `../ReactCommon/callinvoker`)
- React-Core (from `../`)
- React-Core/DevSupport (from `../`)
- React-Core/RCTWebSocket (from `../`)
@ -368,7 +366,6 @@ DEPENDENCIES:
- React-RCTTest (from `./RCTTest`)
- React-RCTText (from `../Libraries/Text`)
- React-RCTVibration (from `../Libraries/Vibration`)
- ReactCommon/callinvoker (from `../ReactCommon`)
- ReactCommon/turbomodule/core (from `../ReactCommon`)
- ReactCommon/turbomodule/samples (from `../ReactCommon`)
- Yoga (from `../ReactCommon/yoga`)
@ -407,6 +404,8 @@ EXTERNAL SOURCES:
:path: "../"
React-ART:
:path: "../Libraries/ART"
React-callinvoker:
:path: "../ReactCommon/callinvoker"
React-Core:
:path: "../"
React-CoreModules:
@ -448,18 +447,18 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
CocoaAsyncSocket: eafaa68a7e0ec99ead0a7b35015e0bf25d2c8987
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
DoubleConversion: cde416483dac037923206447da6e1454df403714
FBLazyVector: 8ea0285646adaf7fa725c20ed737c49ee5ea680a
FBReactNativeSpec: e8f07c749b9cf184c819f5a8ca44b91ab61fca12
Flipper: 10b225e352595f521be0e5badddd90e241336e89
Flipper: 6c1f484f9a88d30ab3e272800d53688439e50f69
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
Flipper-Folly: 2de3d03e0acc7064d5e4ed9f730e2f217486f162
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: 1260a31c05c238eabfa9bb8a64e3983049048371
FlipperKit: 88b7f0d0cf907ddc2137b85eeb7f3d4d8d9395c8
FlipperKit: 6dc9b8f4ef60d9e5ded7f0264db299c91f18832e
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
@ -467,9 +466,10 @@ SPEC CHECKSUMS:
RCTTypeSafety: 1ade47a69b092cddf1e4ea21e0c5bdc65cc950b4
React: cafb3c2321f7df55ce90dbf29d513799a79e4418
React-ART: df0460bdff42ef039e28ee3ffd41f50b75644788
React-callinvoker: 0dada022d38b73e6e15b33e2a96476153f79bbf6
React-Core: 08c69f013e6fd654ea8f9fd84bbd66780a54d886
React-CoreModules: 0b59c833afcc9735e5a0220997fb18876dc9e52c
React-cxxreact: c0246279e902a2a6e71e309e8cdcbaa4877141c6
React-cxxreact: 091da030e879ed93d970e95dd74fcbacb2a1d661
React-jsi: fe94132da767bfc4801968c2a12abae43e9a833e
React-jsiexecutor: 55eff40b2e0696e7a979016e321793ec8b28a2ac
React-jsinspector: 7fbf9b42b58b02943a0d89b0ba9fff0070f2de98
@ -484,7 +484,7 @@ SPEC CHECKSUMS:
React-RCTTest: cfe25fcf70b04a747dba4326105db398250caa9a
React-RCTText: 6c01963d3e562109f5548262b09b1b2bc260dd60
React-RCTVibration: d218336fa28ade97e99b4ddb935f1de5c670e361
ReactCommon: d17767bf5744406b4f4be1db7356ffbf1f99180c
ReactCommon: 39e00b754f5e1628804fab28f44146d06280f700
Yoga: f7fa200d8c49f97b54c9421079e781fb900b5cae
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a

Просмотреть файл

@ -21,7 +21,6 @@
#import <React/RCTDataRequestHandler.h>
#import <React/RCTFileRequestHandler.h>
#import <React/RCTRootView.h>
#import <ReactCommon/BridgeJSCallInvoker.h>
#import <cxxreact/JSExecutor.h>
@ -147,7 +146,7 @@
{
_turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
delegate:self
jsInvoker:std::make_shared<facebook::react::BridgeJSCallInvoker>(bridge.reactInstance)];
jsInvoker:bridge.jsCallInvoker];
__weak __typeof(self) weakSelf = self;
return std::make_unique<facebook::react::JSCExecutorFactory>([weakSelf, bridge](facebook::jsi::Runtime &runtime) {
if (!bridge) {

Просмотреть файл

@ -210,9 +210,9 @@ struct RCTInstanceCallback : public InstanceCallback {
return _jsMessageThread;
}
- (std::weak_ptr<Instance>)reactInstance
- (std::shared_ptr<CallInvoker>)jsCallInvoker
{
return _reactInstance;
return _reactInstance ? _reactInstance->getJSCallInvoker() : nullptr;
}
- (BOOL)isInspectable
@ -1443,10 +1443,10 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithBundleURL
__weak __typeof(self) weakSelf = self;
[self _runAfterLoad:^{
__strong __typeof(self) strongSelf = weakSelf;
if (strongSelf->_reactInstance == nullptr) {
return;
if (std::shared_ptr<CallInvoker> jsInvoker = strongSelf.jsCallInvoker) {
jsInvoker->invokeAsync(std::move(retainedFunc));
}
strongSelf->_reactInstance->invokeAsync(std::move(retainedFunc));
}];
}

Просмотреть файл

@ -14,7 +14,6 @@
#include <vector>
#include <ReactCommon/CallInvokerHolder.h>
#include <ReactCommon/MessageQueueThreadCallInvoker.h>
#include <cxxreact/CxxNativeModule.h>
#include <cxxreact/Instance.h>
#include <cxxreact/JSBigString.h>
@ -288,8 +287,8 @@ void CatalystInstanceImpl::handleMemoryPressure(int pressureLevel) {
jni::alias_ref<CallInvokerHolder::javaobject>
CatalystInstanceImpl::getJSCallInvokerHolder() {
if (!jsCallInvokerHolder_) {
jsCallInvokerHolder_ = jni::make_global(CallInvokerHolder::newObjectCxxArgs(
std::make_shared<BridgeJSCallInvoker>(instance_)));
jsCallInvokerHolder_ = jni::make_global(
CallInvokerHolder::newObjectCxxArgs(instance_->getJSCallInvoker()));
}
return jsCallInvokerHolder_;
@ -298,10 +297,12 @@ CatalystInstanceImpl::getJSCallInvokerHolder() {
jni::alias_ref<CallInvokerHolder::javaobject>
CatalystInstanceImpl::getNativeCallInvokerHolder() {
if (!nativeCallInvokerHolder_) {
nativeCallInvokerHolder_ =
jni::make_global(CallInvokerHolder::newObjectCxxArgs(
std::make_shared<MessageQueueThreadCallInvoker>(
moduleMessageQueue_)));
nativeCallInvokerHolder_ = jni::make_global(
CallInvokerHolder::newObjectCxxArgs(instance_->getNativeCallInvoker(
[moduleMessageQueue =
moduleMessageQueue_](std::function<void()> &&work) {
moduleMessageQueue->runOnQueue(std::move(work));
})));
}
return nativeCallInvokerHolder_;

Просмотреть файл

@ -8,7 +8,6 @@
#include <memory>
#include <string>
#include <ReactCommon/BridgeJSCallInvoker.h>
#include <ReactCommon/CallInvokerHolder.h>
#include <fbjni/fbjni.h>

Просмотреть файл

@ -36,17 +36,8 @@ Pod::Spec.new do |s|
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++14" }
s.subspec "callinvoker" do |ss|
ss.source_files = "callinvoker/**/*.{cpp,h}"
ss.dependency "React-cxxreact", version
ss.dependency "DoubleConversion"
ss.dependency "Folly", folly_version
ss.dependency "glog"
end
s.subspec "turbomodule" do |ss|
ss.dependency "ReactCommon/callinvoker", version
ss.dependency "React-callinvoker", version
ss.dependency "React-Core", version
ss.dependency "React-cxxreact", version
ss.dependency "React-jsi", version

Просмотреть файл

@ -15,8 +15,6 @@ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall
LOCAL_STATIC_LIBRARIES = libreactnative
# Name of this module.
LOCAL_MODULE := callinvoker

Просмотреть файл

@ -1,4 +1,4 @@
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "react_native_xplat_target", "rn_xplat_cxx_library", "subdir_glob")
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "rn_xplat_cxx_library", "subdir_glob")
rn_xplat_cxx_library(
name = "callinvoker",
@ -26,7 +26,4 @@ rn_xplat_cxx_library(
visibility = [
"PUBLIC",
],
deps = [
react_native_xplat_target("cxxreact:bridge"),
],
)

Просмотреть файл

@ -0,0 +1,34 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip
else
source[:tag] = "v#{version}"
end
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
folly_version = '2020.01.13.00'
boost_compiler_flags = '-Wno-documentation'
Pod::Spec.new do |s|
s.name = "React-callinvoker"
s.version = version
s.summary = "-" # TODO
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Facebook, Inc. and its affiliates"
s.platforms = { :ios => "10.0", :tvos => "10.0" }
s.source = source
s.source_files = "**/*.{cpp,h}"
s.header_dir = "ReactCommon"
end

Просмотреть файл

@ -1,26 +0,0 @@
/*
* 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 <ReactCommon/BridgeJSCallInvoker.h>
#include <cxxreact/Instance.h>
namespace facebook {
namespace react {
BridgeJSCallInvoker::BridgeJSCallInvoker(std::weak_ptr<Instance> reactInstance)
: reactInstance_(reactInstance) {}
void BridgeJSCallInvoker::invokeAsync(std::function<void()> &&func) {
auto instance = reactInstance_.lock();
if (instance == nullptr) {
return;
}
instance->invokeAsync(std::move(func));
}
} // namespace react
} // namespace facebook

Просмотреть файл

@ -1,42 +0,0 @@
/*
* 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 <functional>
#include <memory>
#include <ReactCommon/CallInvoker.h>
namespace facebook {
namespace react {
class Instance;
/**
* A native-to-JS call invoker that uses the bridge ('Instance').
* It guarantees that any calls from any thread are queued on the right JS
* thread.
*
* For now, this is a thin-wrapper around existing bridge. Eventually,
* it should be consolidated with Fabric implementation so there's only one
* API to call JS from native, whether synchronously or asynchronously.
* Also, this class should not depend on `Instance` in the future.
*/
class BridgeJSCallInvoker : public CallInvoker {
public:
BridgeJSCallInvoker(std::weak_ptr<Instance> reactInstance);
void invokeAsync(std::function<void()> &&func) override;
// TODO: add sync support
private:
std::weak_ptr<Instance> reactInstance_;
};
} // namespace react
} // namespace facebook

Просмотреть файл

@ -1,22 +0,0 @@
/*
* 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 "MessageQueueThreadCallInvoker.h"
namespace facebook {
namespace react {
MessageQueueThreadCallInvoker::MessageQueueThreadCallInvoker(
std::shared_ptr<MessageQueueThread> moduleMessageQueue)
: moduleMessageQueue_(moduleMessageQueue) {}
void MessageQueueThreadCallInvoker::invokeAsync(std::function<void()> &&func) {
moduleMessageQueue_->runOnQueue(std::move(func));
}
} // namespace react
} // namespace facebook

Просмотреть файл

@ -1,35 +0,0 @@
/*
* 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 <functional>
#include <memory>
#include <ReactCommon/CallInvoker.h>
#include <cxxreact/MessageQueueThread.h>
namespace facebook {
namespace react {
/**
* Used to schedule async calls on the NativeModuels thread.
*/
class MessageQueueThreadCallInvoker : public CallInvoker {
public:
MessageQueueThreadCallInvoker(
std::shared_ptr<MessageQueueThread> moduleMessageQueue);
void invokeAsync(std::function<void()> &&func) override;
// TODO: add sync support
private:
std::shared_ptr<MessageQueueThread> moduleMessageQueue_;
};
} // namespace react
} // namespace facebook

Просмотреть файл

@ -19,13 +19,14 @@ LOCAL_CFLAGS := \
LOCAL_CFLAGS += -fexceptions -frtti -Wno-unused-lambda-capture
LOCAL_STATIC_LIBRARIES := boost jsi
LOCAL_STATIC_LIBRARIES := boost jsi callinvoker
LOCAL_SHARED_LIBRARIES := jsinspector libfolly_json glog
include $(BUILD_STATIC_LIBRARY)
$(call import-module,fb)
$(call import-module,folly)
$(call import-module,callinvoker)
$(call import-module,jsc)
$(call import-module,glog)
$(call import-module,jsi)

Просмотреть файл

@ -148,6 +148,7 @@ rn_xplat_cxx_library(
"//xplat/folly:headers_only",
"//xplat/folly:memory",
"//xplat/folly:molly",
react_native_xplat_target("callinvoker:callinvoker"),
react_native_xplat_target("jsinspector:jsinspector"),
react_native_xplat_target("microprofiler:microprofiler"),
"//xplat/folly:optional",

Просмотреть файл

@ -46,9 +46,16 @@ void Instance::initializeBridge(
callback_ = std::move(callback);
moduleRegistry_ = std::move(moduleRegistry);
jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
nativeToJsBridge_ = std::make_unique<NativeToJsBridge>(
nativeToJsBridge_ = std::make_shared<NativeToJsBridge>(
jsef.get(), moduleRegistry_, jsQueue, callback_);
/**
* After NativeToJsBridge is created, the jsi::Runtime should exist.
* Also, the JS message queue thread exists. So, it's safe to
* schedule all queued up js Calls.
*/
jsCallInvoker_->setNativeToJsBridgeAndFlushCalls(nativeToJsBridge_);
std::lock_guard<std::mutex> lock(m_syncMutex);
m_syncReady = true;
m_syncCV.notify_all();
@ -215,12 +222,60 @@ void Instance::handleMemoryPressure(int pressureLevel) {
nativeToJsBridge_->handleMemoryPressure(pressureLevel);
}
void Instance::invokeAsync(std::function<void()> &&func) {
nativeToJsBridge_->runOnExecutorQueue(
[func = std::move(func)](JSExecutor *executor) {
func();
executor->flush();
});
std::shared_ptr<CallInvoker> Instance::getJSCallInvoker() {
return std::static_pointer_cast<CallInvoker>(jsCallInvoker_);
}
std::shared_ptr<CallInvoker> Instance::getNativeCallInvoker(
std::function<void(std::function<void()> &&work)> &&scheduleWork) {
return nativeToJsBridge_->getNativeCallInvoker(std::move(scheduleWork));
}
void Instance::JSCallInvoker::setNativeToJsBridgeAndFlushCalls(
std::weak_ptr<NativeToJsBridge> nativeToJsBridge) {
std::lock_guard<std::mutex> guard(m_mutex);
m_shouldBuffer = false;
m_nativeToJsBridge = nativeToJsBridge;
while (m_workBuffer.size() > 0) {
scheduleAsync(std::move(m_workBuffer.front()));
m_workBuffer.pop_front();
}
}
void Instance::JSCallInvoker::invokeAsync(std::function<void()> &&work) {
std::lock_guard<std::mutex> guard(m_mutex);
/**
* Why is is necessary to queue up async work?
*
* 1. TurboModuleManager must be created synchronously after the Instance,
* before we load the source code. This is when the NativeModule system
* is initialized. RCTDevLoadingView shows bundle download progress.
* 2. TurboModuleManager requires a JS CallInvoker.
* 3. The JS CallInvoker requires the NativeToJsBridge, which is created on
* the JS thread in Instance::initializeBridge.
*
* Therefore, although we don't anyone to call invokeAsync before the JS
* bundle is executed, this buffering is implemented anyways to ensure
* that work isn't discarded.
*/
if (m_shouldBuffer) {
m_workBuffer.push_back(std::move(work));
return;
}
scheduleAsync(std::move(work));
}
void Instance::JSCallInvoker::scheduleAsync(std::function<void()> &&work) {
if (auto strongNativeToJsBridge = m_nativeToJsBridge.lock()) {
strongNativeToJsBridge->runOnExecutorQueue(
[work = std::move(work)](JSExecutor *executor) {
work();
executor->flush();
});
}
}
} // namespace react

Просмотреть файл

@ -8,7 +8,9 @@
#pragma once
#include <condition_variable>
#include <list>
#include <memory>
#include <mutex>
#include <cxxreact/NativeToJsBridge.h>
@ -86,7 +88,45 @@ class RN_EXPORT Instance {
void handleMemoryPressure(int pressureLevel);
void invokeAsync(std::function<void()> &&func);
/**
* JS CallInvoker is used by TurboModules to schedule work on the JS thread.
*
* Why is the bridge creating JS CallInvoker?
*
* - After every Native -> JS call in the TurboModule system, the bridge
* needs to flush all queued NativeModule method calls. The bridge must
* also dispatch onBatchComplete if the queue of NativeModule method calls
* was not empty.
*/
std::shared_ptr<CallInvoker> getJSCallInvoker();
/**
* Native CallInvoker is used by TurboModules to schedule work on the
* NativeModule thread(s).
*
* Why is the bridge creating JS CallInvoker?
*
* - The bridge must be informed of all TurboModule async method calls. Why?
* When all queued NativeModule method calls are flushed by a call from
* Native -> JS, if that queue was non-zero in size, JsToNativeBridge
* dispatches onBatchComplete. When we turn our NativeModules to
* TurboModuels, there will be less and less pending NativeModule method
* calls, so onBatchComplete will not fire as often. Therefore, the bridge
* needs to know how many TurboModule async method calls have been completed
* since the last time the bridge was flushed. If this number is non-zero,
* we fire onBatchComplete.
*
* Why must we pass in a scheduleWork function?
*
* - On Android, we have one NativeModule thread. That thread is created and
* managed outisde of NativeToJsBridge. On iOS, we have one MethodQueue per
* module. Those MethodQueues are also created and managed outside of
* NativeToJsBridge. Therefore, we need to pass in a function that schedules
* work on the respective thread.
*
*/
std::shared_ptr<CallInvoker> getNativeCallInvoker(
std::function<void(std::function<void()> &&work)> &&scheduleWork);
private:
void callNativeModules(folly::dynamic &&calls, bool isEndOfBatch);
@ -100,12 +140,30 @@ class RN_EXPORT Instance {
std::string startupScriptSourceURL);
std::shared_ptr<InstanceCallback> callback_;
std::unique_ptr<NativeToJsBridge> nativeToJsBridge_;
std::shared_ptr<NativeToJsBridge> nativeToJsBridge_;
std::shared_ptr<ModuleRegistry> moduleRegistry_;
std::mutex m_syncMutex;
std::condition_variable m_syncCV;
bool m_syncReady = false;
class JSCallInvoker : public CallInvoker {
private:
std::weak_ptr<NativeToJsBridge> m_nativeToJsBridge;
std::mutex m_mutex;
bool m_shouldBuffer = true;
std::list<std::function<void()>> m_workBuffer;
void scheduleAsync(std::function<void()> &&work);
public:
void setNativeToJsBridgeAndFlushCalls(
std::weak_ptr<NativeToJsBridge> nativeToJsBridge);
void invokeAsync(std::function<void()> &&work) override;
};
std::shared_ptr<JSCallInvoker> jsCallInvoker_ =
std::make_shared<JSCallInvoker>();
};
} // namespace react

Просмотреть файл

@ -7,6 +7,7 @@
#include "NativeToJsBridge.h"
#include <ReactCommon/CallInvoker.h>
#include <folly/MoveWrapper.h>
#include <folly/json.h>
#include <glog/logging.h>
@ -42,7 +43,7 @@ class JsToNativeBridge : public react::ExecutorDelegate {
}
bool isBatchActive() {
return m_batchHadNativeModuleCalls;
return m_batchHadNativeModuleOrTurboModuleCalls;
}
void callNativeModules(
@ -51,7 +52,8 @@ class JsToNativeBridge : public react::ExecutorDelegate {
bool isEndOfBatch) override {
CHECK(m_registry || calls.empty())
<< "native module calls cannot be completed with no native modules";
m_batchHadNativeModuleCalls = m_batchHadNativeModuleCalls || !calls.empty();
m_batchHadNativeModuleOrTurboModuleCalls =
m_batchHadNativeModuleOrTurboModuleCalls || !calls.empty();
// An exception anywhere in here stops processing of the batch. This
// was the behavior of the Android bridge, and since exception handling
@ -65,9 +67,9 @@ class JsToNativeBridge : public react::ExecutorDelegate {
// decrementPendingJSCalls will be called sync. Be aware that the bridge
// may still be processing native calls when the bridge idle signaler
// fires.
if (m_batchHadNativeModuleCalls) {
if (m_batchHadNativeModuleOrTurboModuleCalls) {
m_callback->onBatchComplete();
m_batchHadNativeModuleCalls = false;
m_batchHadNativeModuleOrTurboModuleCalls = false;
}
m_callback->decrementPendingJSCalls();
}
@ -82,13 +84,17 @@ class JsToNativeBridge : public react::ExecutorDelegate {
moduleId, methodId, std::move(args));
}
void recordTurboModuleAsyncMethodCall() {
m_batchHadNativeModuleOrTurboModuleCalls = true;
}
private:
// These methods are always invoked from an Executor. The NativeToJsBridge
// keeps a reference to the executor, and when destroy() is called, the
// executor is destroyed synchronously on its queue.
std::shared_ptr<ModuleRegistry> m_registry;
std::shared_ptr<InstanceCallback> m_callback;
bool m_batchHadNativeModuleCalls = false;
bool m_batchHadNativeModuleOrTurboModuleCalls = false;
};
NativeToJsBridge::NativeToJsBridge(
@ -291,5 +297,31 @@ void NativeToJsBridge::runOnExecutorQueue(
});
}
std::shared_ptr<CallInvoker> NativeToJsBridge::getNativeCallInvoker(
std::function<void(std::function<void()> &&work)> &&scheduleWork) {
class NativeCallInvoker : public CallInvoker {
private:
std::weak_ptr<JsToNativeBridge> m_jsToNativeBridge;
std::function<void(std::function<void()> &&work)> m_scheduleWork;
public:
NativeCallInvoker(
std::weak_ptr<JsToNativeBridge> jsToNativeBridge,
std::function<void(std::function<void()> &&work)> &&scheduleWork)
: m_jsToNativeBridge(jsToNativeBridge),
m_scheduleWork(std::move(scheduleWork)) {}
void invokeAsync(std::function<void()> &&func) override {
if (auto strongJsToNativeBridge = m_jsToNativeBridge.lock()) {
strongJsToNativeBridge->recordTurboModuleAsyncMethodCall();
}
m_scheduleWork(std::move(func));
}
};
return std::make_shared<NativeCallInvoker>(
m_delegate, std::move(scheduleWork));
}
} // namespace react
} // namespace facebook

Просмотреть файл

@ -12,6 +12,7 @@
#include <map>
#include <vector>
#include <ReactCommon/CallInvoker.h>
#include <cxxreact/JSExecutor.h>
namespace folly {
@ -93,6 +94,13 @@ class NativeToJsBridge {
void runOnExecutorQueue(std::function<void(JSExecutor *)> task);
/**
* Native CallInvoker is used by TurboModules to schedule work on the
* NativeModule thread(s).
*/
std::shared_ptr<CallInvoker> getNativeCallInvoker(
std::function<void(std::function<void()> &&work)> &&scheduleWork);
private:
// This is used to avoid a race condition where a proxyCallback gets queued
// after ~NativeToJsBridge(), on the same thread. In that case, the callback

Просмотреть файл

@ -40,4 +40,5 @@ Pod::Spec.new do |s|
s.dependency "Folly", folly_version
s.dependency "glog"
s.dependency "React-jsinspector", version
s.dependency "React-callinvoker", version
end

Просмотреть файл

@ -212,9 +212,13 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule {
@end
// TODO: Consolidate this extension with the one in RCTSurfacePresenter.
/**
* These methods are all implemented by RCTCxxBridge, which subclasses RCTBridge. Hence, they must only be used in
* contexts where the concrete class of an RCTBridge instance is RCTCxxBridge. This happens, for example, when
* [RCTCxxBridgeDelegate jsExecutorFactoryForBridge:(RCTBridge *)] is invoked by RCTCxxBridge.
*
* TODO: Consolidate this extension with the one in RCTSurfacePresenter.
*/
@interface RCTBridge ()
- (std::weak_ptr<facebook::react::Instance>)reactInstance;
- (std::shared_ptr<facebook::react::CallInvoker>)jsCallInvoker;
@end

Просмотреть файл

@ -40,7 +40,7 @@ def use_react_native! (options={})
pod 'React-jsi', :path => "#{prefix}/ReactCommon/jsi"
pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor"
pod 'React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector"
pod 'ReactCommon/callinvoker', :path => "#{prefix}/ReactCommon"
pod 'React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker"
pod 'ReactCommon/turbomodule/core', :path => "#{prefix}/ReactCommon"
pod 'Yoga', :path => "#{prefix}/ReactCommon/yoga", :modular_headers => true