Make async calls work

Summary:
JSCallInvoker requires a `std::weak_ptr<Instance>` to create. In our C++, `CatalystInstance` is responsible for creating this `Instance` object. This `CatalystInstance` C++ initialization is separate from the `TurboModuleManager` C++ initialization. Therefore, in this diff, I made `CatalystInstance` responsible for creating the `JSCallInvoker`. It then exposes the `JSCallInvoker` using a hybrid class called `JSCallInvokerHolder`, which contains a `std::shared_ptr<JSCallInvoker>` member variable. Using `CatalystInstance.getJSCallInvokerHolder()` in TurboModuleManager.java, we get a handle to this hybrid container. Then, we pass it this hybrid object to `TurboModuleManager::initHybrid`, which retrieves the `std::shared_ptr<JSCallInvoker>` from the `JavaJSCallInvokerHandler`.

There were a few cyclic dependencies, so I had to break down the buck targets:
- `CatalystInstanceImpl.java` depends on `JSCallInvokerHolderImpl.java`, and `TurboModuleManager.java` depends on classes that are packaged with `CatalystInstanceImpl.java`. So, I had to put `JSCallInvokerHolderImpl.java` in its own buck target.
- `CatalystInstance.cpp` depends on `JavaJSCallInvokerHolder.cpp`, and `TurboModuleManager.cpp` depends on classes that are build with `CatalystInstance.cpp`. So, I had to put `JavaJSCallInvokerHolder.cpp` in its own buck target. To make things simpler, I also moved `JSCallInvoker.{cpp,h}` files into the same buck target as `JavaJSCallInvokerHolder.{cpp,h}`.

I think these steps should be enough to create the TurboModuleManager without needing a bridge:
1. Make `JSCallInvoker` an abstract base class.
2. On Android, create another derived class of `JSCallInvoker` that doesn't depend on Instance.
3. Create `JavaJSCallInvokerHolder` using an instance of this new class somewhere in C++.
4. Pass this instance of `JavaJSCallInvokerHolder` to Java and use it to create/instatiate `TurboModuleManager`.

Regarding steps 1 and 2, we can also make JSCallInvoker accept a lambda.

Reviewed By: mdvacca

Differential Revision: D15055511

fbshipit-source-id: 0ad72a86599819ec35d421dbee7e140959a26ab6
This commit is contained in:
Ramanpreet Nara
2019-05-03 13:25:56 -07:00
committed by Facebook Github Bot
parent 01523ae660
commit ef4955fefe
22 changed files with 266 additions and 42 deletions

View File

