narraleaf-react 0.45.0 → 0.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -436,6 +436,34 @@ export declare class GameState {
|
|
|
436
436
|
isStateMounted(key: Values<ExposedKeys>): boolean;
|
|
437
437
|
getExposedState<T extends ExposedStateType>(key: ExposedKeys[T]): ExposedState[T] | null;
|
|
438
438
|
getExposedStateForce<T extends ExposedStateType>(key: ExposedKeys[T]): ExposedState[T];
|
|
439
|
+
/**
|
|
440
|
+
* Wait for a component to expose its state, then hand it over - **never in the caller's own
|
|
441
|
+
* task**, and always the state that is on screen when the hand-over happens.
|
|
442
|
+
*
|
|
443
|
+
* The waiting branch used to call back straight out of `mountState`, which runs inside the
|
|
444
|
+
* component's mount effect, and that is what made starting a scene one unbroken synchronous
|
|
445
|
+
* chain: the action flushes the stage, React commits, the new displayable mounts, this fires,
|
|
446
|
+
* the action resolves, `next()` runs the following action, which flushes the stage again - one
|
|
447
|
+
* commit per element, all of it without ever returning to the event loop.
|
|
448
|
+
*
|
|
449
|
+
* React counts those: a commit that leaves synchronous work pending on the same root bumps
|
|
450
|
+
* `nestedUpdateCount`, and at fifty it **throws** `Maximum update depth exceeded` - in
|
|
451
|
+
* production as well as in development. So a scene was only ever allowed about twenty-five
|
|
452
|
+
* displayables before the whole player died at its error boundary, with the game silently
|
|
453
|
+
* never starting. Measured 2026-09-04: a scene of 23 images ran, one of 26 did not.
|
|
454
|
+
*
|
|
455
|
+
* Two things follow from handing over a turn later, and both are load-bearing:
|
|
456
|
+
*
|
|
457
|
+
* - The turn is taken through {@link yieldToBrowser} rather than a timer. A scene of 44 images
|
|
458
|
+
* took **45 seconds** to start through `setTimeout(0)` when its window was not the foreground
|
|
459
|
+
* one, because a background window's timers are throttled to about one a second; the boot
|
|
460
|
+
* preload gave up first.
|
|
461
|
+
* - The state is **re-read at hand-over** instead of being taken from the event. React mounts an
|
|
462
|
+
* effect, tears it down and mounts it again under `StrictMode`, so what announced itself one
|
|
463
|
+
* turn ago can already be the throwaway mount's state, and calling `initDisplayable` on that
|
|
464
|
+
* one leaves the action waiting for ever. A hand-over that finds nothing mounted stays
|
|
465
|
+
* subscribed and waits for the mount that follows.
|
|
466
|
+
*/
|
|
439
467
|
getExposedStateAsync<T extends ExposedStateType>(key: ExposedKeys[T], onExpose: (state: ExposedState[T]) => void): LiveGameEventToken;
|
|
440
468
|
/**
|
|
441
469
|
* Dispose of the game state
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand a callback to the next turn of the event loop, without a timer.
|
|
3
|
+
*
|
|
4
|
+
* Exists because two different things need to be true at once, and `setTimeout(fn, 0)` only manages
|
|
5
|
+
* one of them:
|
|
6
|
+
*
|
|
7
|
+
* - It has to be a **task**, not a microtask. React counts commits that leave synchronous work
|
|
8
|
+
* pending on the same root, and throws `Maximum update depth exceeded` at fifty of them - so a
|
|
9
|
+
* chain of work driven out of mount effects has to return to the event loop to reset that count.
|
|
10
|
+
* Microtasks drain before the loop turns and would not.
|
|
11
|
+
* - It must not be a **timer**. A window that is not the foreground one has its timers throttled to
|
|
12
|
+
* roughly one a second by the browser, so a chain of fifty `setTimeout(0)` hand-overs takes about
|
|
13
|
+
* fifty seconds in a background window and milliseconds in a focused one. Measured 2026-09-04:
|
|
14
|
+
* starting a scene of 44 images this way timed the boot preload out at 45s.
|
|
15
|
+
*
|
|
16
|
+
* A `MessageChannel` message is a task and is not a timer, which is the same reason React's own
|
|
17
|
+
* scheduler reaches for one. The `setTimeout` fallback is for hosts that have no `MessageChannel`
|
|
18
|
+
* (a plain Node test run, say), where throttling is not a thing either.
|
|
19
|
+
*
|
|
20
|
+
* The returned function cancels the hand-over if it has not happened yet.
|
|
21
|
+
*/
|
|
22
|
+
export declare function yieldToBrowser(callback: () => void): () => void;
|