mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
281daf1222
Summary: The RuntimeExecutor that Fabric gets from the bridge doesn't call JSIExecutor::flush(). In the legacy NativeModule system, we're supposed to flush the queue of NativeModule calls after every call into JavaScript. The lack of this flushing means that we execute NativeModule calls less frequently with Fabric enabled, and TurboModules disabled. It also means that [the microtask checkpoints we placed inside JSIExecutor::flush()](https://www.internalfb.com/code/fbsource/[62f69606ae81530f7d6f0cba8466ac604934c901]/xplat/js/react-native-github/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp?lines=427%2C445) won't be executed as frequently, with Fabric enabled. Changelog: [Android][Fixed] - Flush NativeModule calls with Fabric on Android, on every Native -> JS call. Reviewed By: JoshuaGross, mdvacca Differential Revision: D28620982 fbshipit-source-id: ae4d1c16c62b6d4a5089e63104ad97f4ed44c440
332 lines
10 KiB
C++
332 lines
10 KiB
C++
/*
|
|
* 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 "Instance.h"
|
|
|
|
#include "ErrorUtils.h"
|
|
#include "JSBigString.h"
|
|
#include "JSBundleType.h"
|
|
#include "JSExecutor.h"
|
|
#include "MessageQueueThread.h"
|
|
#include "MethodCall.h"
|
|
#include "NativeToJsBridge.h"
|
|
#include "RAMBundleRegistry.h"
|
|
#include "RecoverableError.h"
|
|
#include "SystraceSection.h"
|
|
|
|
#include <cxxreact/JSIndexedRAMBundle.h>
|
|
#include <folly/MoveWrapper.h>
|
|
#include <folly/json.h>
|
|
|
|
#include <glog/logging.h>
|
|
|
|
#include <condition_variable>
|
|
#include <exception>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
|
|
namespace facebook {
|
|
namespace react {
|
|
|
|
Instance::~Instance() {
|
|
if (nativeToJsBridge_) {
|
|
nativeToJsBridge_->destroy();
|
|
}
|
|
}
|
|
|
|
void Instance::initializeBridge(
|
|
std::unique_ptr<InstanceCallback> callback,
|
|
std::shared_ptr<JSExecutorFactory> jsef,
|
|
std::shared_ptr<MessageQueueThread> jsQueue,
|
|
std::shared_ptr<ModuleRegistry> moduleRegistry) {
|
|
callback_ = std::move(callback);
|
|
moduleRegistry_ = std::move(moduleRegistry);
|
|
jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
|
|
nativeToJsBridge_ = std::make_shared<NativeToJsBridge>(
|
|
jsef.get(), moduleRegistry_, jsQueue, callback_);
|
|
|
|
nativeToJsBridge_->initializeRuntime();
|
|
|
|
/**
|
|
* 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();
|
|
});
|
|
|
|
CHECK(nativeToJsBridge_);
|
|
}
|
|
|
|
void Instance::loadBundle(
|
|
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
|
|
std::unique_ptr<const JSBigString> string,
|
|
std::string sourceURL) {
|
|
callback_->incrementPendingJSCalls();
|
|
SystraceSection s("Instance::loadBundle", "sourceURL", sourceURL);
|
|
nativeToJsBridge_->loadBundle(
|
|
std::move(bundleRegistry), std::move(string), std::move(sourceURL));
|
|
}
|
|
|
|
void Instance::loadBundleSync(
|
|
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
|
|
std::unique_ptr<const JSBigString> string,
|
|
std::string sourceURL) {
|
|
std::unique_lock<std::mutex> lock(m_syncMutex);
|
|
m_syncCV.wait(lock, [this] { return m_syncReady; });
|
|
|
|
SystraceSection s("Instance::loadBundleSync", "sourceURL", sourceURL);
|
|
nativeToJsBridge_->loadBundleSync(
|
|
std::move(bundleRegistry), std::move(string), std::move(sourceURL));
|
|
}
|
|
|
|
void Instance::setSourceURL(std::string sourceURL) {
|
|
callback_->incrementPendingJSCalls();
|
|
SystraceSection s("Instance::setSourceURL", "sourceURL", sourceURL);
|
|
|
|
nativeToJsBridge_->loadBundle(nullptr, nullptr, std::move(sourceURL));
|
|
}
|
|
|
|
void Instance::loadScriptFromString(
|
|
std::unique_ptr<const JSBigString> string,
|
|
std::string sourceURL,
|
|
bool loadSynchronously) {
|
|
SystraceSection s("Instance::loadScriptFromString", "sourceURL", sourceURL);
|
|
if (loadSynchronously) {
|
|
loadBundleSync(nullptr, std::move(string), std::move(sourceURL));
|
|
} else {
|
|
loadBundle(nullptr, std::move(string), std::move(sourceURL));
|
|
}
|
|
}
|
|
|
|
bool Instance::isHBCBundle(const char *sourcePath) {
|
|
std::ifstream bundle_stream(sourcePath, std::ios_base::in);
|
|
BundleHeader header;
|
|
|
|
if (!bundle_stream ||
|
|
!bundle_stream.read(reinterpret_cast<char *>(&header), sizeof(header))) {
|
|
return false;
|
|
}
|
|
|
|
return parseTypeFromHeader(header) == ScriptTag::HBCBundle;
|
|
}
|
|
|
|
bool Instance::isIndexedRAMBundle(const char *sourcePath) {
|
|
std::ifstream bundle_stream(sourcePath, std::ios_base::in);
|
|
BundleHeader header;
|
|
|
|
if (!bundle_stream ||
|
|
!bundle_stream.read(reinterpret_cast<char *>(&header), sizeof(header))) {
|
|
return false;
|
|
}
|
|
|
|
return parseTypeFromHeader(header) == ScriptTag::RAMBundle;
|
|
}
|
|
|
|
bool Instance::isIndexedRAMBundle(std::unique_ptr<const JSBigString> *script) {
|
|
BundleHeader header;
|
|
strncpy(
|
|
reinterpret_cast<char *>(&header),
|
|
script->get()->c_str(),
|
|
sizeof(header));
|
|
|
|
return parseTypeFromHeader(header) == ScriptTag::RAMBundle;
|
|
}
|
|
|
|
void Instance::loadRAMBundleFromString(
|
|
std::unique_ptr<const JSBigString> script,
|
|
const std::string &sourceURL) {
|
|
auto bundle = std::make_unique<JSIndexedRAMBundle>(std::move(script));
|
|
auto startupScript = bundle->getStartupCode();
|
|
auto registry = RAMBundleRegistry::singleBundleRegistry(std::move(bundle));
|
|
loadRAMBundle(std::move(registry), std::move(startupScript), sourceURL, true);
|
|
}
|
|
|
|
void Instance::loadRAMBundleFromFile(
|
|
const std::string &sourcePath,
|
|
const std::string &sourceURL,
|
|
bool loadSynchronously) {
|
|
auto bundle = std::make_unique<JSIndexedRAMBundle>(sourcePath.c_str());
|
|
auto startupScript = bundle->getStartupCode();
|
|
auto registry = RAMBundleRegistry::multipleBundlesRegistry(
|
|
std::move(bundle), JSIndexedRAMBundle::buildFactory());
|
|
loadRAMBundle(
|
|
std::move(registry),
|
|
std::move(startupScript),
|
|
sourceURL,
|
|
loadSynchronously);
|
|
}
|
|
|
|
void Instance::loadRAMBundle(
|
|
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
|
|
std::unique_ptr<const JSBigString> startupScript,
|
|
std::string startupScriptSourceURL,
|
|
bool loadSynchronously) {
|
|
if (loadSynchronously) {
|
|
loadBundleSync(
|
|
std::move(bundleRegistry),
|
|
std::move(startupScript),
|
|
std::move(startupScriptSourceURL));
|
|
} else {
|
|
loadBundle(
|
|
std::move(bundleRegistry),
|
|
std::move(startupScript),
|
|
std::move(startupScriptSourceURL));
|
|
}
|
|
}
|
|
|
|
void Instance::setGlobalVariable(
|
|
std::string propName,
|
|
std::unique_ptr<const JSBigString> jsonValue) {
|
|
nativeToJsBridge_->setGlobalVariable(
|
|
std::move(propName), std::move(jsonValue));
|
|
}
|
|
|
|
void *Instance::getJavaScriptContext() {
|
|
return nativeToJsBridge_ ? nativeToJsBridge_->getJavaScriptContext()
|
|
: nullptr;
|
|
}
|
|
|
|
bool Instance::isInspectable() {
|
|
return nativeToJsBridge_ ? nativeToJsBridge_->isInspectable() : false;
|
|
}
|
|
|
|
bool Instance::isBatchActive() {
|
|
return nativeToJsBridge_ ? nativeToJsBridge_->isBatchActive() : false;
|
|
}
|
|
|
|
void Instance::callJSFunction(
|
|
std::string &&module,
|
|
std::string &&method,
|
|
folly::dynamic &¶ms) {
|
|
callback_->incrementPendingJSCalls();
|
|
nativeToJsBridge_->callFunction(
|
|
std::move(module), std::move(method), std::move(params));
|
|
}
|
|
|
|
void Instance::callJSCallback(uint64_t callbackId, folly::dynamic &¶ms) {
|
|
SystraceSection s("Instance::callJSCallback");
|
|
callback_->incrementPendingJSCalls();
|
|
nativeToJsBridge_->invokeCallback((double)callbackId, std::move(params));
|
|
}
|
|
|
|
void Instance::registerBundle(
|
|
uint32_t bundleId,
|
|
const std::string &bundlePath) {
|
|
nativeToJsBridge_->registerBundle(bundleId, bundlePath);
|
|
}
|
|
|
|
const ModuleRegistry &Instance::getModuleRegistry() const {
|
|
return *moduleRegistry_;
|
|
}
|
|
|
|
ModuleRegistry &Instance::getModuleRegistry() {
|
|
return *moduleRegistry_;
|
|
}
|
|
|
|
void Instance::handleMemoryPressure(int pressureLevel) {
|
|
if (nativeToJsBridge_) {
|
|
// This class resets `nativeToJsBridge_` only in the destructor,
|
|
// hence a race is not possible there.
|
|
nativeToJsBridge_->handleMemoryPressure(pressureLevel);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<CallInvoker> Instance::getJSCallInvoker() {
|
|
return std::static_pointer_cast<CallInvoker>(jsCallInvoker_);
|
|
}
|
|
|
|
RuntimeExecutor Instance::getRuntimeExecutor() {
|
|
std::weak_ptr<NativeToJsBridge> weakNativeToJsBridge = nativeToJsBridge_;
|
|
|
|
auto runtimeExecutor =
|
|
[weakNativeToJsBridge](
|
|
std::function<void(jsi::Runtime & runtime)> &&callback) {
|
|
if (auto strongNativeToJsBridge = weakNativeToJsBridge.lock()) {
|
|
strongNativeToJsBridge->runOnExecutorQueue(
|
|
[callback = std::move(callback)](JSExecutor *executor) {
|
|
jsi::Runtime *runtime =
|
|
(jsi::Runtime *)executor->getJavaScriptContext();
|
|
try {
|
|
callback(*runtime);
|
|
executor->flush();
|
|
} catch (jsi::JSError &originalError) {
|
|
handleJSError(*runtime, originalError, true);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
return runtimeExecutor;
|
|
}
|
|
|
|
std::shared_ptr<CallInvoker> Instance::getDecoratedNativeCallInvoker(
|
|
std::shared_ptr<CallInvoker> nativeInvoker) {
|
|
return nativeToJsBridge_->getDecoratedNativeCallInvoker(nativeInvoker);
|
|
}
|
|
|
|
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::invokeSync(std::function<void()> &&work) {
|
|
// TODO: Replace JS Callinvoker with RuntimeExecutor.
|
|
throw std::runtime_error(
|
|
"Synchronous native -> JS calls are currently not supported.");
|
|
}
|
|
|
|
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 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
|
|
} // namespace facebook
|