@@ -47,6 +47,7 @@ rn_android_library(
react_native_target("java/com/facebook/react/uimanager/common:common"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/turbomodule/core/interfaces:interfaces"),
react_native_target("java/com/facebook/react/turbomodule/core:jscallinvokerholder"),
] + ([react_native_target("jni/react/jni:jni")] if not IS_OSS_BUILD else []),
exported_deps = [
react_native_dep("java/com/facebook/jni:jni"),

View File

@@ -10,6 +10,7 @@ package com.facebook.react.bridge;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.turbomodule.core.interfaces.JSCallInvokerHolder;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
@@ -103,4 +104,10 @@ public interface CatalystInstance
JavaScriptContextHolder getJavaScriptContextHolder();
void addJSIModules(List<JSIModuleSpec> jsiModules);
/**
* Returns a hybrid object that contains a pointer to JSCallInvoker.
* Required for TurboModuleManager initialization.
*/
JSCallInvokerHolder getJSCallInvokerHolder();
}

View File

@@ -24,6 +24,7 @@ import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.turbomodule.core.JSCallInvokerHolderImpl;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.TraceListener;
import java.lang.annotation.Annotation;
@@ -99,6 +100,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
// C++ parts
private final HybridData mHybridData;
private native static HybridData initHybrid();
public native JSCallInvokerHolderImpl getJSCallInvokerHolder();
private CatalystInstanceImpl(
final ReactQueueConfigurationSpec reactQueueConfigurationSpec,

View File

@@ -6,6 +6,7 @@ rn_android_library(
[
"*.java",
],
exclude = ["JSCallInvokerHolderImpl.java"],
),
required_for_source_only_abi = True,
visibility = [
@@ -21,5 +22,20 @@ rn_android_library(
react_native_target("java/com/facebook/debug/holder:holder"),
react_native_target("java/com/facebook/react/bridge:interfaces"),
react_native_target("java/com/facebook/react/bridge:bridge"),
":jscallinvokerholder",
],
)
rn_android_library(
name = "jscallinvokerholder",
srcs = ["JSCallInvokerHolderImpl.java"],
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
react_native_target("java/com/facebook/react/turbomodule/core/interfaces:interfaces"),
react_native_dep("libraries/fbjni:java"),
],
)

View File

@@ -0,0 +1,30 @@
/**
* 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.
*/
package com.facebook.react.turbomodule.core;
import com.facebook.jni.HybridData;
import com.facebook.soloader.SoLoader;
import com.facebook.react.turbomodule.core.interfaces.JSCallInvokerHolder;
/**
* JSCallInvoker is created at a different time/place (i.e: in CatalystInstance)
* than TurboModuleManager. Therefore, we need to wrap JSCallInvoker
* within a hybrid class so that we may pass it from CatalystInstance, through
* Java, to TurboMoudleManager::initHybrid.
*/
public class JSCallInvokerHolderImpl implements JSCallInvokerHolder {
static {
SoLoader.loadLibrary("turbomodulejsijni");
}
private final HybridData mHybridData;
private JSCallInvokerHolderImpl(HybridData hd) {
mHybridData = hd;
}
}

View File

@@ -36,12 +36,11 @@ public class TurboModuleManager implements JSIModule {
public TurboModuleManager(
ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext, ModuleProvider moduleProvider) {
mReactApplicationContext = reactApplicationContext;
MessageQueueThread jsMessageQueueThread =
mReactApplicationContext
JSCallInvokerHolderImpl instanceHolder =
(JSCallInvokerHolderImpl) mReactApplicationContext
.getCatalystInstance()
.getReactQueueConfiguration()
.getJSQueueThread();
mHybridData = initHybrid(jsContext.get(), jsMessageQueueThread);
.getJSCallInvokerHolder();
mHybridData = initHybrid(jsContext.get(), instanceHolder);
mModuleProvider = moduleProvider;
}
@@ -51,7 +50,7 @@ public class TurboModuleManager implements JSIModule {
return mModuleProvider.getModule(name, mReactApplicationContext);
}
protected native HybridData initHybrid(long jsContext, MessageQueueThread jsQueue);
protected native HybridData initHybrid(long jsContext, JSCallInvokerHolderImpl jsQueue);
protected native void installJSIBindings();

View File

@@ -2,9 +2,9 @@ load("//tools/build_defs/oss:rn_defs.bzl", "rn_android_library")
rn_android_library(
name = "interfaces",
srcs = [
"TurboModule.java",
],
srcs = glob(
["*.java"],
),
required_for_source_only_abi = True,
visibility = [
"PUBLIC",

View File

@@ -0,0 +1,21 @@
/**
* 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.
*/
package com.facebook.react.turbomodule.core.interfaces;
/**
* JSCallInvoker is created by CatalystInstance.cpp, but used by
* TurboModuleManager.cpp. Both C++ classes are instantiated at different
* times/places. Therefore, to pass the JSCallInvoker instance from
* CatalystInstance to TurboModuleManager, we make it take a trip through
* Java.
*
* This interface represents the opaque Java object that contains a pointer to
* and instance of JSCallInvoker.
*/
public interface JSCallInvokerHolder {
}

View File

@@ -1,24 +1,34 @@
load("@fbsource//tools/build_defs:glob_defs.bzl", "subdir_glob")
load("@fbsource//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library")
load("@fbsource//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library")
rn_xplat_cxx_library(
name = "jni",
srcs = glob(["**/*.cpp"]),
platforms = ANDROID,
preferred_linkage = "static",
visibility = [
"PUBLIC",
],
exported_deps = [
":turbomodulemanager",
],
)
rn_xplat_cxx_library(
name = "turbomodulemanager",
srcs = [
"TurboModuleManager.cpp",
],
header_namespace = "",
exported_headers = subdir_glob(
[
("", "**/*.h"),
],
prefix = "jsireact",
),
exported_headers = {
"jsireact/TurboModuleManager.h": "TurboModuleManager.h",
},
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
force_static = True,
platforms = ANDROID,
preferred_linkage = "static",
preprocessor_flags = [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
@@ -30,6 +40,7 @@ rn_xplat_cxx_library(
react_native_target("jni/react/jni:jni"),
"fbsource//xplat/jsi:JSIDynamic",
"fbsource//xplat/jsi:jsi",
":jscallinvokerholder",
],
exported_deps = [
"fbsource//xplat/jsi:jsi",
@@ -37,3 +48,38 @@ rn_xplat_cxx_library(
react_native_xplat_target("turbomodule/core:core"),
],
)
rn_xplat_cxx_library(
name = "jscallinvokerholder",
srcs = [
"JSCallInvokerHolder.cpp",
],
header_namespace = "",
exported_headers = {
"jsireact/JSCallInvokerHolder.h": "JSCallInvokerHolder.h",
},
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
fbandroid_deps = [
FBJNI_TARGET,
],
platforms = ANDROID,
preferred_linkage = "static",
preprocessor_flags = [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
],
visibility = [
"PUBLIC",
],
deps = [
react_native_xplat_target("cxxreact:bridge"),
],
exported_deps = [
react_native_xplat_target("jscallinvoker:jscallinvoker"),
],
)

View File

@@ -0,0 +1,17 @@
#include "JSCallInvokerHolder.h"
namespace facebook {
namespace react {
JSCallInvokerHolder::JSCallInvokerHolder(
std::shared_ptr<JSCallInvoker> jsCallInvoker)
: _jsCallInvoker(jsCallInvoker) {}
std::shared_ptr<JSCallInvoker> JSCallInvokerHolder::getJSCallInvoker() {
return _jsCallInvoker;
}
void JSCallInvokerHolder::registerNatives() {}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,33 @@
/**
* 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 <fb/fbjni.h>
#include <jsireact/JSCallInvoker.h>
#include <memory>
namespace facebook {
namespace react {
class JSCallInvokerHolder
: public jni::HybridClass<JSCallInvokerHolder> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/facebook/react/turbomodule/core/JSCallInvokerHolderImpl;";
static void registerNatives();
std::shared_ptr<JSCallInvoker> getJSCallInvoker();
private:
friend HybridBase;
JSCallInvokerHolder(std::shared_ptr<JSCallInvoker> jsCallInvoker);
std::shared_ptr<JSCallInvoker> _jsCallInvoker;
};
} // namespace react
} // namespace facebook

View File

@@ -26,20 +26,21 @@ static JTurboModuleProviderFunctionType moduleProvider_ = nullptr;
TurboModuleManager::TurboModuleManager(
jni::alias_ref<TurboModuleManager::javaobject> jThis,
jsi::Runtime* rt,
std::shared_ptr<JMessageQueueThread> jsMessageQueueThread
std::shared_ptr<JSCallInvoker> jsCallInvoker
):
javaPart_(make_global(jThis)),
javaPart_(jni::make_global(jThis)),
runtime_(rt),
jsMessageQueueThread_(jsMessageQueueThread)
jsCallInvoker_(jsCallInvoker)
{}
jni::local_ref<TurboModuleManager::jhybriddata> TurboModuleManager::initHybrid(
jni::alias_ref<jhybridobject> jThis,
jlong jsContext,
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue
jni::alias_ref<JSCallInvokerHolder::javaobject> jsCallInvokerHolder
) {
auto sharedJSMessageQueueThread = std::make_shared<JMessageQueueThread> (jsQueue);
return makeCxxInstance(jThis, (jsi::Runtime *) jsContext, sharedJSMessageQueueThread);
auto jsCallInvoker = jsCallInvokerHolder->cthis()->getJSCallInvoker();
return makeCxxInstance(jThis, (jsi::Runtime *) jsContext, jsCallInvoker);
}
void TurboModuleManager::registerNatives() {
@@ -56,11 +57,7 @@ void TurboModuleManager::installJSIBindings() {
TurboModuleBinding::install(*runtime_, std::make_shared<TurboModuleBinding>(
[this](const std::string &name) {
const auto moduleInstance = getJavaModule(name);
// TODO: Pass in react Instance to JSCallInvoker instead.
std::shared_ptr<Instance> instance = nullptr;
std::weak_ptr<Instance> weakInstance = instance;
const auto jsInvoker = std::make_shared<react::JSCallInvoker>(weakInstance);
return moduleProvider_(name, moduleInstance, jsInvoker);
return moduleProvider_(name, moduleInstance, jsCallInvoker_);
})
);
}

View File

@@ -13,6 +13,7 @@
#include <jsireact/TurboModule.h>
#include <jsireact/JavaTurboModule.h>
#include <react/jni/JMessageQueueThread.h>
#include <jsireact/JSCallInvokerHolder.h>
namespace facebook {
namespace react {
@@ -26,7 +27,7 @@ public:
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jhybridobject> jThis,
jlong jsContext,
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue
jni::alias_ref<JSCallInvokerHolder::javaobject> jsCallInvokerHolder
);
static void registerNatives();
static void setModuleProvider(JTurboModuleProviderFunctionType moduleProvider);
@@ -34,14 +35,14 @@ private:
friend HybridBase;
jni::global_ref<TurboModuleManager::javaobject> javaPart_;
jsi::Runtime* runtime_;
std::shared_ptr<JMessageQueueThread> jsMessageQueueThread_;
std::shared_ptr<JSCallInvoker> jsCallInvoker_;
jni::global_ref<JTurboModule> getJavaModule(std::string name);
void installJSIBindings();
explicit TurboModuleManager(
jni::alias_ref<TurboModuleManager::jhybridobject> jThis,
jsi::Runtime* rt,
std::shared_ptr<JMessageQueueThread> jsMessageQueueThread
jsi::Runtime *rt,
std::shared_ptr<JSCallInvoker> jsCallInvoker
);
};

View File

@@ -1,4 +1,4 @@
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "IS_OSS_BUILD", "react_native_xplat_dep", "react_native_xplat_target", "rn_xplat_cxx_library")
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "IS_OSS_BUILD", "react_native_target", "react_native_xplat_dep", "react_native_xplat_target", "rn_xplat_cxx_library")
EXPORTED_HEADERS = [
"CxxModuleWrapper.h",
@@ -58,6 +58,7 @@ rn_xplat_cxx_library(
"fbsource//xplat/folly:molly",
"fbandroid//xplat/fbgloginit:fbgloginit",
"fbsource//xplat/fbsystrace:fbsystrace",
react_native_target("java/com/facebook/react/turbomodule/core/jni:jscallinvokerholder"),
react_native_xplat_target("cxxreact:bridge"),
react_native_xplat_target("cxxreact:jsbigstring"),
react_native_xplat_target("cxxreact:module"),

View File

@@ -26,6 +26,7 @@
#include <folly/Memory.h>
#include <jni/Countable.h>
#include <jni/LocalReference.h>
#include <jsireact/JSCallInvokerHolder.h>
#include "CxxModuleWrapper.h"
#include "JavaScriptExecutorHolder.h"
@@ -111,6 +112,7 @@ void CatalystInstanceImpl::registerNatives() {
makeNativeMethod("jniCallJSCallback", CatalystInstanceImpl::jniCallJSCallback),
makeNativeMethod("setGlobalVariable", CatalystInstanceImpl::setGlobalVariable),
makeNativeMethod("getJavaScriptContext", CatalystInstanceImpl::getJavaScriptContext),
makeNativeMethod("getJSCallInvokerHolder", CatalystInstanceImpl::getJSCallInvokerHolder),
makeNativeMethod("jniHandleMemoryPressure", CatalystInstanceImpl::handleMemoryPressure),
});
@@ -264,4 +266,13 @@ void CatalystInstanceImpl::handleMemoryPressure(int pressureLevel) {
instance_->handleMemoryPressure(pressureLevel);
}
jni::alias_ref<JSCallInvokerHolder::javaobject> CatalystInstanceImpl::getJSCallInvokerHolder() {
if (javaInstanceHolder_ == nullptr) {
jsCallInvoker_ = std::make_shared<JSCallInvoker>(instance_);
javaInstanceHolder_ = jni::make_global(JSCallInvokerHolder::newObjectCxxArgs(jsCallInvoker_));
}
return javaInstanceHolder_;
}
}}

View File

@@ -7,6 +7,7 @@
#include <fb/fbjni.h>
#include <folly/Memory.h>
#include <jsireact/JSCallInvokerHolder.h>
#include "CxxModuleWrapper.h"
#include "JavaModuleWrapper.h"
@@ -73,6 +74,7 @@ class CatalystInstanceImpl : public jni::HybridClass<CatalystInstanceImpl> {
void jniLoadScriptFromDeltaBundle(const std::string& sourceURL, jni::alias_ref<NativeDeltaClient::jhybridobject> deltaClient, bool loadSynchronously);
void jniCallJSFunction(std::string module, std::string method, NativeArray* arguments);
void jniCallJSCallback(jint callbackId, NativeArray* arguments);
jni::alias_ref<JSCallInvokerHolder::javaobject> getJSCallInvokerHolder();
void setGlobalVariable(std::string propName,
std::string&& jsonValue);
jlong getJavaScriptContext();
@@ -83,6 +85,8 @@ class CatalystInstanceImpl : public jni::HybridClass<CatalystInstanceImpl> {
std::shared_ptr<Instance> instance_;
std::shared_ptr<ModuleRegistry> moduleRegistry_;
std::shared_ptr<JMessageQueueThread> moduleMessageQueue_;
jni::global_ref<JSCallInvokerHolder::javaobject> javaInstanceHolder_;
std::shared_ptr<JSCallInvoker> jsCallInvoker_;
};
}}

View File

@@ -0,0 +1,36 @@
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "FBJNI_TARGET", "react_native_xplat_target", "rn_xplat_cxx_library", "subdir_glob")
rn_xplat_cxx_library(
name = "jscallinvoker",
srcs = glob(
["*.cpp"],
),
header_namespace = "",
exported_headers = subdir_glob(
[
("", "*.h"),
],
prefix = "jsireact",
),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
fbandroid_deps = [
FBJNI_TARGET,
],
platforms = (ANDROID, APPLE),
preferred_linkage = "static",
preprocessor_flags = [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
],
visibility = [
"PUBLIC",
],
deps = [
react_native_xplat_target("cxxreact:bridge"),
],
)

View File

@@ -5,17 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/
#include "JSCallInvoker.h"
#include <jsireact/JSCallInvoker.h>
#include <cxxreact/Instance.h>
namespace facebook {
namespace react {
JSCallInvoker::JSCallInvoker(std::weak_ptr<Instance> reactInstance)
: reactInstance_(reactInstance) {}
: reactInstance_(reactInstance) {}
void JSCallInvoker::invokeAsync(std::function<void()>&& func) {
void JSCallInvoker::invokeAsync(std::function<void()> &&func) {
auto instance = reactInstance_.lock();
if (instance == nullptr) {
return;

View File

@@ -3,7 +3,9 @@ load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "react_native_tar
rn_xplat_cxx_library(
name = "core",
srcs = glob(["*.cpp"]),
srcs = glob(
["*.cpp"],
),
header_namespace = "",
exported_headers = subdir_glob(
[
@@ -76,6 +78,7 @@ rn_xplat_cxx_library(
"fbsource//xplat/third-party/glog:glog",
react_native_xplat_target("cxxreact:bridge"),
react_native_xplat_target("cxxreact:module"),
react_native_xplat_target("jscallinvoker:jscallinvoker"),
],
exported_deps = [
"fbsource//xplat/jsi:jsi",

View File

@@ -12,7 +12,7 @@
#include <jsi/jsi.h>
#include "JSCallInvoker.h"
#include <jsireact/JSCallInvoker.h>
namespace facebook {
namespace react {

View File

@@ -11,7 +11,7 @@
#include <jsi/jsi.h>
#include "JSCallInvoker.h"
#include <jsireact/JSCallInvoker.h>
using namespace facebook;