Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TurboModules (NativeModules Re-architecture) #40

Closed
fkgozali opened this issue Oct 12, 2018 · 102 comments
Closed

TurboModules (NativeModules Re-architecture) #40

fkgozali opened this issue Oct 12, 2018 · 102 comments
Labels
👓 Transparency This label identifies a subject on which the core has been already discussing prior to the repo

Comments

@fkgozali
Copy link

fkgozali commented Oct 12, 2018

Introduction

This is a place for discussions around the upcoming "TurboModule" feature.

Terminology

  • Fabric: just the UI layer re-architecture, to take full advantage of concurrent React architecture. (dedicated issue)
  • JSI: JavaScript Interface, it's a unified lightweight general purpose API for (theoretically) any JavaScript virtual machine. It enables every other piece of the rearchitecture. (dedicated issue)
  • CodeGen: a tool to "automate" the compatibility between JS and native side. (dedicated issue)

TL;DR

From @axe-fb's blogpost, here's a temporary description of TurboModules (please consider that this is not yet finalized, it may change in the future)

The JSI system can also be used to call leverage device capabilities like bluetooth or other sensors by exposing functions that JS can call. This is similar to how browsers expose functions like navigator.geolocation.getCurrentPosition that, when invoked in JavaScript, trigger the respective C++ call in the browser.
In the current system, a table with information about module names and methods is created. When JS calls a specific native module, the indices of the module and methods are passed to Java/ObjC, which then invoke the specific methods. The arguments and return values are also converted between JavaScript and JNI/ObjC objects.
[...] Now that we have a JSI object for "SampleTurboModule", can invoke methods on this JSI object from JavaScript. During the calls, we also need to convert JSI Values to JNI for argument parameters, and the reverse when sending back results.
Like in the current architecture, most types including boolean, strings, Maps, Arrays, Callbacks and Promises are supported.

Available Materials

At ReactConf 2018 @axe-fb did a talk about React Native's New Architecture, which also explains the 3 concepts above: JSI, Fabric, TurboModule.

IN Q1 2019, @kelset wrote a more high-level explanation in a blogpost: https://formidable.com/blog/2019/fabric-turbomodules-part-3/ and did a talk about the whole rearchitecture in April 2019 at React Edinburgh.

@kelset also did a more in-depth talk at React Advanced London in Oct 2019: youtube recording & slides.

Q&A

This is also a place for questions related to this effort and its direction.

@fkgozali
Copy link
Author

cc @kelset - wanna label this one with "Transparency"? I don't have access looks like.

cc @axe-fb

@satya164 satya164 added the 👓 Transparency This label identifies a subject on which the core has been already discussing prior to the repo label Oct 14, 2018
@empyrical

This comment has been minimized.

@TheSavior
Copy link

@empyrical, this isn’t related to turbomodules, but rather one of the other projects I’m working on. This won’t need to be lazy because requireNativeComponent won’t exist and the only work that will be done is returning the string “RCTView”. There will be no round trips to native so it won’t provide value to make this async.

@fkgozali fkgozali changed the title TurboModule Discussions TurboModule (NativeModules Re-architecture) Discussions Nov 2, 2018
@tomduncalf
Copy link

Does this proposal (and related parts such as JSI) mean C++ native modules will become a first class, documented citizen of the RN universe?

That would be awesome, I’m doing a lot of work in this area and most of it had to be figured out by reverse engineering/trial and error.

@fkgozali
Copy link
Author

fkgozali commented Nov 17, 2018

Does this proposal (and related parts such as JSI) mean C++ native modules will become a first class, documented citizen of the RN universe?

Depends on what you meant by first class citizen. You can already build C++ class that talks directly to the VM via JSI's HostObject - all JSI code is already in master: https://github.com/facebook/react-native/blob/bf2500e38ec06d2de501c5a3737e396fe43d1fae/ReactCommon/jsi/jsi.h#L80

An example consumer is https://github.com/facebook/react-native/blob/94d49e544d10d0039a1178dc97533e96a4354198/ReactCommon/fabric/uimanager/UIManagerBinding.h

We plan to build a thin C++ wrapper on top of this direct access to HostObject to make the binding slightly easier in the future, but the first focus will be ObjC/Java modules. Documentation will come when they are all ready and stable. So yes, C++ modules will become first class in this angle.

