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

Components with JSI events #1

Open
RichardLindhout opened this issue Aug 11, 2021 · 7 comments
Open

Components with JSI events #1

RichardLindhout opened this issue Aug 11, 2021 · 7 comments

Comments

@RichardLindhout
Copy link

Amazing examples!

I'm looking to send events in a custom MapView with ViewManager component over the bridge is this possible?

@barthap
Copy link
Owner

barthap commented Aug 12, 2021

I have no experience with components in JSI yet, but the keyword you should google for is "Fabric" / "React Native Fabric"

Update: There's now one more good example of Fabric usage: https://blog.swmansion.com/introducing-fabric-to-react-native-screens-fd17bf18858e

@barthap
Copy link
Owner

barthap commented Aug 14, 2021

I just remembered an example of Fabric usage - the react-native-swiftui repo. It's a fork of react-native and it's rather complicated, but maybe browsing commit history will uncover some interesting stuff. 😉 I didn't dig deep into it, but maybe it helps.

The author describes it briefly in a blog post.

@yaaliuzhipeng
Copy link

Hey , dude . Nice to have this great example.
I'm new to JSI , and I encountered an asynchronous problem as I described below . Can you do me a favor, give me some advices or recommend some example usages which can help me out of this, thanks here .

--- Code Below ---

// JavaXXXModule.java
...
static void doSomeAsyncTask() {
        SomeModule.runTask()
            .addOnSuccessListener(v -> {

            })
            .addOnFailureListener(e -> {

            });
    }
...
//CppXXXModule.cpp
jsiRuntime.global().setProperty(jsiRuntime, "runNativeTask", move(runNativeTask));
// App.tsx
global.runNativeTask( (v) => {
    // accept the result from native
} );

Here is the full flow

  1. calling install jsi method
  2. call the runNativeTask method from js
  3. the cpp method runNativeTask will invoke the java method doSomeAsyncTask through jni ,

here comes the question , when and where should I invoke the callback passed from javascript to return the output . It's pretty easy to get this done if the java method is synchronous . but it's async ... 😕

@barthap
Copy link
Owner

barthap commented Feb 11, 2022

Hi, actually I'm doing something similar here: expo/expo#16075, but in your case, it could be simplified more:

  1. you call the runNativeTask JSI method from JS
  2. its first argument is callback, so in C++ it will be jsi::Function - store it somewhere in c++
  3. call Java from C++ via JNI / fbjni from inside runNativeTask on the c++ side
  4. meanwhile, export from C++ to Java the JNI native method native void onTaskSuccess(result) - its c++ body should call previously stored jsi::Function callback. Here you might have problems with threading, in this case you'd need to learn about "JSI Call Invoker" (even more complicated example here: [expo-av][ios] Implement JSI Audio on iOS expo/expo#14904)
  5. call that JNI method from Java inside onSuccessListener

@yaaliuzhipeng
Copy link

Hi, actually I'm doing something similar here: expo/expo#16075, but in your case, it could be simplified more:

  1. you call the runNativeTask JSI method from JS
  2. its first argument is callback, so in C++ it will be jsi::Function - store it somewhere in c++
  3. call Java from C++ via JNI / fbjni from inside runNativeTask on the c++ side
  4. meanwhile, export from C++ to Java the JNI native method native void onTaskSuccess(result) - its c++ body should call previously stored jsi::Function callback. Here you might have problems with threading, in this case you'd need to learn about "JSI Call Invoker" (even more complicated example here: [expo-av][ios] Implement JSI Audio on iOS expo/expo#14904)
  5. call that JNI method from Java inside onSuccessListener

dude , I've tried the flow . There comes another question of the jsi::Value *args .
this is my onTaskSuccess callback

static void onTaskSuccess(jni::alias_ref<jclass> clazz,
                              jlong jsiRuntimePtr,
                              jni::alias_ref<react::CallInvokerHolder::javaobject> holder) {
   auto jsiRuntime = reinterpret_cast<jsi::Runtime *>(jsiRuntimePtr);
   if(store.args != nullptr) {
       if(store.args[0].asObject(*jsiRuntime).isFunction(*jsiRuntime)){
            LOGD("yes ,it is");
       }else{
            LOGD("not a function");
       }
   }
}

