react-native-community / discussions-and-proposals

Discussions and proposals related to the main React Native project
https://reactnative.dev
1.69k stars 127 forks source link

JSI (JavaScript Interface) & JSC (JavaScript Core) Discussion #91

Closed kelset closed 2 years ago

kelset commented 5 years ago

What's the current status of JSI? Read here

Intro

With this issue I'd like to try and create a "one stop" for all the information available around the JavaScript Interface, the unified lightweight general purpose API for (theoretically) any JavaScript virtual machine.

Currently, the default Javascript Virtual Machine used by React Native is JavaScriptCore (JSC) - it is used in WebKit.

Terminology

TL;DR

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

Instead of using the bridge for queuing messages, the new architecture allows us to directly "invoke" (think RPC) Java/ObjC methods. An analogy would be how we call DOM methods from JavaScript in the browser. For example, in the statement var el = document.createElement('div'); the variable el holds a reference not to a JavaScript object, but to an object that was possibly instantiated in C++. When JavaScript calls el.setAttribute('width', 100), we end up synchronously invoking the setWidth method in C++ that changes the actual width of that element. In React Native, we can similarly use the JavaScript interface to invoke methods on UI Views and Native Modules that are implemented in Java/ObjC.

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.

Recently the JSC was upgraded for Android, you can check the commit here and the related issue in the main repo.

On twitter, @karanjthakkar did a thread detailing how he integrated the new JSC in the Skyscanner app.

In Q1 2019, @kelset wrote a high level explanation in a blogpost: https://formidable.com/blog/2019/jsi-jsc-part-2/ and did a talk about the whole rearchitecture in April 2019 at React Edinburgh.

Over on twitter, @ericlewis published a sample repo with @chrfalch to showcase how to use directly C++ in a RN app (thanks to the JSI): https://github.com/ericlewis/react-native-hostobject-demo

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

@Jarred-Sumner published a quick benchmark difference between a lib and a reimplementation using JSI twitter.

Libraries implemented using JSI

Blogposts

Q&A

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

yaaliuzhipeng commented 2 years ago

Is there any way to call the callback passed from javascript async . I have a java method with async listener like below

static public void identifyLanguage(String text) {
        LanguageIdentifier languageIdentifier = LanguageIdentification.getClient();
        languageIdentifier.identifyLanguage(text)
            .addOnSuccessListener(v -> {

            })
            .addOnFailureListener(e -> {

            });
    }

I invoked the c++ method from javascript through jsi and the c++ method called this static java method . the problem is I passed in an anonymous callback from javascript to receive calling result , but as you can see the result from java is asynchronous . the registered c++ method cannot get the result synchronous . how can I invoke the passed in callback once the SuccessListener invoked ?

stephenkopylov commented 2 years ago

@yaaliuzhipeng the main thing you need to achieve this is react::CallInvoker. When you'll get all you need to do is something like this:

#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/TurboModule.h>
#include <ReactCommon/TurboModuleUtils.h>

static Value foo(Runtime &rt, TurboModule &turboModule, const Value *args, size_t arg_count) {
    std::weak_ptr<CallbackWrapper> callbackWrapper = react::CallbackWrapper::createWeak(args[0].getObject(rt).getFunction(rt), rt, turboModule.jsInvoker_);

    callYourJavaMethodWithAsyncCallbackAsArgument([callbackWrapper](){
        auto strongWrapper = callbackWrapper.lock();
        if (!strongWrapper) {
            return;
        }

        strongWrapper->jsInvoker().invokeAsync([callbackWrapper]() {
            auto strongWrapper2 = callbackWrapper.lock();
            if (!strongWrapper2) {
                return;
            }

            strongWrapper2->callback().call(strongWrapper2->runtime()); //calling your async callback

            strongWrapper2->destroy();
        });
    };
    );

    return 0;
};

PS: I'd recommend you to use CallbackWrapper - it's really useful if you are not sure that JSRuntime (ant all it's objects including your callback) is still alive

PPS: Don't forget to destroy all your callbacks in TurboModule's destructor

PPPS: as I mentioned before - here's a good example of TurboModule with JSCallInvoker in it

laptou commented 2 years ago

How do I get a shared_ptr to the JS runtime on Android? It seems to be exposed as a shared_ptr on iOS, which is great b/c I can convert it into a weak_ptr and this lets me know when the runtime is destroyed, but on Android it seems that I just get a raw pointer with no easy way to track the lifetime of the runtime.

intergalacticspacehighway commented 2 years ago

Is there any recommended way to release native resources held by the HostObject when it's being garbage collected? (Assuming HostObject gets GCed when there are no references to them in JS world and when the garbage collector runs)

mrousavy commented 2 years ago

Yes, the HostObject's C++ destructor.

intergalacticspacehighway commented 2 years ago

Great. Thank you @mrousavy. Tried putting a destructor and noticed it gets called sometimes but not consistently when we release the variable references from JS. Maybe because it's still not garbage collected? In that case, I guess it would be safer to provide some function in HostObject that we can manually call to release resources? Let me know if I am understanding it correctly.

mrousavy commented 2 years ago

Yes, JS memory management works different than C++' memory management. The HostObject only gets deleted when the GC runs (which btw might even be on another Thread, so be careful when doing JSI or JNI stuff).

See react-native-vision-camera's Frame Host Object for an example :)

laptou commented 2 years ago

Is the lifetime of the CallInvoker tied to the lifetime of the Runtime? As in, if I schedule a callback using invokeAsync, is it guaranteed that the runtime will still exist when the callback is called?

laptou commented 2 years ago

@DominickVale install React Native into a node_modules folder, then add the following paths to your header search path:

node_modules/react-native node_modules/react-native/React node_modules/react-native/React/Base node_modules/react-native/ReactCommon/jsi node_modules/react-native/ReactCommon/callinvoker

You can also get the same headers by downloading the Hermes source code (they are located in the API folder).

laptou commented 2 years ago

How do you get a reference to the RuntimeExecutor on iOS? It's available via CatalystInstance on Android, but I can't find the analogous method on iOS.

DominickVale commented 2 years ago

Hi, what would be the correct way to have an array of objects constantly being updated and the js side being notified of those changes? All the attempts I've made end up in crashes. I tried callbacks, i tried manually getting the data from a js loop etc. They are all not working at higher speeds (for example less than 50ms)

JiriHoffmann commented 2 years ago

Hi, I was wondering... Is there was a way to pass an ArrayBuffer from C++ to the JS side? I know there is the getArrayBuffer method to get ArrayBuffer from JS, but I couldn't find anything for the other way around.

mhorowitz commented 2 years ago

Hi, I was wondering... Is there was a way to pass an ArrayBuffer from C++ to the JS side? I know there is the getArrayBuffer method to get ArrayBuffer from JS, but I couldn't find anything for the other way around.

Not currently. There was discussion of this in https://github.com/facebook/hermes/pull/419#issuecomment-747119254 in the past. It's not something on our roadmap, but we would be happy to advise on what we would want to see in a PR.

mfbx9da4 commented 2 years ago

@JiriHoffmann Does react-native-buffer do what you need?

@DominickVale I've also experienced performance issues with async JSI functions

JiriHoffmann commented 2 years ago

@JiriHoffmann Does react-native-buffer do what you need?

Not really, it uses memcpy which is the same thing I do.

Hi, I was wondering... Is there was a way to pass an ArrayBuffer from C++ to the JS side? I know there is the getArrayBuffer method to get ArrayBuffer from JS, but I couldn't find anything for the other way around.

Not currently. There was discussion of this in facebook/hermes#419 (comment) in the past. It's not something on our roadmap, but we would be happy to advise on what we would want to see in a PR.

@mhorowitz I would love to give it a try, although I'm not sure if my JSI and C++ knowledge is up to par yet. Maybe a new issue would be helpful so that others can take a look at it as well?

Also I created two JSI libraries if you want to add them to the list:

DominickVale commented 2 years ago

@JiriHoffmann Does react-native-buffer do what you need?

@DominickVale I've also experienced performance issues with async JSI functions

I'd love to only get performance issues. I literally cannot find a reliable way to get state updates from the c++ side of things. Whether i call a c++ function from javascript every 20ms or i call said javascript function from state hooks on the c++ side it crashes in a non-deterministic way 80% of the time (at least to me). The worst part is actually the fact that it works most of the time but it crashes in this random way. I tried debugging but the stack trace doesn't make sense to me as it's just internal JSI stuff and nothing regarding what I'm doing natively. What i need to do is pretty simple... I just want real-time data from the c++ side. It's been a month and still no solution.

kelset commented 2 years ago

@DominickVale that sounds like a big problem - can you try to isolate it into a repro project and report it (similarly to what @mfbx9da4 did) so that it can be investigated further?

chrfalch commented 2 years ago

@DominickVale Could there be an issue with accessing the Javascript JSI objects from other threads than the JS thread? This could be a source of unreliable crashes like this.

yotamishak commented 2 years ago

Not sure if this is the right place for this, if so it would be great if you could refer me to where this can be discussed

I wanted to ask how native modules in the new architecture work compared to "jsi modules".

"jsi modules" below

Libraries implemented using JSI

Will all new native modules (turbo modules) essentially be "jsi modules"?

Is there any need for c++ code in order to implement this or are codegen declarations on the js side enough for synchronous calls to native functions out of the box?

Does calling native function synchronously mean that return values are also returned synchronously ( as opposed to promises or callbacks)?

From my understanding, the bridge will still exist for backward compatibility, so how will migration of legacy native modules be handled? Is it enough to generate a codegen schema for the modules to redirect calls to these modules through the jsi as opposed to the bridge?

I don't want to overwhelm with questions but I feel as if the answers to all of these questions are related... Thanks in advance!

mrousavy commented 2 years ago

Hey @yotamishak, I spotted my libs there! 😄

"JSI Modules" is what I call my libs that use the bare JSI C++ API to provide fast and synchronous bindings from JS to native (C++).

Turbomodules is an abstraction built ontop of JSI to make it easy for everyone to write native modules that are powered by JSI, without having to touch C++ code.

So yes, Turbomodules (the new native module system for React Native) will use JSI under the hood, just like my libraries. It's just a bit abstracted/wrapped to make it easier, since my JSI Modules are a bit complex in code (lots of C++)

yotamishak commented 2 years ago

@mrousavy thanks for the quick response I'm well familiar with your libraries and have been trying to pick out part of your code to find a Jsi solution to my use case.

I noticed this repo https://github.com/mrousavy/react-native-jsi-library-template of yours.

So using the new architecture I wouldn't need to use this template at all correct? I would get all of this functionality out of the box?

mrousavy commented 2 years ago

Yea, when using Turbomodules you won't need my jsi-library-template. Instead, take a look at this guide.

There are some parts that are still not covered by Turbomodules, e.g. custom JSI HostObjects, shared memory ArrayBuffers, and multithreaded JSI Runtimes - which is why MMKV or VisionCamera (the Frame Processors Part specifically) cannot be migrated to a Turbomodule (yet).

I've been investigating solutions that allow you to create JSI HostObjects with the Java/ObjC APIs on Turbomodules and pass them around like normal types (boolean, number, string, object, array), maybe I'll open a PR to react-native core soon

fwon commented 2 years ago

@Kudo

2.5.6 Apps that browse the web must use the appropriate WebKit framework and WebKit Javascript.

If so, why Hermes engine is supported in iOS, does it means that we should use Hermes for JSI & JavasScriptCore for inner WebView. Then we must keep two engine in RN iOS project?

kelset commented 2 years ago

hey folks, since we have an official deep dive discussions section open on this in the ReactWG New architecture, let's close this one and make the conversation progress over there: https://github.com/reactwg/react-native-new-architecture/discussions/categories/deep-dive (there are also some other sections, like Q&A, to ask more specific questions with an easier UX for replying)

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