@tomduncalf
Copy link

This is really interesting, thanks for the pointers. So far we've been working with CxxModule, looks like this could be a cleaner alternative!

Will have a proper look into it when time allows, we are doing some pretty interesting stuff with C++/React Native integration which pushes it fairly hard and could make a good test case for some of this stuff, so if you'd be interested in discussing in more detail please feel free to get in touch (details in Github bio)!

@an-kumar
Copy link

@fkgozali Do you mean in the current version of react native we can already build and register C++ classes using jsi's HostObject to the VM?

Are there any examples of this? I'm particularly unclear on where the registration would occur -- from outside of RN core.

@tomduncalf
Copy link

Yeah same question here - would be nice to know the recommended way to register a C++ JSI module

@fkgozali
Copy link
Author

Do you mean in the current version of react native we can already build and register C++ classes using jsi's HostObject to the VM?

Yes. We're working on a cleaner abstraction, but at the moment you can try something like this at your own:

  • iOS
  • Android
    • Same deal as iOS, but get the runtime off catalystInstance.getJavaScriptContextHolder(), which you can pass down to JNI, then cast it to jsi::Runtime * like in iOS.

would be nice to know the recommended way to register a C++ JSI module

Expect more documentation when TurboModule is ready to use. We moved a bunch of code for iOS already to github, Android coming soon, but sample code will be provided at later time.

@tomduncalf
Copy link

Thanks very much - I was looking at this today but it would have taken me a while to figure that out :)

I noticed the Turbomodules stuff in github - seems to be adding a new layer of abstraction for this stuff? Would you say it’s better to develop against what is in master than what is in the RC branch if we want to have more of a chance of keeping close to the final API?

@fkgozali
Copy link
Author

fkgozali commented Jan 14, 2019

I noticed the Turbomodules stuff in github - seems to be adding a new layer of abstraction for this stuff?

That's correct. It's abstraction for crossing C++-->ObjC and C++-->Java. You don't need it if you want to stay in pure C++ via jsi::HostObject.

Would you say it’s better to develop against what is in master than what is in the RC branch if we want to have more of a chance of keeping close to the final API?

Nothing in master/RC is ready to use, but you can get the idea about where we're headed. If you for some reason needs to play with jsi::HostObject sooner, by all means go for it. If you're in no rush, I'd wait for more official guidelines/docs on how to use TurboModule.

@kelset kelset changed the title TurboModule (NativeModules Re-architecture) Discussions TurboModule (NativeModules Re-architecture) Jan 24, 2019
@kelset kelset changed the title TurboModule (NativeModules Re-architecture) TurboModules (NativeModules Re-architecture) Jan 24, 2019
@tomduncalf
Copy link

@fkgozali I wonder if you'd be able to advise me a little further on the Android side of this? I was able to get it up and running on iOS without too much trouble and it seems great, but on Android I'm having issues and as I'm pretty new to the Android platform, not sure quite where I am going wrong.

You said:

Android
Same deal as iOS, but get the runtime off catalystInstance.getJavaScriptContextHolder(), which you can pass down to JNI, then cast it to jsi::Runtime * like in iOS.

