diff --git a/example/App.tsx b/example/App.tsx index b4a834f..5d502f4 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -40,6 +40,7 @@ import { ChatBubble } from './src/chat/ChatBubble'; import type { RenderedChatItem } from './src/native/ComposeChatListNativeComponent'; import { call, + getCurrentRuntime, OnRuntime, threadedComponent, Threaded, @@ -2920,15 +2921,7 @@ function titleForRnMode(mode: RnBenchmarkMode) { } function runtimeKind() { - const globals = globalThis as { - __COMPOSE_CHAT_LIST_ENV__?: { kind?: string }; - __THREADED_RUNTIME_ENV__?: { kind?: string }; - }; - return ( - globals.__THREADED_RUNTIME_ENV__?.kind ?? - globals.__COMPOSE_CHAT_LIST_ENV__?.kind ?? - 'main' - ); + return getCurrentRuntime().kind ?? 'main'; } function scrollRnListToIndex( diff --git a/example/index.js b/example/index.js index 85a7431..ddac9e6 100644 --- a/example/index.js +++ b/example/index.js @@ -3,6 +3,10 @@ */ const { AppRegistry } = require('react-native'); +const { + getCurrentRuntime, + isMainRuntime, +} = require('@react-native-runtimes/core'); // Register threaded roots/callable modules in every runtime. Component modules // stay lazy; production runtime-specific entries are gated inside the generated @@ -15,17 +19,13 @@ if (typeof __DEV__ !== 'undefined' && __DEV__) { require('./index.business-runtime'); } -const threadedRuntimeEnv = global.__THREADED_RUNTIME_ENV__; -const isListEnvironment = - global._is_it_a_list_env === true && global.__COMPOSE_CHAT_LIST_ENV__; +const currentRuntime = getCurrentRuntime(); -if (threadedRuntimeEnv || isListEnvironment) { - if (threadedRuntimeEnv?.kind !== 'business-runtime') { - AppRegistry.registerComponent( - 'ComposeChatSecondRuntimeRnList', - () => require('./App').SecondRuntimeRnListApp, - ); - } +if (!isMainRuntime() && currentRuntime.kind !== 'business-runtime') { + AppRegistry.registerComponent( + 'ComposeChatSecondRuntimeRnList', + () => require('./App').SecondRuntimeRnListApp, + ); } const { name: appName } = require('./app.json'); diff --git a/example/src/examples/fibonacciRuntimeFunction.ts b/example/src/examples/fibonacciRuntimeFunction.ts index f58c482..0527d10 100644 --- a/example/src/examples/fibonacciRuntimeFunction.ts +++ b/example/src/examples/fibonacciRuntimeFunction.ts @@ -1,4 +1,4 @@ -import { runtimeFunction } from '@react-native-runtimes/core'; +import { getCurrentRuntime, runtimeFunction } from '@react-native-runtimes/core'; export type FibonacciResult = { input: number; @@ -9,15 +9,10 @@ export type FibonacciResult = { }; function runtimeInfo() { - const globals = globalThis as { - __THREADED_RUNTIME_ENV__?: { kind?: string; runtimeName?: string }; - __COMPOSE_CHAT_LIST_ENV__?: { kind?: string; runtimeName?: string }; - }; - const threadedEnv = globals.__THREADED_RUNTIME_ENV__; - const listEnv = globals.__COMPOSE_CHAT_LIST_ENV__; + const runtime = getCurrentRuntime(); return { - runtimeKind: threadedEnv?.kind ?? listEnv?.kind ?? 'main', - runtimeName: threadedEnv?.runtimeName ?? listEnv?.runtimeName ?? 'main', + runtimeKind: runtime.kind ?? 'main', + runtimeName: runtime.name, }; } diff --git a/example/src/examples/heavyWorkloadRuntimeFunction.ts b/example/src/examples/heavyWorkloadRuntimeFunction.ts index c5bcf54..0e97f8f 100644 --- a/example/src/examples/heavyWorkloadRuntimeFunction.ts +++ b/example/src/examples/heavyWorkloadRuntimeFunction.ts @@ -1,4 +1,4 @@ -import { runtimeFunction } from '@react-native-runtimes/core'; +import { getCurrentRuntime, runtimeFunction } from '@react-native-runtimes/core'; export type HeavyRunResult = { n: number; @@ -33,15 +33,10 @@ function recursiveFibonacci(n: number): number { } function runtimeInfo() { - const globals = globalThis as { - __THREADED_RUNTIME_ENV__?: { kind?: string; runtimeName?: string }; - __COMPOSE_CHAT_LIST_ENV__?: { kind?: string; runtimeName?: string }; - }; - const threadedEnv = globals.__THREADED_RUNTIME_ENV__; - const listEnv = globals.__COMPOSE_CHAT_LIST_ENV__; + const runtime = getCurrentRuntime(); return { - runtimeKind: threadedEnv?.kind ?? listEnv?.kind ?? 'main', - runtimeName: threadedEnv?.runtimeName ?? listEnv?.runtimeName ?? 'main', + runtimeKind: runtime.kind ?? 'main', + runtimeName: runtime.name, }; } diff --git a/example/src/examples/twoRuntimesArchitecture.ts b/example/src/examples/twoRuntimesArchitecture.ts index bad791e..1473142 100644 --- a/example/src/examples/twoRuntimesArchitecture.ts +++ b/example/src/examples/twoRuntimesArchitecture.ts @@ -1,4 +1,5 @@ import { + getCurrentRuntime, ThreadedRuntime, registerThreadedHeadlessTask, runtimeFunction, @@ -98,15 +99,7 @@ export const twoRuntimeMetrics = ); function runtimeKind() { - const globals = globalThis as { - __COMPOSE_CHAT_LIST_ENV__?: { kind?: string }; - __THREADED_RUNTIME_ENV__?: { kind?: string }; - }; - return ( - globals.__THREADED_RUNTIME_ENV__?.kind ?? - globals.__COMPOSE_CHAT_LIST_ENV__?.kind ?? - 'main' - ); + return getCurrentRuntime().kind ?? 'main'; } function nextMetrics( diff --git a/packages/core/README.md b/packages/core/README.md index 9b94244..9b7c0e7 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -113,7 +113,11 @@ Add the generated directory to `.gitignore`: Load the generated entry only in the secondary runtime path: ```js -if (global.__THREADED_RUNTIME_ENV__) { +const { + isMainRuntime, +} = require('@react-native-runtimes/core'); + +if (!isMainRuntime()) { require('./.threaded-runtime/entry'); } ``` @@ -145,8 +149,7 @@ index.two-runtimes-business-runtime.ts Only files matching `index..ts` in the project root are discovered. The generated entry requires a file when `` matches either -`global.__THREADED_RUNTIME_ENV__.kind` or -`global.__THREADED_RUNTIME_ENV__.runtimeName`. +`getCurrentRuntime().kind` or `getCurrentRuntime().name`. The component module is required only when `ThreadedRuntimeHost` receives that component name. @@ -590,8 +593,11 @@ creates a surface: ```js const { AppRegistry } = require('react-native'); +const { + isMainRuntime, +} = require('@react-native-runtimes/core'); -if (global.__THREADED_RUNTIME_ENV__) { +if (!isMainRuntime()) { require('./App'); // component registrations AppRegistry.registerComponent( 'ThreadedRuntimeHost', @@ -666,13 +672,42 @@ ThreadedRuntime.prewarmBusinessRuntime(applicationContext, "business-runtime") That runtime receives `global.__THREADED_RUNTIME_ENV__` before the bundle runs: ```tsx -if (global.__THREADED_RUNTIME_ENV__?.kind === 'business-runtime') { +import { getCurrentRuntime } from '@react-native-runtimes/core'; + +if (getCurrentRuntime().kind === 'business-runtime') { require('./src/businessRuntimeEntry'); } else { require('./src/mainRuntimeEntry'); } ``` +## Runtime Globals + +Supported runtime identity is exposed through the public API: + +```ts +import { getCurrentRuntime, isMainRuntime } from '@react-native-runtimes/core'; + +const runtime = getCurrentRuntime(); +``` + +- `getCurrentRuntime()` returns `{ isMain, name, kind }` +- `isMainRuntime()` is the preferred bootstrap check + +The only supported runtime global is `global.__THREADED_RUNTIME_ENV__`, which is +installed before bundle execution on secondary runtimes and currently contains: + +- `runtimeName` +- `kind` + +Internal globals used by runtime-function dispatch still exist, but they are not +part of the app-facing API: + +- `global.__rnrRegisterRuntimeFunction` +- `global.__rnrCallRuntimeFunction` + +Apps should not depend on those names directly. + iOS threaded runtimes already use the configured React Native delegate for native-module lookup, so `ThreadedRuntime.prewarmBusinessRuntime("business-runtime")` uses the app's module resolution path. diff --git a/packages/core/android/src/main/java/com/nativecompose/threadedruntime/ThreadedRuntime.kt b/packages/core/android/src/main/java/com/nativecompose/threadedruntime/ThreadedRuntime.kt index 95bc117..0c075f8 100644 --- a/packages/core/android/src/main/java/com/nativecompose/threadedruntime/ThreadedRuntime.kt +++ b/packages/core/android/src/main/java/com/nativecompose/threadedruntime/ThreadedRuntime.kt @@ -498,20 +498,9 @@ private class ThreadedRuntimeBundleLoader( """ var __threadedRuntimeGlobal = typeof globalThis !== 'undefined' ? globalThis : Function('return this')(); - __threadedRuntimeGlobal.global = __threadedRuntimeGlobal; - __threadedRuntimeGlobal.globalThis = __threadedRuntimeGlobal; - __threadedRuntimeGlobal._is_it_a_list_env = true; __threadedRuntimeGlobal.__THREADED_RUNTIME_ENV__ = { kind: ${jsString(options.kind)}, - runtimeName: ${jsString(runtimeName)}, - isBackgroundRuntime: ${options.kind != ThreadedRuntime.DEFAULT_RUNTIME_KIND}, - useMainNativeModules: ${options.useMainNativeModules}, - version: 1 - }; - __threadedRuntimeGlobal.__COMPOSE_CHAT_LIST_ENV__ = { - kind: 'background-list', - runtimeName: ${jsString(runtimeName)}, - version: 1 + runtimeName: ${jsString(runtimeName)} }; """.trimIndent(), ) diff --git a/packages/core/cpp/nativecompose/threadedruntime/RuntimeFunctionJsi.cpp b/packages/core/cpp/nativecompose/threadedruntime/RuntimeFunctionJsi.cpp index c4cae28..d8c549f 100644 --- a/packages/core/cpp/nativecompose/threadedruntime/RuntimeFunctionJsi.cpp +++ b/packages/core/cpp/nativecompose/threadedruntime/RuntimeFunctionJsi.cpp @@ -140,10 +140,6 @@ void installRuntimeFunctionJsi(Runtime &runtime, const std::string &runtimeName) runtime, "__rnrCallRuntimeFunction", std::move(callFunction)); - runtime.global().setProperty( - runtime, - "__rnrRuntimeFunctionCacheRuntimeName", - String::createFromUtf8(runtime, runtimeName)); } } // namespace nativecompose::threadedruntime diff --git a/packages/core/ios/ThreadedRuntime.mm b/packages/core/ios/ThreadedRuntime.mm index 951a2ec..e3f8677 100644 --- a/packages/core/ios/ThreadedRuntime.mm +++ b/packages/core/ios/ThreadedRuntime.mm @@ -190,24 +190,12 @@ - (void)host:(RCTHost *)host didInitializeRuntime:(facebook::jsi::Runtime &)runt _didInitializeRuntime = YES; auto global = runtime.global(); - global.setProperty(runtime, "global", global); - global.setProperty(runtime, "globalThis", global); - global.setProperty(runtime, "_is_it_a_list_env", true); auto threadedEnv = facebook::jsi::Object(runtime); threadedEnv.setProperty(runtime, "kind", facebook::jsi::String::createFromUtf8(runtime, [_kind UTF8String])); threadedEnv.setProperty(runtime, "runtimeName", facebook::jsi::String::createFromUtf8(runtime, [_runtimeName UTF8String])); - threadedEnv.setProperty(runtime, "isBackgroundRuntime", ![_kind isEqualToString:ThreadedRuntimeDefaultRuntimeKind]); - threadedEnv.setProperty(runtime, "useMainNativeModules", true); - threadedEnv.setProperty(runtime, "version", 1); global.setProperty(runtime, "__THREADED_RUNTIME_ENV__", threadedEnv); - auto listEnv = facebook::jsi::Object(runtime); - listEnv.setProperty(runtime, "kind", facebook::jsi::String::createFromUtf8(runtime, "background-list")); - listEnv.setProperty(runtime, "runtimeName", facebook::jsi::String::createFromUtf8(runtime, [_runtimeName UTF8String])); - listEnv.setProperty(runtime, "version", 1); - global.setProperty(runtime, "__COMPOSE_CHAT_LIST_ENV__", listEnv); - nativecompose::threadedruntime::installRuntimeFunctionJsi(runtime, [_runtimeName UTF8String]); if ([_delegate respondsToSelector:@selector(host:didInitializeRuntime:)]) { diff --git a/packages/core/src/ThreadedRuntime.tsx b/packages/core/src/ThreadedRuntime.tsx index 4d66903..2d9c936 100644 --- a/packages/core/src/ThreadedRuntime.tsx +++ b/packages/core/src/ThreadedRuntime.tsx @@ -118,17 +118,10 @@ const nativeRuntime = NativeModules.ThreadedRuntime as let runtimeFunctionsNitro: ThreadedRuntimeFunctionsNitro | null | undefined; let didWarnRuntimeFunctionsNitroUnavailable = false; +let didInstallThreadedRuntimeEventEmitterFallback = false; function currentRuntimeName() { - const globals = globalThis as { - __THREADED_RUNTIME_ENV__?: { runtimeName?: string }; - __COMPOSE_CHAT_LIST_ENV__?: { runtimeName?: string }; - }; - return ( - globals.__THREADED_RUNTIME_ENV__?.runtimeName ?? - globals.__COMPOSE_CHAT_LIST_ENV__?.runtimeName ?? - DEFAULT_RUNTIME_NAME - ); + return getCurrentRuntime().name ?? DEFAULT_RUNTIME_NAME; } export const MAIN_RUNTIME_NAME = 'main'; @@ -142,10 +135,8 @@ export type CurrentRuntimeInfo = { export function getCurrentRuntime(): CurrentRuntimeInfo { const globals = globalThis as { __THREADED_RUNTIME_ENV__?: { runtimeName?: string; kind?: string }; - __COMPOSE_CHAT_LIST_ENV__?: { runtimeName?: string; kind?: string }; }; - const env = - globals.__THREADED_RUNTIME_ENV__ ?? globals.__COMPOSE_CHAT_LIST_ENV__; + const env = globals.__THREADED_RUNTIME_ENV__; if (env?.runtimeName) { return { isMain: false, @@ -164,6 +155,35 @@ export function isMainRuntime(): boolean { return getCurrentRuntime().isMain; } +function getRuntimeFunctionJsiBindings() { + const globals = globalThis as RuntimeFunctionJsiGlobal; + return { + registerRuntimeFunction: globals.__rnrRegisterRuntimeFunction, + callRuntimeFunction: globals.__rnrCallRuntimeFunction, + }; +} + +function hasRuntimeFunctionJsiBindings() { + const bindings = getRuntimeFunctionJsiBindings(); + return !!( + bindings.registerRuntimeFunction && bindings.callRuntimeFunction + ); +} + +function registerRuntimeFunctionBinding( + id: string, + loadFunction: RuntimeFunctionLoader, +) { + getRuntimeFunctionJsiBindings().registerRuntimeFunction?.(id, loadFunction); +} + +function callRuntimeFunctionBinding(functionId: string, argsJson: string) { + return getRuntimeFunctionJsiBindings().callRuntimeFunction?.( + functionId, + argsJson, + ); +} + function getRuntimeFunctionsNitro() { if (runtimeFunctionsNitro !== undefined) { return runtimeFunctionsNitro; @@ -198,11 +218,7 @@ function getRuntimeFunctionsNitro() { } function installRuntimeFunctionJsi() { - const globals = globalThis as RuntimeFunctionJsiGlobal; - if ( - globals.__rnrRegisterRuntimeFunction && - globals.__rnrCallRuntimeFunction - ) { + if (hasRuntimeFunctionJsiBindings()) { return; } @@ -325,10 +341,7 @@ export function registerRuntimeFunction( ) { installRuntimeFunctionJsi(); runtimeFunctions.set(id, loadFunction as RuntimeFunctionLoader); - (globalThis as RuntimeFunctionJsiGlobal).__rnrRegisterRuntimeFunction?.( - id, - loadFunction as RuntimeFunctionLoader, - ); + registerRuntimeFunctionBinding(id, loadFunction as RuntimeFunctionLoader); } function attachRuntimeFunction( @@ -624,10 +637,12 @@ function loadRegisteredRuntimeFunction(functionId: string) { function callRegisteredRuntimeFunction(functionId: string, argsJson: string) { installRuntimeFunctionJsi(); - const jsiCall = (globalThis as RuntimeFunctionJsiGlobal) - .__rnrCallRuntimeFunction; + const jsiCall = callRuntimeFunctionBinding; if (jsiCall) { - return jsiCall(functionId, argsJson); + const result = jsiCall(functionId, argsJson); + if (result !== undefined) { + return result; + } } let args: unknown[]; @@ -844,17 +859,11 @@ const registerCallableModule = ) => void; function installThreadedRuntimeEventEmitterFallback() { - const globals = globalThis as { - __THREADED_RUNTIME_EVENT_EMITTER_FALLBACK__?: boolean; - }; - if ( - Platform.OS !== 'ios' || - globals.__THREADED_RUNTIME_EVENT_EMITTER_FALLBACK__ - ) { + if (Platform.OS !== 'ios' || didInstallThreadedRuntimeEventEmitterFallback) { return; } - globals.__THREADED_RUNTIME_EVENT_EMITTER_FALLBACK__ = true; + didInstallThreadedRuntimeEventEmitterFallback = true; registerCallableModule('RCTEventEmitter', () => ({ receiveEvent() {}, receiveTouches() {}, diff --git a/website/docs/installation.md b/website/docs/installation.md index 042b196..9fe3679 100644 --- a/website/docs/installation.md +++ b/website/docs/installation.md @@ -124,7 +124,11 @@ Add the generated folder to `.gitignore`: Load the generated entry only inside threaded runtimes: ```js -if (global.__THREADED_RUNTIME_ENV__ || global._is_it_a_list_env === true) { +const { + isMainRuntime, +} = require('@react-native-runtimes/core'); + +if (!isMainRuntime()) { require('./.threaded-runtime/entry'); } ``` @@ -134,4 +138,20 @@ The generated entry registers lazy component loaders and the `ThreadedRuntimeHos For runtime-specific startup code, add root-level files named `index..ts`, for example `index.business-runtime.ts`. The generated entry emits static conditional requires for those files and matches `` -against `global.__THREADED_RUNTIME_ENV__.kind` and `.runtimeName`. +against `getCurrentRuntime().kind` and `.name`. + +## Runtime Globals + +Use the public runtime helpers in app code: + +```ts +import { getCurrentRuntime, isMainRuntime } from '@react-native-runtimes/core'; +``` + +`global.__THREADED_RUNTIME_ENV__` is the only supported runtime identity global. +It is installed before the secondary runtime bundle executes and currently +contains `kind` and `runtimeName`. + +`global.__rnrRegisterRuntimeFunction` and `global.__rnrCallRuntimeFunction` are +internal implementation details for runtime functions and should not be used by +app code.