Skip to content

Commit f5fdc9f

Browse files
chore: update docs
1 parent cbebd21 commit f5fdc9f

27 files changed

Lines changed: 349 additions & 259 deletions

docs/content/docs/concepts/runtime-functions.mdx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ target runtime, called by stable id, with JSON-serialized arguments.
1919
```tsx
2020
import { call, runtimeFunction } from '@react-native-runtimes/core';
2121

22-
export const fibonacci = runtimeFunction((n: number) => {
23-
return fib(n);
22+
export const fibonacci = runtimeFunction((n: number): number => {
23+
return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
2424
});
2525

2626
const result = await call(fibonacci).on('worker-runtime')(38);
@@ -35,15 +35,17 @@ runtimes, or against different worker pools.
3535
async function refreshCache(key: string) {
3636
'background';
3737
await cacheStore.hydrate();
38-
return cacheStore.get(key);
38+
return cacheStore.getPathState(key);
3939
}
4040

4141
const value = await refreshCache('settings');
4242
```
4343

44-
The first directive (`'background'`, `'main'`, or any runtime name) is the
45-
target. Metro rewrites the function into a registered runtime function plus a
46-
local scheduled alias, so call sites stay ordinary.
44+
The first directive (`'background'` or any other secondary runtime name) is
45+
the target. Metro rewrites the function into a registered runtime function
46+
plus a local scheduled alias, so call sites stay ordinary. Directives target
47+
**secondary** runtimes — to get data back to the main runtime, return a value
48+
or write to [shared state](/docs/shared-state/overview).
4749

4850
<Callout type="info" title="Use the directive for fixed-runtime helpers">
4951
When a function semantically belongs to one runtime, the directive form
@@ -63,10 +65,12 @@ Code is never sent over the wire. Only **id + args**.
6365
## Constraints
6466

6567
- Arguments and return values must be JSON-serializable.
66-
- The function must be **exported** and wrapped in `runtimeFunction` *or* use a top-level directive.
67-
- Directive functions must be declared at module scope.
68-
- Closures aren't captured — pass everything through arguments.
69-
- Inline lambdas and non-exported functions are not supported.
68+
- `runtimeFunction(...)` values must be **exported** at module scope. Directive
69+
functions only need to be declared at module scope — Metro exports the
70+
generated registration for you.
71+
- Closures aren't captured — pass everything through arguments (module-scope
72+
imports work, since every runtime loads the same bundle).
73+
- Inline lambdas are not supported.
7074

7175
See [Scheduling functions on another runtime](/docs/threaded-runtime/scheduling-functions)
7276
for the full API.

docs/content/docs/concepts/runtimes.mdx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ const RUNTIME_NAME = 'messages-runtime';
1414
```
1515

1616
When you ask for `messages-runtime` to render something, native spawns the
17-
runtime (if it doesn't already exist), executes the same bundle inside it,
18-
loads `.threaded-runtime/entry` to register components and functions, and then
19-
mounts a `ThreadedRuntimeHost` that renders the requested component.
17+
runtime (if it doesn't already exist) and executes the same bundle inside it.
18+
The bundle's `index.js` requires `.threaded-runtime/entry`, which registers
19+
components and functions, and native then mounts a `ThreadedRuntimeHost` that
20+
renders the requested component.
2021

2122
## Naming runtimes
2223

@@ -59,7 +60,6 @@ A runtime can be implicitly started by any of these:
5960
- Mounting `<ThreadedScreen runtimeName="..." />` (auto-prewarms)
6061
- Calling `ThreadedRuntime.runHeadlessTask` with that runtime name
6162
- Calling `call(fn).on('...')(...)`
62-
- Native dispatch: `ThreadedRuntime.dispatchHeadlessTask`
6363

6464
## Cost
6565

@@ -79,15 +79,19 @@ runtimes by their logical owner:
7979

8080
## Discovery
8181

82-
Inside any threaded runtime, two globals describe where you are:
82+
Use `getCurrentRuntime()` to find out which runtime your code is running on:
8383

8484
```ts
85-
declare const __THREADED_RUNTIME_ENV__: {
86-
kind: 'main' | 'threaded' | 'business';
87-
runtimeName: string;
88-
};
85+
import { getCurrentRuntime, isMainRuntime } from '@react-native-runtimes/core';
86+
87+
const { isMain, name, kind } = getCurrentRuntime();
88+
// main runtime: { isMain: true, name: 'main', kind: null }
89+
// threaded runtime: { isMain: false, name: 'messages-runtime', kind: 'threaded-runtime' }
90+
// business runtime: { isMain: false, name: 'background', kind: 'business-runtime' }
8991
```
9092

91-
This is how the per-runtime bootstrap mechanism works — the generated entry
92-
checks `runtimeName` and conditionally requires `index.<runtime>.ts`. See
93+
Under the hood, native sets a `__THREADED_RUNTIME_ENV__` global (with
94+
`runtimeName` and `kind`) in every secondary runtime before the bundle
95+
evaluates. The generated entry uses it to conditionally require
96+
`index.<runtime>.ts`. See
9397
[Metro setup](/docs/getting-started/metro-setup#per-runtime-bootstrap-files).

docs/content/docs/concepts/shared-state.mdx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,11 @@ Subscribers fire for **related paths**, not only exact matches:
6060
That makes broad subscriptions ("anything under conversations changed") cheap
6161
to express.
6262

63-
<Callout title="One writer per path is the rule of thumb" type="info">
64-
If two runtimes can both write to the same path, prefer `update(...)` or a
65-
reducer — they read the current value through the lock and write back, so
66-
concurrent updates compose instead of clobbering each other.
63+
<Callout title="One writer per path is the rule" type="warn">
64+
Writes are last-writer-wins — there is no compare-and-swap. `update(...)`
65+
reads the runtime's local mirror of the value, so two runtimes writing the
66+
same path concurrently can still clobber each other. Design so each path has
67+
a single writing runtime; other runtimes read and subscribe.
6768
</Callout>
6869

6970
## When to reach for it instead of props

docs/content/docs/concepts/threaded-components.mdx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ main runtime messages-runtime
2828
(native view) (real component)
2929
```
3030