So, the only way I could find to get access to the JavaScriptContextHolder was to create a new Native Module, and get hold of this in the initialise method like: long context = getReactApplicationContext().getJavaScriptContextHolder().get(); (in iOS, I was able to do this without a module, in my AppDelegate by just getting .runtime from the RCTCxxBridge instance, but I couldn't work out the equivalent in Android).

I'm then passing the context over JNI to C++, so my native module code looks like:

package com.reactnativejsi;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class JSIModule extends ReactContextBaseJavaModule {
    static {
        System.loadLibrary("test_module_jni");
    }

    public JSIModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public void initialize() {
        super.initialize();

        long context = getReactApplicationContext().getJavaScriptContextHolder().get();
        install(context);
    }

    @Override
    public String getName() {
        return "JSIModule";
    }

    public native void install(long runtime);
}

Then in my TestModule.cpp, I'm installing the module by calling runtime.global().setProperty(runtime, testModuleName, std::move(object)); - the code is roughly:

std::shared_ptr<TestModuleBinding> testModuleBinding;

extern "C" {
  JNIEXPORT void JNICALL
  Java_com_reactnativejsi_JSIModule_install(JNIEnv* env, jobject thiz, jlong runtimePtr) {
    testModuleBinding = std::make_shared<TestModuleBinding>();
    jsi::Runtime* runtime = (jsi::Runtime*) runtimePtr;

    auto testModuleName = "testModule";
    auto object = jsi::Object::createFromHostObject(*runtime, testModuleBinding);
    runtime->global().setProperty(*runtime, testModuleName, std::move(object));
  }
}

But this causes it to crash with a useless stack trace and no errors that I can see in logcat:

image

Indeed, either of the lines auto object = jsi::Object::createFromHostObject(*runtime, testModuleBinding); or runtime->global().setProperty(*runtime, testModuleName, std::move(object)); on their own is enough to cause the crash, or even just trying to access runtime->global() and assign it to something... so I am guessing there is something bad about my runtime instance, but I'm a bit of a loss how to debug further!

Like I say, new to all this so I may be making some stupid mistake – if you have any pointers (heh...) or examples of doing this on Android, I'd be really grateful!

Thanks,
Tom

@rhysforyou
Copy link

Will TuboModules make it easier to write native modules in Swift? Right now we still have to write Objective-C bridging code which is far from ideal.

@rhdeck
Copy link

rhdeck commented Feb 15, 2019 via email

@TheSavior
Copy link

@rhdeck that’s awesome and a great idea. We are actually planning on building this into the system for TurboModules. ;-) Have you seen this discussion? #92

@fkgozali
Copy link
Author

Will TuboModules make it easier to write native modules in Swift? Right now we still have to write Objective-C bridging code which is far from ideal.

TurboModule foundation is just JSI + platform specific binding. JSI is C++, and for iOS, we need a binding to convert c++ calls into objc++ class methods.

To support swift, such c++ to swift binding needs to be built (we won’t be building this as we’re focused on objc support right now).

Then with codegen, swift code per module can be generated to some degree.

@sunnylqm
Copy link

@fkgozali Can you introduce more about the jsi/jsc refactor? Some native modules may need the jsContextRef which is now gone facebook/react-native@c49d365#diff-e15318f48b6447f2d9936c5e047d882fL395

@fkgozali
Copy link
Author

Some native modules may need the jsContextRef which is now gone

See the comment above for now: #40 (comment)

Why would you need direct access to the VM though?

@sunnylqm
Copy link

@fkgozali the webgl context needs this https://github.com/react-native-community/react-native-webgl/blob/master/cpp/RNWebGL.cpp#L205
I am not familar with c++, is there anyway to access the vm in the legacy native module?

@fkgozali
Copy link
Author

I am not familar with c++, is there anyway to access the vm in the legacy native module?

The existing nativemodules system was not designed to provide you access to the VM directly since that can be dangerous (e.g. multithreaded access, since JS is single threaded, etc). The jsContextRef just happened to be added for a different purpose in the past.

With JSI/TurboModule, you can achieve the same thing as https://github.com/react-native-community/react-native-webgl/blob/master/cpp/RNWebGL.cpp#L205 without getting access to the VM. This is done by subclassing JSI HostObject: https://github.com/facebook/react-native/blob/0d7faf6f73b942126e1f45016cde8fd480fd0164/ReactCommon/jsi/jsi.h#L98 -- In fact, TurboModule is just a special subclass of HostObject.

So if your webgl stuff registers itself as TurboModule compatible and you provide your own HostObject subclass, then you don't need any access to the VM directly. This process is work in progress and will be documented when they're ready. For now, you can see the comment above as I previously pasted. #40 (comment)

@sunnylqm
Copy link

@fkgozali Thanks for your detailed explanation! I'll give it a try.

@mrousavy
Copy link

mrousavy commented Mar 19, 2021

Do TurboModules support multiple callbacks per function? I noticed that with the "legacy" native module system you cannot pass a Callback (RCTResponseSenderBlock) and a Promise (RCTPromiseResolveBlock + RCTPromiseRejectBlock) into the same function, I assume that by building upon JSI this will work out of the box, no?

@msageryd
Copy link

msageryd commented Mar 26, 2021

Sorry if this is the wrong place. I didn't find any good place to post this possibly turboModules issue.

I just upgraded from RN 0.63.2 to 0.64. And my project doesn't build anymore due to this:
shared_timed_mutex' is unavailable: introduced in iOS 10.0
Error located in RCTTurboModuleManager

My build target is ios 10.0. I even tried to bump it to 11. Googling this error brings nothing, which is quite rare when googling RN problems.

Should I post this elsewhere?
Any idéas?


Edit:

My bad, sorry. Solved.
facebook/react-native#31250

@henrymoulton
Copy link

henrymoulton commented Apr 5, 2021

@fkgozali sorry not relevant to this thread but is there documentation on how to disable TurboModules on RNTester? I think it's breaking fast refresh and debugging with Chrome.

@fkgozali
Copy link
Author

fkgozali commented Apr 5, 2021

@henrymoulton

Fast Refresh should work normally, but old Chrome debugger flow won't work because of the lack of sync call support in that env. We're still hashing out the debugger solution later, but a few things you can try:

If you're hitting issue with Fast refresh, make sure you're not connected to the old Chrome debugger flow (the one you launch from dev menu) and see if the problem is solved. If not, would you mind opening a new issue and linking to it here?

To disable TurboModule in RNTester (not recommended but OK for your own exploration), change the flag(s) here:

@prakashjais99
Copy link

I want to use TurboModules to send events from Java to JS using JSI bridge. But i am not able to find a right docs or sample code to proceed. Below is what I have achieved so far.

Code to register and later callback

 void TrimNativeModule::sendKeyEvent(jsi::Runtime &rt, const jsi::Object &arg) {
        if (keyCallback != nullptr) {
            jsi::Function funPtr = keyCallback->getFunction(rt);
            jsInvoker_->invokeAsync([&rt, &funPtr](){
                funPtr.call(rt);
            });
        }
    }

    void TrimNativeModule::registerKeyCallback(jsi::Runtime &rt, const jsi::Function &callback) {
        keyCallback = &callback;
    }

Below is the JS side code

  global.trimModule?.registerKeyCallback(() => {
  console.log("Got Call back");
});

Registration is working but when I tried to do call sendKeyEvet later for an event from Java native function call back is not called back

Any help or pointers on this will be helpful

@radelcom
Copy link

radelcom commented Jun 9, 2021

@prakashjais99 quick question, I am also in the similar situation but was wondering how where you getting reference to the jsInvoker_ value?

@mrousavy
Copy link

@radelcom

iOS

  1. import RCTTurboModule.h
  2. use bridge.jsCallInvoker (RCTBridge)

Android

  1. Use context.getCatalystInstance().getJSCallInvokerHolder() (ReactApplicationContext)

@radelcom
Copy link

@mrousavy follow up question... when passing the invoker to the cpp-adapter.cpp file, what is the proper way to cast the invoker?

extern "C" JNIEXPORT void JNICALL Java_com_MyAwesomeModule_initialize(JNIEnv* env, jclass clazz, jlong jsi, jobject invoker) { installMyAwesomeModule(*reinterpret_cast<facebook::jsi::Runtime *>(jsi), invoker); }

my cpp file has this declaration
void installMyAwesomeModule(jsi::Runtime& jsiRuntime, std::shared_ptr<react::CallInvoker> jsInvoker)

@kidroca
Copy link

kidroca commented Jul 16, 2021

Another workaround to "old debug flow not working": If you're using Hermes you can follow the steps here: https://reactnative.dev/docs/hermes#debugging-js-on-hermes-using-google-chromes-devtools
This doesn't require Flipper

@rangav
Copy link

rangav commented Aug 20, 2021

Here is roadmap update from react native team

https://reactnative.dev/blog/2021/08/19/h2-2021

Looks like they are planning to release new architecture by end of this year.

@yaaliuzhipeng
Copy link

yaaliuzhipeng commented Feb 10, 2022

@mrousavy follow up question... when passing the invoker to the cpp-adapter.cpp file, what is the proper way to cast the invoker?

extern "C" JNIEXPORT void JNICALL Java_com_MyAwesomeModule_initialize(JNIEnv* env, jclass clazz, jlong jsi, jobject invoker) { installMyAwesomeModule(*reinterpret_cast<facebook::jsi::Runtime *>(jsi), invoker); }

my cpp file has this declaration void installMyAwesomeModule(jsi::Runtime& jsiRuntime, std::shared_ptr<react::CallInvoker> jsInvoker)

dude , did you solved this cast problem ? I encountered this too .

@mrousavy
Copy link

Here's the magic 🪄:

// from java:
CallInvokerHolderImpl callInvoker = (CallInvokerHolderImpl) context.getCatalystInstance().getJSCallInvokerHolder()
initialize(runtime.get(), callInvoker)
// in c++:
extern "C" JNIEXPORT void JNICALL Java_com_mrousavy_MyModule_initialize(JNIEnv* env, jclass clazz, jlong jsiRuntime, jni::alias_ref<facebook::react::CallInvokerHolder::javaobject> callInvoker);

// cast:
auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker();

But keep in mind that this is pretty unrelated to the issue.

@yaaliuzhipeng
Copy link

hey ! guys ! As the rn0.68.0 released the new arch feature; I'm about digging into rn's new archi adoption;
👉🏻 https://reactnative.dev/docs/next/new-architecture-library-android
I met this gradle error when building

Could not find method react() for arguments [build_cbn8oel6m6i7bg72w8bsdvl$_run_closure3$_closure11@ef70746] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler

my library 'module-level' react spec looks like below

react {
    libraryName = "Test"
    codegenJavaPackageName = "com.iturbomodule.test"
    root = rootProject.file("..")
    jsRootDir = rootProject.file("../js/")
    reactNativeDir = rootProject.file("../react-native/")
    codegenDir = rootProject.file("../react-native-codegen/")
}

@cortinico
Copy link
Member

@yaaliuzhipeng As a rule of thumb, report issues like this one either on github.com/reactwg/react-native-new-architecture/ (if you don't have access yet, you can apply for it with the form in the README).

Could not find method react() for arguments

This happens as you don't have the React Gradle Plugin applied in that module. I would say that you miss a:

plugins {
  id("com.facebook.react")
}

but not knowing your full gradle file setup, makes hard to say if this is the only issue

@yaaliuzhipeng
Copy link

@yaaliuzhipeng As a rule of thumb, report issues like this one either on github.com/reactwg/react-native-new-architecture/ (if you don't have access yet, you can apply for it with the form in the README).

Could not find method react() for arguments

This happens as you don't have the React Gradle Plugin applied in that module. I would say that you miss a:

plugins {
  id("com.facebook.react")
}

but not knowing your full gradle file setup, makes hard to say if this is the only issue

love you dude, I did missed the plugin, everything goes fine after this plugin's added ! ! ! guess I have to learn gradle for a while 😅

@MateWW
Copy link

MateWW commented Apr 1, 2022

Hello, I'm playing with TurboModules and figured out something confusing. I was testing my brand new turbo module which uses ExoPlayer. ExoPlayer requires all actions to be triggered from the same thread. I've created two methods represented by the below interface.

export interface Spec extends TurboModule {
  register(): string;
  play(key: string, url: string): void;
}

I was surprised when I've triggered those methods one by one and got an error

const key = Player.register()
Player.play(key, "url")

I've tested it further and noticed that there are two types of methods in TurboModule

  • synchronous - methods that return values are running on the JS thread and can be treated in JS files as a synchronous call
  • asynchronous - methods that define void as a return type are running on something called native modules thread and we cannot guess when it will be accomplished(order is different with every application run)

I've prepared a small playground.
https://github.com/MateWW/RNNewArchitectureApp

Can someone elaborate on why it's like that?

@kelset
Copy link
Member

kelset commented Jun 27, 2022

hey folks, since we have now an official deep dive discussion open on this in the ReactWG New architecture, let's close this one and make the conversation progress over there: reactwg/react-native-new-architecture#2

Remember that you can apply to the WG (in case you are not in it yet) by applying to the form in the readme.

@kelset kelset closed this as completed Jun 27, 2022
@react-native-community react-native-community locked as resolved and limited conversation to collaborators Jun 27, 2022
@kelset kelset unpinned this issue Jun 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
👓 Transparency This label identifies a subject on which the core has been already discussing prior to the repo
Projects
None yet
Development

No branches or pull requests