Skip to content

feat: Add CallInvoker to installJSIBindings(..)/BindingsInstaller#46851

Closed
mrousavy wants to merge 13 commits into
facebook:mainfrom
mrousavy:feat/add-callinvoker-to-installjsibindings
Closed

feat: Add CallInvoker to installJSIBindings(..)/BindingsInstaller#46851
mrousavy wants to merge 13 commits into
facebook:mainfrom
mrousavy:feat/add-callinvoker-to-installjsibindings

Conversation

@mrousavy
Copy link
Copy Markdown
Contributor

@mrousavy mrousavy commented Oct 5, 2024

Summary:

While the new installJSIBindings(..)/BindingsInstaller functionality allows you to synchronously set up stuff in the JS runtime before JS runs, there is no access to the JS CallInvoker, meaning you cannot really set up anything that uses callbacks or asynchronous code.

Nitro needs this.

Changelog:

[IOS] [ADDED] - Add CallInvoker to installJSIBindings(..)
[ANDROID] [ADDED] - Add CallInvoker to BindingsInstaller

Test Plan:

In SampleTurboModuleJSIBindings, we can now use the CallInvoker.

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 5, 2024
@mrousavy
Copy link
Copy Markdown
Contributor Author

mrousavy commented Oct 5, 2024

Note: This is technically not backwards compatible since it adds a new parameter to an existing function.

If anyone uses this, they need to update their method signature.

I am not aware of any public repos using installJSIBindings(...)/BindingsInstaller as it was just recently introduced, so I think it should be fine, but if you guys want I can also make this backwards compatible by leaving in both methods - one with and one without CallInvoker.

@facebook-github-bot facebook-github-bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Oct 5, 2024
@javache
Copy link
Copy Markdown
Member

javache commented Oct 7, 2024

Will import locally to test, but would be great to think about ways to make this backwards compatible.

@facebook-github-bot
Copy link
Copy Markdown
Contributor

@javache has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@mrousavy
Copy link
Copy Markdown
Contributor Author

mrousavy commented Oct 7, 2024

Build failed because I am not passing along the CallInvoker here:

Is there any way to access the CallInvoker on a RuntimeScheduler?

@mrousavy
Copy link
Copy Markdown
Contributor Author

mrousavy commented Oct 7, 2024

Will import locally to test, but would be great to think about ways to make this backwards compatible.

Sure - I'll add both methods.

@mrousavy
Copy link
Copy Markdown
Contributor Author

mrousavy commented Oct 7, 2024

I guess I could create an instance of RuntimeSchedulerCallInvoker (with the existing RuntimeScheduler) just to pass to that function, not sure if it's a good idea to do that though?

EDIT: I did just that. Let me know if there is already an existing instance of CallInvoker somewhere in that location (ReactInstance.cpp:461), if not I think this is fine either way. The allocation is lightweight.

@mrousavy
Copy link
Copy Markdown
Contributor Author

mrousavy commented Oct 7, 2024

backwards compatibility might look a bit ugly

Screenshot 2024-10-07 at 12 05 41

@javache
Copy link
Copy Markdown
Member

javache commented Oct 7, 2024

backwards compatibility might look a bit ugly

Screenshot 2024-10-07 at 12 05 41

That seems pretty reasonable to me. We add a little bit of overhead for the legacy path. Just make sure to document is a deprecated and clean it up after the next branch cut :)

timerManager_->attachGlobals(runtime);

bindingsInstallFunc(runtime);
auto callInvoker = std::make_shared<RuntimeSchedulerCallInvoker>(runtimeScheduler_);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should able to get to the existing one and avoid allocating a new one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

How? Do you have any pointers for me?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'll have a look. I assumed this would be accessible from Reactinstance, since C++ TurboModules get it, but I may be wrong.

mrousavy and others added 2 commits October 7, 2024 13:46
…ule/ReactCommon/BindingsInstallerHolder.cpp

Co-authored-by: Pieter De Baets <pieter.debaets@gmail.com>
…form/ios/ReactCommon/RCTTurboModuleWithJSIBindings.h

Co-authored-by: Pieter De Baets <pieter.debaets@gmail.com>
#include <jsireact/JSIExecutor.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
#include <react/renderer/RuntimeSchedulerCallInvoker.h>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
#include <react/renderer/RuntimeSchedulerCallInvoker.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h>

@javache
Copy link
Copy Markdown
Member

javache commented Oct 7, 2024

Actually, we should only support this for the TurboModule-based bindings installer - and TurboModuleManager already has the jsCallInvoker. I don't think you need any of the changes to ReactInstance.

@mrousavy
Copy link
Copy Markdown
Contributor Author

mrousavy commented Oct 7, 2024

Sorry, you're right I messed up my local files. I built this inside another react-native app and had a few patches that conflicted with eachother.

I now checked out this branch in rn-tester/ specifically and will fix the issues :)

