Skip to content

Commit

Permalink
3. TurboModules!
Browse files Browse the repository at this point in the history
  • Loading branch information
barthap committed May 19, 2021
1 parent 592ec39 commit 5244ab8
Show file tree
Hide file tree
Showing 14 changed files with 225 additions and 36 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
A journey to discover RN TurboModules. Each commit is a different stage towards a fully featured Turbo Module.

0. Basic initialization and boilerplate (use `npx create-react-native-library` with C++ template)
1. _TODO:_ Adding `fbjni` for Android - it greatly improves Java - C++ interop.
2. _TODO:_ Migrating from legacy RN bridge to JSI.
1. Adding `fbjni` for Android - it greatly improves Java - C++ interop.
2. Migrating from legacy RN bridge to JSI.
3. _TODO:_ Implementing the "Real" Turbo Module.
4. _TODO:_ Calling Kotlin/Swift code from C++ module.
5. _TODO:_ Multithreading / asynchronous operations
Expand Down
21 changes: 21 additions & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ include_directories(
../cpp
../node_modules/react-native/React
../node_modules/react-native/React/Base
../node_modules/react-native/ReactCommon
../node_modules/react-native/ReactCommon/jsi
../node_modules/react-native/ReactCommon/callinvoker
../node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon
../node_modules/react-native/ReactCommon/turbomodule/core
../node_modules/react-native/ReactCommon/turbomodule
../node_modules/react-native/React
../node_modules/react-native/React/Base
../node_modules/react-native/ReactCommon/jsi
)

Expand All @@ -22,9 +30,22 @@ find_package(fbjni REQUIRED CONFIG)
add_library(${PACKAGE_NAME}
SHARED
../node_modules/react-native/ReactCommon/jsi/jsi/jsi.cpp
../node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/CallInvokerHolder.cpp
../node_modules/react-native/ReactCommon/turbomodule/core/TurboModule.cpp
../cpp/TurboUtilsModule.cpp
cpp-adapter.cpp
)

# link fbjni and logger to my_turbo_utils
target_link_libraries(${PACKAGE_NAME} fbjni::fbjni android log)

# A workaround to avoid runtime error:
# E/SoLoader: couldn't find DSO to load: libturbomodulejsijni.so
add_library(
turbomodulejsijni
# Sets the library as a shared library.
SHARED
# an empty cpp file
../cpp/empty.cpp
)

2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:4.2.0'
classpath 'com.android.tools.build:gradle:4.2.1'
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.50"
}
Expand Down
16 changes: 12 additions & 4 deletions android/cpp-adapter.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#include <jni.h>
#include <CallInvokerHolder.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>

#include "TurboUtilsModule.h"
#include "Logging.h"

#include <fbjni/fbjni.h>

#include <jsi/jsi.h>

using namespace facebook;

Expand Down Expand Up @@ -43,10 +44,17 @@ struct NativeProxy : jni::JavaClass<NativeProxy> {
}

private:
static void installNativeJsi(jni::alias_ref<jni::JObject> thiz, jlong jsiRuntimePtr) {
static void installNativeJsi(jni::alias_ref<jni::JObject> thiz,
jlong jsiRuntimePtr,
jni::alias_ref<react::CallInvokerHolder::javaobject> jsCallInvokerHolder) {
auto jsiRuntime = reinterpret_cast<jsi::Runtime*>(jsiRuntimePtr);
auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker();

// initialize jsi module
turboutils::installJsi(*jsiRuntime);

// initialize turbo module
turboutils::installTurboModule(*jsiRuntime, jsCallInvoker);
}

private:
Expand Down
9 changes: 7 additions & 2 deletions android/src/main/java/com/myturboutils/NativeProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
// just not to make mess in MyTurboUtilsModule
// c++ counterparts are in cpp-adapter.cpp

import android.util.Log;

import com.facebook.react.bridge.ReactContext;
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;

// see https://github.com/facebookincubator/fbjni/blob/master/docs/quickref.md
public class NativeProxy {
Expand All @@ -14,10 +17,12 @@ public class NativeProxy {
public static native String nativeMakeGreeting(String name);

/*** jsi stuff ***/ //these could be static as well
private native void installNativeJsi(long jsContextNativePointer);
private native void installNativeJsi(long jsContextNativePointer, CallInvokerHolderImpl jsCallInvokerHolder);

public void installJsi(ReactContext context) {
Log.d("Turboutils", "Installing native...");
CallInvokerHolderImpl holder = (CallInvokerHolderImpl)context.getCatalystInstance().getJSCallInvokerHolder();
long contextPointer = context.getJavaScriptContextHolder().get();
installNativeJsi(contextPointer);
installNativeJsi(contextPointer, holder);
}
}
58 changes: 58 additions & 0 deletions cpp/TurboUtilsModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,65 @@ namespace turboutils
return a*a + b*b;
}

/*** TurboModule implementation ***/
void installTurboModule(jsi::Runtime& runtime, std::shared_ptr<react::CallInvoker> jsCallInvoker) {
std::shared_ptr<UtilsTurboModule> nativeModule =
std::make_shared<UtilsTurboModule>(jsCallInvoker);

// register UtilsTurboModule instance as global._myUtilsTurboModule
runtime.global().setProperty(
runtime,
jsi::PropNameID::forAscii(runtime, "_myUtilsTurboModule"),
jsi::Object::createFromHostObject(runtime, nativeModule));
}

static jsi::Value
_hostFunction_TurboUtilsSpecJSI_nativeSumSquares(jsi::Runtime &rt, react::TurboModule &turboModule,
const jsi::Value *args, size_t arg_count) {
// We can do input validation here
if (arg_count != 2 || !args[0].isNumber() || !args[1].isNumber()) {
jsi::detail::throwJSError(rt, "Invalid input for sumSquares()");
return {};
}
auto a = args[0].asNumber();
auto b = args[1].asNumber();

LOG("Calling UtilsTurboModule.sumSquares(%f, %f)", a, b);
return dynamic_cast<TurboUtilsSpecJSI *>(&turboModule)->nativeSumSquares(rt, a, b);
}

static jsi::Value _hostFunction_TurboUtilsSpecJSI_nativeGreeting(jsi::Runtime &rt, react::TurboModule &turboModule,
const jsi::Value *args, size_t arg_count) {
if (arg_count != 1 || !args[0].isString()) {
jsi::detail::throwJSError(rt, "Invalid input for makeGreetingFor()");
return {};
}
return dynamic_cast<TurboUtilsSpecJSI *>(&turboModule)->nativeGreeting(rt, args[0].getString(rt));
}

TurboUtilsSpecJSI::TurboUtilsSpecJSI(std::shared_ptr<react::CallInvoker> jsInvoker)
: TurboModule("UtilsTurboModule", jsInvoker) {
//here we assign our TurboModule object properties
LOG("Initializing TurboModule MethodMap");
methodMap_["makeGreetingFor"] = MethodMetadata{1, _hostFunction_TurboUtilsSpecJSI_nativeGreeting};
methodMap_["sumSquares"] = MethodMetadata{2, _hostFunction_TurboUtilsSpecJSI_nativeSumSquares};
}

/**************************************************************************/

jsi::String UtilsTurboModule::nativeGreeting(jsi::Runtime &rt, const jsi::String &name) {
auto msg = std::string("Greeting not implemented yet for ") + name.utf8(rt);
return jsi::String::createFromAscii(rt, msg);
}

jsi::Value UtilsTurboModule::nativeSumSquares(jsi::Runtime &rt, double a, double b) {
return jsi::Value(sumSquares(a, b));
}

/**************************************************************************/


// JSI non-turbomodule implementation
void installJsi(jsi::Runtime& rt) {
LOG("Installing JSI...");

Expand Down
35 changes: 34 additions & 1 deletion cpp/TurboUtilsModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,43 @@

#include <jsi/jsi.h>

#ifdef __ANDROID__
#include <TurboModule.h>
#else
#include <ReactCommon/TurboModule.h>
#endif

namespace turboutils {
using namespace facebook;

double sumSquares(double a, double b);

void installJsi(facebook::jsi::Runtime& rt);
void installJsi(jsi::Runtime& rt);

/****** TURBO MODULE STUFF BELOW *******/
void installTurboModule(jsi::Runtime& runtime, std::shared_ptr<react::CallInvoker> jsCallInvoker);

// This abstract class defines JSI interfaces for the turbo module
class JSI_EXPORT TurboUtilsSpecJSI : public facebook::react::TurboModule {
protected:
TurboUtilsSpecJSI(std::shared_ptr<facebook::react::CallInvoker> jsInvoker);

public:
// define our interface methods
virtual jsi::String nativeGreeting(jsi::Runtime &rt, const jsi::String &name) = 0;
virtual jsi::Value nativeSumSquares(jsi::Runtime &rt, double a, double b) = 0;

};

// This is the actual implementation of the module methods
class UtilsTurboModule : public TurboUtilsSpecJSI {
public:
UtilsTurboModule(std::shared_ptr<react::CallInvoker> jsInvoker)
: TurboUtilsSpecJSI(jsInvoker) {}

jsi::String nativeGreeting(jsi::Runtime &rt, const jsi::String &name) override;
jsi::Value nativeSumSquares(jsi::Runtime &rt, double a, double b) override;
};
}

#endif /* TURBOUTILSMODULE_H */
5 changes: 5 additions & 0 deletions cpp/empty.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*
* An empty cpp file just to avoid
* "E/SoLoader: couldn't find DSO to load: libturbomodulejsijni.so"
* error
*/
42 changes: 21 additions & 21 deletions example/ios/MyTurboUtilsExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2DCD954D1E0B4F2C00145EB5 /* MyTurboUtilsExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* MyTurboUtilsExampleTests.m */; };
4C39C56BAD484C67AA576FFA /* libPods-MyTurboUtilsExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CA3E69C5B9553B26FBA2DF04 /* libPods-MyTurboUtilsExample.a */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
9B74B19C8FC414C82EE0351D /* libPods-MyTurboUtilsExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 66F73A44D9E6EF75381CBAF4 /* libPods-MyTurboUtilsExample.a */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -50,8 +50,8 @@
2D02E47B1E0B4A5D006451C7 /* MyTurboUtilsExample-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MyTurboUtilsExample-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
2D02E4901E0B4A5D006451C7 /* MyTurboUtilsExample-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MyTurboUtilsExample-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
47F7ED3B7971BE374F7B8635 /* Pods-MyTurboUtilsExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyTurboUtilsExample.debug.xcconfig"; path = "Target Support Files/Pods-MyTurboUtilsExample/Pods-MyTurboUtilsExample.debug.xcconfig"; sourceTree = "<group>"; };
66F73A44D9E6EF75381CBAF4 /* libPods-MyTurboUtilsExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MyTurboUtilsExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = MyTurboUtilsExample/LaunchScreen.storyboard; sourceTree = "<group>"; };
CA3E69C5B9553B26FBA2DF04 /* libPods-MyTurboUtilsExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MyTurboUtilsExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
E00ACF0FDA8BF921659E2F9A /* Pods-MyTurboUtilsExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyTurboUtilsExample.release.xcconfig"; path = "Target Support Files/Pods-MyTurboUtilsExample/Pods-MyTurboUtilsExample.release.xcconfig"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; };
Expand All @@ -69,7 +69,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4C39C56BAD484C67AA576FFA /* libPods-MyTurboUtilsExample.a in Frameworks */,
9B74B19C8FC414C82EE0351D /* libPods-MyTurboUtilsExample.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -126,7 +126,7 @@
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
ED2971642150620600B7C4FE /* JavaScriptCore.framework */,
CA3E69C5B9553B26FBA2DF04 /* libPods-MyTurboUtilsExample.a */,
66F73A44D9E6EF75381CBAF4 /* libPods-MyTurboUtilsExample.a */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down Expand Up @@ -205,7 +205,7 @@
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
C1D60D28B925C94BD88E79D7 /* [CP] Copy Pods Resources */,
4C836E49FD1186B282D4EEA9 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
Expand Down Expand Up @@ -363,44 +363,44 @@
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
};
4F0A6FC082772762E3E4C96C /* [CP] Check Pods Manifest.lock */ = {
4C836E49FD1186B282D4EEA9 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MyTurboUtilsExample/Pods-MyTurboUtilsExample-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-MyTurboUtilsExample-checkManifestLockResult.txt",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MyTurboUtilsExample/Pods-MyTurboUtilsExample-resources.sh\"\n";
showEnvVarsInLog = 0;
};
C1D60D28B925C94BD88E79D7 /* [CP] Copy Pods Resources */ = {
4F0A6FC082772762E3E4C96C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MyTurboUtilsExample/Pods-MyTurboUtilsExample-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
"$(DERIVED_FILE_DIR)/Pods-MyTurboUtilsExample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MyTurboUtilsExample/Pods-MyTurboUtilsExample-resources.sh\"\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
FD10A7F022414F080027D42C /* Start Packager */ = {
Expand Down
9 changes: 7 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ PODS:
- glog
- glog (0.3.5)
- my-turbo-utils (0.1.0):
- Folly
- React
- React-callinvoker
- React-Core
- React-jsi
- ReactCommon/turbomodule/core
- RCTRequired (0.63.4)
- RCTTypeSafety (0.63.4):
- FBLazyVector (= 0.63.4)
Expand Down Expand Up @@ -347,7 +352,7 @@ SPEC CHECKSUMS:
FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
my-turbo-utils: e87b8f0115e6b5d09306e170ec560c4ab2545c79
my-turbo-utils: 1a46edba67f0865a7cc4bc7808142223625e760c
RCTRequired: 082f10cd3f905d6c124597fd1c14f6f2655ff65e
RCTTypeSafety: 8c9c544ecbf20337d069e4ae7fd9a377aadf504b
React: b0a957a2c44da4113b0c4c9853d8387f8e64e615
Expand All @@ -370,6 +375,6 @@ SPEC CHECKSUMS:
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
Yoga: 4bd86afe9883422a7c4028c00e34790f560923d6

PODFILE CHECKSUM: 376455e5c18f190445dbfb465c87ec2cc527f890
PODFILE CHECKSUM: bc14b2a6003af2a6d858f70d6ad69c61ffcdd73c

COCOAPODS: 1.10.1
6 changes: 3 additions & 3 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';

import { StyleSheet, View, Text } from 'react-native';
import { MyUtilsJSI /* MyUtilsBridged */ } from 'my-turbo-utils';
import { MyUtilsTurbo /* MyUtilsJSI, MyUtilsBridged */ } from 'my-turbo-utils';

export default function App() {
const [result, setResult] = React.useState<number | undefined>();
Expand All @@ -11,8 +11,8 @@ export default function App() {
// Bridged
// MyUtilsBridged.sumSquares(3, 4).then(setResult);
// MyUtilsBridged.makeGreetingFor('Bridge').then(setGreeting);
setResult(MyUtilsJSI.sumSquares(3, 4));
setGreeting(MyUtilsJSI.makeGreetingFor('JSI'));
setResult(MyUtilsTurbo.sumSquares(3, 4));
setGreeting(MyUtilsTurbo.makeGreetingFor('TurboModule'));
}, []);

return (
Expand Down
Loading

0 comments on commit 5244ab8

Please sign in to comment.