Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { ChatBubble } from './src/chat/ChatBubble';
import type { RenderedChatItem } from './src/native/ComposeChatListNativeComponent';
import {
call,
getCurrentRuntime,
OnRuntime,
threadedComponent,
Threaded,
Expand Down Expand Up @@ -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(
Expand Down
20 changes: 10 additions & 10 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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');
Expand Down
13 changes: 4 additions & 9 deletions example/src/examples/fibonacciRuntimeFunction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { runtimeFunction } from '@react-native-runtimes/core';
import { getCurrentRuntime, runtimeFunction } from '@react-native-runtimes/core';

export type FibonacciResult = {
input: number;
Expand All @@ -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,
};
}

Expand Down
13 changes: 4 additions & 9 deletions example/src/examples/heavyWorkloadRuntimeFunction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { runtimeFunction } from '@react-native-runtimes/core';
import { getCurrentRuntime, runtimeFunction } from '@react-native-runtimes/core';

export type HeavyRunResult = {
n: number;
Expand Down Expand Up @@ -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,
};
}

Expand Down
11 changes: 2 additions & 9 deletions example/src/examples/twoRuntimesArchitecture.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
getCurrentRuntime,
ThreadedRuntime,
registerThreadedHeadlessTask,
runtimeFunction,
Expand Down Expand Up @@ -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(
Expand Down
45 changes: 40 additions & 5 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
```
Expand Down Expand Up @@ -145,8 +149,7 @@ index.two-runtimes-business-runtime.ts

Only files matching `index.<runtime>.ts` in the project root are discovered.
The generated entry requires a file when `<runtime>` 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.
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 0 additions & 12 deletions packages/core/ios/ThreadedRuntime.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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:)]) {
Expand Down
73 changes: 41 additions & 32 deletions packages/core/src/ThreadedRuntime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -198,11 +218,7 @@ function getRuntimeFunctionsNitro() {
}

function installRuntimeFunctionJsi() {
const globals = globalThis as RuntimeFunctionJsiGlobal;
if (
globals.__rnrRegisterRuntimeFunction &&
globals.__rnrCallRuntimeFunction
) {
if (hasRuntimeFunctionJsiBindings()) {
return;
}

Expand Down Expand Up @@ -325,10 +341,7 @@ export function registerRuntimeFunction<TFunction extends AnyFunction>(
) {
installRuntimeFunctionJsi();
runtimeFunctions.set(id, loadFunction as RuntimeFunctionLoader);
(globalThis as RuntimeFunctionJsiGlobal).__rnrRegisterRuntimeFunction?.(
id,
loadFunction as RuntimeFunctionLoader,
);
registerRuntimeFunctionBinding(id, loadFunction as RuntimeFunctionLoader);
}

function attachRuntimeFunction<TFunction extends AnyFunction>(
Expand Down Expand Up @@ -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[];
Expand Down Expand Up @@ -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() {},
Expand Down
Loading
Loading