static auto constexpr kJavaDescriptor =
"Lcom/facebook/react/turbomodule/core/interfaces/BindingsInstallerHolder;";
using BindingsInstallFunc = std::function<void(jsi::Runtime& runtime,
std::shared_ptr<CallInvoker> callInvoker)>;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@javache should we make this const& as well?

_turboModuleCache.insert({moduleName, turboModule});

if ([module respondsToSelector:@selector(installJSIBindingsWithRuntime:)]) {
if ([module respondsToSelector:@selector(installJSIBindingsWithRuntime:callInvoker:)]) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so we do have an API to init the TM with the call invoker, i'm wondering if we can use this and then reference it in the implementation of installJSIBindingsWithRuntime:

https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm#L693-L697

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I mean we don't have the jsi::Runtime in there where you linked - or are you saying a TurboModule can just fetch the CallInvoker themselves using the callInvoker property (if implemented)? I guess that's fine too, but I think this API is a bit more verbose

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think both of these options work, and perhaps for symmetry with Android, sharing the callInvoker here is nice too? But it would allow us to avoid a breaking API change here too, which is nice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

so we do have an API to init the TM with the call invoker, i'm wondering if we can use this and then reference it in the implementation of installJSIBindingsWithRuntime:

https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm#L693-L697

What's the difference between this codepath and the one in https://github.com/facebook/react-native/blob/main/packages/react-native/React/Base/RCTModuleData.mm#L206-L208 ? Legacy native modules vs TurboModules? Or old bridge vs bridgeless?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The one in RCTModuleData can not access jsi::Runtime - it both doesn't have a reference to it, and it isn't guaranteed to run on the proper Thread (legacy modules)

Copy link
Copy Markdown
Contributor

@philIip philIip Oct 8, 2024

Choose a reason for hiding this comment

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

so we do have an API to init the TM with the call invoker, i'm wondering if we can use this and then reference it in the implementation of installJSIBindingsWithRuntime:
https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm#L693-L697

What's the difference between this codepath and the one in https://github.com/facebook/react-native/blob/main/packages/react-native/React/Base/RCTModuleData.mm#L206-L208 ? Legacy native modules vs TurboModules? Or old bridge vs bridgeless?

it's just a forward compatible API. so the idea is that you can change your code and not worry about legacy vs new arch, you will be guaranteed that your module has access to the callinvoker.

essentially in your module, you could have sth like:

- (void)installJSIBindingsWithRuntime:(jsi::Runtime &)runtime
{
  auto callinvoker = self.callInvoker;
  // do something with runtime & callInvoker
}

i do think having API symmetry would be nice though. i wonder if adding the CallInvoker to this argument will make the other ways of retrieving the callInvoker irrelevant.

auto installer = getBindingsInstaller(moduleInstance);
if (installer) {
installer->cthis()->installBindings(runtime);
installer->cthis()->installBindings(runtime, jsCallInvoker_);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we can actually get the callInvoker off the ReactContext, but i can see how that's inapplicable here... but i'm reading all of this and i'm starting to feel like a C++ TM is more appropriate here? what's the current limitation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I can't use a C++ TM because

  1. I need backwards compatibility for now (I guess this is only a temporary thing)
  2. I need the ReactApplicationContext. As far as I know, there's no way to get that in a C++ TM (since those are platform-independent)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I need it in NitroModules.kt (init) - this will (optionally) be passed along to other Nitro Modules. But it goes through C++ first.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I need it in NitroModules.kt (init) - this will (optionally) be passed along to other Nitro Modules. But it goes through C++ first.

thanks! is the idea that we want to replace install with installJSIBindings?

if so, is it possible to keep the current native pipeline to pass down the CallInvoker (i.e. do what you have now), but use installJSIBindings for the runtime installation? or will there be some sort of timing issue?

i can see how ergonomically that might feel weird, what's your feeling on that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep - I want to have a clear, public and guaranteed to be future-proof API to install Nitro into the jsi::Runtime.
The current approach works, but having to annotate with @OptIn(FrameworkAPI::class) doesn't sound very public-API-like to me?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

if so, is it possible to keep the current native pipeline to pass down the CallInvoker (i.e. do what you have now), but use installJSIBindings for the runtime installation? or will there be some sort of timing issue?

Well installJSIBindings (or BindingsInstaller on Android) is accessed before I can call install() on the module. So I don't have a CallInvoker yet at that point on Android.

@facebook-github-bot
Copy link
Copy Markdown
Contributor

@javache has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@facebook-github-bot facebook-github-bot added the Merged This PR has been merged. label Oct 9, 2024
@facebook-github-bot
Copy link
Copy Markdown
Contributor

@javache merged this pull request in 87bae7f.

@react-native-bot
Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @mrousavy in 87bae7f

When will my fix make it into a release? | How to file a pick request?

@mrousavy
Copy link
Copy Markdown
Contributor Author

mrousavy commented Oct 9, 2024

thanks guys!! 🖤

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

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants