Skip to content

Conversation

tjzel
Copy link
Collaborator

@tjzel tjzel commented Jul 22, 2025

Summary

As requested in #7831. I opted to use jsi::NativeState instead of jsi::HostObject since it introduces less boilerplate.

Test plan

diff --git a/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx b/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx
index 7f3a1ac26c..6e476f11ea 100644
--- a/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx
+++ b/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx
@@ -1,10 +1,33 @@
 import React from 'react';
-import { StyleSheet, Text, View } from 'react-native';
+import { Button, StyleSheet, Text, View } from 'react-native';
+import { createWorkletRuntime, runOnRuntime } from 'react-native-worklets';
 
 export default function EmptyExample() {
   return (
     <View style={styles.container}>
       <Text>Hello world!</Text>
+      <Button
+        title="Create Worklet Runtime"
+        onPress={() => {
+          const asyncQueue = globalThis._WORKLETS_ASYNC_QUEUE;
+          console.log(asyncQueue, 'Async Queue');
+          const workletRuntime = createWorkletRuntime(
+            'EmptyExample',
+            () => {
+              'worklet';
+            },
+            asyncQueue
+          );
+          console.log(workletRuntime, 'Worklet Runtime');
+          runOnRuntime(workletRuntime, () => {
+            'worklet';
+            console.log(
+              'Worklet Runtime created successfully!',
+              globalThis._LABEL
+            );
+          })();
+        }}
+      />
     </View>
   );
 }
diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RNRuntimeWorkletDecorator.cpp b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RNRuntimeWorkletDecorator.cpp
index afaac36a40..7864a9a7c7 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RNRuntimeWorkletDecorator.cpp
+++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RNRuntimeWorkletDecorator.cpp
@@ -1,3 +1,4 @@
+#include <worklets/Tools/AsyncQueue.h>
 #include <worklets/Tools/WorkletsVersion.h>
 #include <worklets/WorkletRuntime/RNRuntimeWorkletDecorator.h>
 #include <worklets/WorkletRuntime/WorkletRuntimeCollector.h>
@@ -20,6 +21,16 @@ void RNRuntimeWorkletDecorator::decorate(
   rnRuntime.global().setProperty(
       rnRuntime, "__workletsModuleProxy", std::move(jsiWorkletsModuleProxy));
 
+  auto asyncQueue = std::make_shared<AsyncQueueImpl>("TestRuntimeAsyncQueue");
+
+  auto obj = jsi::Object(rnRuntime);
+
+  obj.setNativeState(
+      rnRuntime, std::static_pointer_cast<jsi::NativeState>(asyncQueue));
+
+  rnRuntime.global().setProperty(
+      rnRuntime, "_WORKLETS_ASYNC_QUEUE", std::move(obj));
+
   WorkletRuntimeCollector::install(rnRuntime);
 
 #ifndef NDEBUG

Copy link
Contributor

@mrousavy mrousavy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beat me to it! Wanted to draft something up today but couldn't find the time.

I'll test this tmrw, thanks so much! :)

@mrousavy
Copy link
Contributor

oh btw., we would still need support for capturing NativeState properly.

const myObj = ...
function worklet() {
  'worklet'
  myObj.myFunc()
}

myObj is an empty jsi::Object (or sometimes it contains some values) with jsi::NativeState - and myFunc() expects it's this to be bound to the myObj (which contains the NativeState).

Then it'd be ready for a VisionCamera prototype

@tjzel
Copy link
Collaborator Author

tjzel commented Jul 22, 2025

Could you elaborate on that? I'm not sure I get it, since you said that myObj is empty but then you invoke myFunc() on it. Is myFunc() a host function?

@mrousavy
Copy link
Contributor

Yes myFunc is a HostFunction that needs this bound.

@tjzel
Copy link
Collaborator Author

tjzel commented Jul 23, 2025

Could you give me a link to the place in the code when you use this pattern? I think I need to understand what's going on to implement that.

@mrousavy
Copy link
Contributor

This is how a function on a Nitro HybridObject looks like.

It's a jsi::HostFunction, which uses the const jsi::Value& thisValue (second argument in a jsi::HostFunction) to get the bound this value of the function - which, if you call it from JS like this;

myObj.myFunc()

..will be myObj. myObj is then the jsi::Object that contains jsi::NativeState.

So for Worklets what needs to be done to properly keep this functionality;

  • capture myObj as a normal jsi::Object as usual, and copy any values if it has some (e.g. in debug it has a __type: string property), and most importantly; copy over the std::shared_ptr<jsi::NativeState> it contains.

@mrousavy
Copy link
Contributor

mrousavy commented Jul 23, 2025

The simplest way to test this is;

  • Install react-native-nitro-modules into REA example app
  • In native C++, create a HybridObject;
    using namespace margelo::nitro;
    
    class HybridTestObject: public HybridObject {
    public:
      HybridTestObject(): HybridObject(NAME) { }
      static constexpr auto NAME = "TestObject";
    
    public:
      void hello() {
        std::cout << "It works!" << std::endl;
      }
    
    protected:
      void loadHybridMethods() override {
        HybridObject::loadHybridMethods();
        registerHybrids(this, [](Prototype& proto) {
          proto.registerHybridMethod("hello", & HybridTestObject::hello);
        });
      }
    };
  1. Create an instance of HybridTestObject, then get it to JS - if you already have a JS Runtime you can just call:
    auto hybrid = std::make_shared<HybridTestObject>();
    jsi::Value object = hybrid->toObject(runtime);
    runtime.global().setProperty(runtime, "testObject", std::move(object));

..or just install a Nitro Module like e.g. react-native-unistyles, react-native-nitro-image, react-native-video v7, ...

EDIT: Btw the most efficient way of capturing Nitro HybridObjects would be if you guys would actually call the toObject(runtime) function on the HybridObject. This is cached (jsi::WeakObject per runtime) and super fast. But it would require you to do a #if __has_include(<NitroModules/HybridObject.hpp>) - not sure if you guys want to do that.

@tjzel
Copy link
Collaborator Author

tjzel commented Jul 23, 2025

I think this is already supported right off the bat, but let me double check it 🙏

@tjzel
Copy link
Collaborator Author

tjzel commented Jul 23, 2025

Yep, I tested it - HostFunction & this and the native state work already. Regarding the #ifdef, I don't mind putting it in the code if it's going to be non-invasive.

@mrousavy
Copy link
Contributor

awesome!

@tjzel tjzel changed the base branch from main to @tjzel/worklets/virtual-async-queue July 24, 2025 18:14
Base automatically changed from @tjzel/worklets/virtual-async-queue to main July 25, 2025 08:34
@tjzel tjzel changed the base branch from main to @tjzel/worklets/runtime-lock July 25, 2025 14:19
@tjzel tjzel requested a review from Copilot July 25, 2025 22:15
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for passing user-implemented AsyncQueue objects to the createWorkletRuntime function, fulfilling a feature request from issue #7831. The implementation uses jsi::NativeState to avoid boilerplate and provides backward compatibility through function overloads.

  • Enhanced createWorkletRuntime to accept optional custom AsyncQueue implementations
  • Updated the type system with discriminated unions to ensure proper queue configuration
  • Modified the C++ layer to handle custom queue injection through the JSI bridge

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/react-native-worklets/src/runtimes.ts Added overloads and queue configuration options to createWorkletRuntime
packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts Extended interface to include queue parameters
packages/react-native-worklets/src/WorkletsModule/NativeWorklets.ts Updated implementation to pass queue parameters through
packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts Simplified JSWorklets implementation signature
packages/react-native-worklets/typetests/createWorkletRuntime.tsx Added comprehensive type tests for new queue configuration options
packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h Added queue parameter to constructor and runtime assertion
packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp Updated constructor to accept and store custom queue
packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeManager.h Extended createWorkletRuntime method signature
packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeManager.cpp Updated runtime creation to pass custom queue
packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp Added queue extraction logic and parameter handling

Base automatically changed from @tjzel/worklets/runtime-lock to main July 29, 2025 14:43
@tjzel tjzel marked this pull request as ready for review July 29, 2025 15:07
@tjzel tjzel requested a review from piaskowyk July 29, 2025 15:16
@tjzel tjzel added this pull request to the merge queue Jul 30, 2025
Merged via the queue into main with commit 9cec3c2 Jul 30, 2025
18 checks passed
@tjzel tjzel deleted the @tjzel/worklets/runtime-async-queue branch July 30, 2025 08:54
@tjzel
Copy link
Collaborator Author

tjzel commented Jul 30, 2025

@mrousavy We got it merged 🥳 It will be available in [email protected] soon™!

@mrousavy
Copy link
Contributor

super cool!! thanks

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

Successfully merging this pull request may close these issues.

3 participants