31-
The boundary is a native view, not a JS bridge. Props cross it once, then the
32-
threaded runtime owns rendering, scheduling, and re-rendering for that
33-
subtree.
31+
The boundary is a native view, not a JS bridge. Props cross it once at mount
32+
(changing them later remounts the surface), then the threaded runtime owns
33+
rendering, scheduling, and re-rendering for that subtree.
3434

3535
## Direct child rule
3636

@@ -58,6 +58,10 @@ subtree.
5858
Metro needs a stable, file-based reference to register. It rewrites the direct
5959
child into an exported `const` and registers it under a stable id.
6060

61+
Auto-registration only sees **function declarations in the same file** as the
62+
`<OnRuntime>` usage. For a component imported from another file, export it
63+
wrapped in `threadedComponent('name', Component)` instead.
64+
6165
<Callout title="When the direct child rule feels restrictive" type="info">
6266
Use `threadedComponent` to give the component an explicit id, then put any
6367
wrapper you like *inside* that component. The registered component is the
@@ -66,8 +70,8 @@ child into an exported `const` and registers it under a stable id.
6670

6771
## Props cross the boundary as JSON
6872

69-
Props are serialized at the main runtime and deserialized on the threaded
70-
runtime. They must be JSON-safe:
73+
Props are serialized at the main runtime when the surface mounts and
74+
deserialized on the threaded runtime. They must be JSON-safe:
7175

7276
- ✅ Strings, numbers, booleans, `null`
7377
- ✅ Plain objects and arrays of the above

docs/content/docs/getting-started/android-setup.mdx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,12 @@ nativecompose::threadedruntime::dispatchHeadlessTask(
116116
);
117117
```
118118

119-
Native dispatch is queued — if the target runtime is still starting, the
120-
runtime flushes the task once it's ready. The returned status reflects whether
121-
the dispatch was accepted, not whether the JS body has finished.
119+
Dispatch is fire-and-forget: the task is queued, the runtime is started if
120+
needed, and queued tasks are flushed once the runtime is ready.
122121

123122
## Common issues
124123

125-
<Callout type="warn" title="`Could not find Nitro module`">
124+
<Callout type="warn" title="`Cannot create an instance of HybridObject ... It has not yet been registered`">
126125
Add `NitroModulesPackage()` to `setExtraReactPackagesProvider` and make sure
127126
the package is also in your normal host packages (so the main runtime can
128127
use it too).

docs/content/docs/getting-started/installation.mdx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,17 @@ cd ios
2727
bundle exec pod install
2828
```
2929

30-
Configure the threaded runtime from the app delegate **before any secondary
31-
runtime is created**:
30+
Expose the `ThreadedRuntime` class to Swift through your bridging header
3231

33-
```swift title="AppDelegate.swift"
34-
import NativeComposeThreadedRuntime
3532

33+
```objc title="MyApp-Bridging-Header.h"
34+
#import <NativeComposeThreadedRuntime/ThreadedRuntime.h>
35+
```
36+
37+
Then configure the threaded runtime from the app delegate **before any
38+
secondary runtime is created**:
39+
40+
```swift title="AppDelegate.swift"
3641
ThreadedRuntime.configure(
3742
withReactNativeDelegate: delegate,
3843
launchOptions: launchOptions
@@ -121,11 +126,6 @@ const config = {};
121126

122127
module.exports = withThreadedRuntime(
123128
mergeConfig(getDefaultConfig(__dirname), config),
124-
{
125-
roots: ['App.tsx', 'src'],
126-
generatedDir: '.threaded-runtime',
127-
generatedEntry: 'entry.js',
128-
},
129129
);
130130
```
131131

@@ -135,23 +135,23 @@ Add the generated folder to `.gitignore`:
135135
.threaded-runtime/
136136
```
137137

138-
Load the generated entry only inside threaded runtimes:
138+
Load the generated entry at the top of `index.js`, in **every** runtime:
139139

140140
```js title="index.js"
141-
if (global.__THREADED_RUNTIME_ENV__ || global._is_it_a_list_env === true) {
142-
require('./.threaded-runtime/entry');
143-
}
141+
require('./.threaded-runtime/entry');
144142
```
145143

146-
The generated entry registers lazy component loaders and the
147-
`ThreadedRuntimeHost` root used by native.
144+
The generated entry registers lazy component loaders, runtime functions, and
145+
the `ThreadedRuntimeHost` root used by native. It is safe on the main runtime
146+
(component requires stay lazy) and required there so the runtime-function
147+
registry exists for dispatching calls to secondary runtimes.
148148

149149
<Callout type="info" title="Tip: per-runtime startup code">
150150
For startup code that should only run on a specific runtime, create a
151151
root-level file named `index.<runtime>.ts` — for example
152-
`index.background.ts`. The generated entry emits a static conditional
153-
require for it and matches `<runtime>` against
154-
`global.__THREADED_RUNTIME_ENV__.kind` and `.runtimeName`.
152+
`index.background.ts`. The generated entry requires it only when
153+
`global.__THREADED_RUNTIME_ENV__.runtimeName` or `.kind` matches
154+
`<runtime>`.
155155
</Callout>
156156

157157
See [Metro setup](/docs/getting-started/metro-setup) for the full plugin
@@ -189,6 +189,6 @@ Rebuild the app. If you see the text, the runtime, the Metro plugin, and the
189189
native side are all wired up correctly.
190190

191191
<Callout type="success" title="Next">
192-
Read [Rendering components on a background runtime](/docs/threaded-runtime/render-components)
192+
Read [Rendering components on a secondary runtime](/docs/threaded-runtime/render-components)
193193
to learn the full `OnRuntime` / `ThreadedScreen` / `threadedComponent` API.
194194
</Callout>

docs/content/docs/getting-started/ios-setup.mdx

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,34 +27,39 @@ If pod install fails with a Nitro-related error, ensure
2727

2828
<Step>
2929

30+
### Expose ThreadedRuntime to Swift
31+
32+
Add the header to your bridging header (create one if the project doesn't
33+
have it yet). Don't `import NativeComposeThreadedRuntime` in Swift — it pulls
34+
Nitro's C++ umbrella into the Swift importer and can fail the build:
35+
36+
```objc title="MyApp-Bridging-Header.h"
37+
#import <NativeComposeThreadedRuntime/ThreadedRuntime.h>
38+
```
39+
40+
</Step>
41+
42+
<Step>
43+
3044
### Configure the runtime from the app delegate
3145

32-
Configure threading **before any secondary runtime is created** — typically
33-
the very first call in your delegate setup:
46+
Pass the React Native factory delegate to `configure` **before any secondary
47+
runtime is created** — right after the delegate is set up, before
48+
`startReactNative`:
3449

3550
```swift title="AppDelegate.swift"
36-
import NativeComposeThreadedRuntime
37-
38-
@main
39-
class AppDelegate: RCTAppDelegate {
40-
override func application(
41-
_ application: UIApplication,
42-
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
43-
) -> Bool {
44-
ThreadedRuntime.configure(
45-
withReactNativeDelegate: self,
46-
launchOptions: launchOptions
47-
)
48-
49-
// ... your existing setup
50-
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
51-
}
52-
}
51+
let delegate = ReactNativeDelegate()
52+
let factory = RCTReactNativeFactory(delegate: delegate)
53+
delegate.dependencyProvider = RCTAppDependencyProvider()
54+
55+
// Add this line:
56+
ThreadedRuntime.configure(withReactNativeDelegate: delegate, launchOptions: launchOptions)
5357
```
5458

5559
<Callout type="warn" title="Order matters">
56-
If you call `prewarmRuntime` before `configure`, the runtime cannot find the
57-
React Native delegate and the prewarm will fail silently.
60+
Creating a threaded runtime (including `prewarmRuntime`) before `configure`
61+
crashes with `ThreadedRuntime.configureWithReactNativeDelegate must be
62+
called before creating iOS threaded runtimes.`
5863
</Callout>
5964

6065
</Step>
@@ -106,7 +111,6 @@ nativecompose::threadedruntime::prewarmRuntime(
106111
</Callout>
107112

108113
<Callout type="warn" title="Threaded surfaces show up blank">
109-
Confirm that your `index.js` loads `.threaded-runtime/entry` when
110-
`global.__THREADED_RUNTIME_ENV__` is set — without it, the secondary runtime
111-
has no components registered.
114+
Confirm that your `index.js` requires `./.threaded-runtime/entry` — without
115+
it, the secondary runtime has no components registered.
112116
</Callout>

docs/content/docs/getting-started/metro-setup.mdx

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,31 @@ const config = {};
1717

1818
module.exports = withThreadedRuntime(
1919
mergeConfig(getDefaultConfig(__dirname), config),
20-
{
21-
roots: ['App.tsx', 'src'],
22-
generatedDir: '.threaded-runtime',
23-
generatedEntry: 'entry.js',
24-
},
2520
);
2621
```
2722

2823
## Options
2924

3025
| Option | Default | What it controls |
3126
| --- | --- | --- |
32-
| `roots` | `['App.tsx', 'src']` | Files and folders Metro scans for `OnRuntime`, `threadedComponent`, `runtimeFunction`, and directive functions. |
33-
| `generatedDir` | `.threaded-runtime` | Where the generated entry and the registration tables are written. Add this folder to `.gitignore`. |
27+
| `roots` | `['App.tsx', 'src']` | Files and folders scanned for `OnRuntime`, `threadedComponent`, `runtimeFunction`, and directive functions. |
28+
| `generatedDir` | `.threaded-runtime` | Where the generated entry is written. Add this folder to `.gitignore`. |
3429
| `generatedEntry` | `entry.js` | Filename of the generated entry inside `generatedDir`. |
30+
| `projectRoot` | Metro's `projectRoot` | Base directory for scanning and for generated ids. |
3531

3632
## Loading the generated entry
3733

3834
The generated entry registers component loaders, runtime functions, and the
39-
`ThreadedRuntimeHost` root. It must be loaded **only inside secondary
40-
runtimes**, never on the main runtime:
35+
`ThreadedRuntimeHost` root. Require it at the top of `index.js` so it runs in
36+
**every** runtime:
4137

4238
```js title="index.js"
43-
if (global.__THREADED_RUNTIME_ENV__ || global._is_it_a_list_env === true) {
44-
require('./.threaded-runtime/entry');
45-
}
39+
require('./.threaded-runtime/entry');
4640
```
4741

48-
The two globals exist for backwards compatibility — both indicate the bundle
49-
is being evaluated in a secondary runtime. New code should rely on
50-
`__THREADED_RUNTIME_ENV__`.
42+
Component registrations are lazy (`() => require(...)`), so this adds nothing
43+
to main-runtime startup. Loading it on main is required for the JSI function
44+
dispatch mechanism, which enables calling functions on secondary runtimes.
5145

5246
## .gitignore
5347

@@ -66,12 +60,12 @@ root-level file with the runtime's name:
6660
index.background.ts
6761
```
6862

69-
The Metro plugin discovers files matching `index.<runtime>.ts(x)` in the
70-
project root and emits a static conditional require. When native starts a
71-
runtime named `background`, the generated entry loads `index.background.ts`
72-
inside that runtime only.
63+
The Metro plugin discovers files matching `index.<runtime>.ts` in the project
64+
root and emits a static conditional require. The generated entry loads
65+
`index.background.ts` only inside runtimes whose **name or kind** is
66+
`background`.
7367

74-
```tsx title="index.background.ts"
68+
```ts title="index.background.ts"
7569
import { registerThreadedHeadlessTask } from '@react-native-runtimes/core';
7670
import { business } from './src/businessStore';
7771

@@ -80,6 +74,7 @@ registerThreadedHeadlessTask<{ reason: string }>(
8074
async ({ payload }) => {
8175
await business.hydrate();
8276
await business.update(state => ({
77+
...state,
8378
lastRefreshReason: payload.reason,
8479
refreshCount: state.refreshCount + 1,
8580
}));
@@ -108,11 +103,6 @@ index.sync-engine.ts
108103

109104
## Troubleshooting
110105

111-
<Callout type="warn" title="Metro picks up the generated entry on the main runtime">
112-
Double-check the `if (global.__THREADED_RUNTIME_ENV__ ...)` guard in your
113-
`index.js`. The generated entry is large and should never load on main.
114-
</Callout>
115-
116106
<Callout type="warn" title="A threaded component isn't being registered">
117107
Add its file to `roots` or place it under one of the existing roots. Files
118108
outside the configured roots are invisible to the plugin.

0 commit comments

Comments
 (0)