this is my c++ store class

class Store {
public:
    jsi::Runtime *runtime;
    const jsi::Value *args;
    void setRuntime(jsi::Runtime *rt){
        this->runtime = rt;
    }
    void setArgs(const jsi::Value *ags) {
        this->args = ags;
    }
};

and this is my host-function

jsi::Function::createFromHostFunction(
                jsiRuntime,
                jsi::PropNameID::forAscii(jsiRuntime, "identifyLanguage"),
                1,
                [](jsi::Runtime &runtime, const jsi::Value &thiz, const jsi::Value *args, size_t count) -> jsi::Value {
                    MlkitTranslateTextModule::identifyLanguage();

                    jsi::Value* cps = new jsi::Value[1];
                    memcpy(cps,args,sizeof(args[0])*1);

                    store.setArgs(cps);
                    return jsi::Value::null();
                });

on the TaskSuccessCallback , it logged out

D/JNI-cpp-adapter: not a function

so , I assume that the args[0] is released somehow.
at an earlier time , I saved the jsi runtime to the store too . but I found out that it's not safe to do this as the description of jsi::Runtime said . So I passed the jsi runtime again from Java .
do you have any ideas about this situation ...
guess I'd consider other ways to solve this question later anyway , its really a challenge for me at this moment 😥

@barthap
Copy link
Owner

barthap commented Feb 12, 2022

I think one problem is that you're trying to memcpy the JSI values instead of extracting the jsi::Function callback and moving (std::move) it into some smart pointer (like in Android) or another wrapper class (iOS, here it was even more tricky if you read the PR description/comments):

// iOS, see AudioSampleCallbackWrapper code for further part
if (argsCount > 1 && args[1].isObject()) {
      auto callback = args[1].asObject(runtime).asFunction(runtime);
      auto wrapper = std::make_unique<AudioSampleCallbackWrapper>(std::move(callback), runtime, strongCallInvoker);
// Android PR example
if (argsCount > 1 && args[1].isObject()) {
  auto callback = args[1].asObject(runtime).asFunction(runtime);
  auto callbackShared = std::make_shared<jsi::Function>(std::move(callback));

  // you want to store this function (as std::function) somewhere
  auto cppFunction = [callbackShared, &runtime, callInvoker](int someResultYouWantToPass)  {
    callInvoker->invokeAsync([callbackShared, &runtime, someResultYouWantToPass] () {
                            try {
                                // not sure about passing the result here, but see the PR code
                                callbackShared->call(runtime, jsi::Value(someResultYouWantToPass));
                            } catch (std::exception &exception) {
                                // ...
                            }
    });


   // then call that function in JNI function
   cppFunction()

@yaaliuzhipeng
Copy link

I think one problem is that you're trying to memcpy the JSI values instead of extracting the jsi::Function callback and moving (std::move) it into some smart pointer (like in Android) or another wrapper class (iOS, here it was even more tricky if you read the PR description/comments):

// iOS, see AudioSampleCallbackWrapper code for further part
if (argsCount > 1 && args[1].isObject()) {
      auto callback = args[1].asObject(runtime).asFunction(runtime);
      auto wrapper = std::make_unique<AudioSampleCallbackWrapper>(std::move(callback), runtime, strongCallInvoker);
// Android PR example
if (argsCount > 1 && args[1].isObject()) {
 auto callback = args[1].asObject(runtime).asFunction(runtime);
 auto callbackShared = std::make_shared<jsi::Function>(std::move(callback));

 // you want to store this function (as std::function) somewhere
 auto cppFunction = [callbackShared, &runtime, callInvoker](int someResultYouWantToPass)  {
   callInvoker->invokeAsync([callbackShared, &runtime, someResultYouWantToPass] () {
                           try {
                               // not sure about passing the result here, but see the PR code
                               callbackShared->call(runtime, jsi::Value(someResultYouWantToPass));
                           } catch (std::exception &exception) {
                               // ...
                           }
   });


  // then call that function in JNI function
  cppFunction()

dude !! you saved my ass !!!!!!! It worked. 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants