NarraLeaf

Preloading

The behaviour described on this page is available since 0.17.0.

The player fetches and decodes a scene's assets before that scene paints. Since 0.17.0 that work starts as soon as the story is loaded, rather than when the game is entered, and it runs in two tiers, so the first frame waits only on the assets it needs.

onPreloadComplete moved. It now fires before the game is entered, while a menu may still be on screen. For the "the game has content on screen" signal, use onFirstSceneReady. See Which signal to wait on.

When preloading starts

Player registers story.entryScene as the preloading scene as soon as the story is loaded, so the assets are fetched behind whatever is already on screen. Nothing is mounted and no action runs.

The automatic registration applies only when there is neither a preloading scene nor a mounted scene, so a host that calls preloadScene itself keeps its own choice.

Critical and look-ahead tiers

A scene's asset list is transitive: it carries the asset set of every scene reachable from it. The pass runs in two tiers so the first frame does not wait for all of them.

TierWhat it holdsHow it runs
CriticalWhat the scene about to paint registers directly: its own backgrounds and images, plus the immediate background of any scene it jumps toUnpaced, and the only tier that gates event:preloaded.complete, and therefore the first painted frame
Look-aheadThe full asset set of every scene reachable from here, minus anything already in the critical tierRuns after the critical tier, paced by preloadDelay. Nothing waits for it

preloadDelay does not charge the first frame: it paces speculative work only. The cache-eviction pass runs once over the union of both tiers, and is skipped for a superseded pass, so switching scenes mid-preload keeps the images the current scene just cached.

Games running with preloadAllImages: false keep the predict-by-action behaviour, and none of this applies to them.

Decoded bitmaps

The critical tier holds its decoded elements until the source leaves the cache, so the first visible frame does not decode again.

The look-ahead tier does not hold them. A full-resolution bitmap costs width × height × 4 bytes.

Sound preloading

The critical tier also warms the current scene's sounds, through preload on the audio manager:

<Player
    story={story}
    onReady={({ gameState }) => {
        // fetch and decode a source into the audio cache without playing it
        void gameState.audioManager.preload(bgm);
    }}
/>
  • preload(sound: Sound): Promise<void> - Resolves when the source is in the audio cache. A source that fails to load is logged as a warning and loads on first play instead, so the promise never rejects.

Never gate anything on this promise. The audio context stays locked until the browser's autoplay policy is satisfied by a user gesture, so an audio warm-up can sit pending on a page nobody has touched yet. The engine's own call is fire-and-forget.

Only the current scene's sounds are warmed automatically. A look-ahead scene's audio is left to that scene's own pass.

Which signal to wait on

SignalFires when
onReadyThe Player instance is initialized. This is where newGame() belongs. It is not a preload or a first-render guarantee.
onPreloadCompleteThe initial preload pass (the critical tier) has finished. Since 0.17.0 this is before the game is entered: a menu may still be on screen and no scene is mounted.
onFirstSceneReadyThe preload pass has finished, the first scene component has mounted, and the browser has had a frame to render it. This is the "there is content on screen" signal.

The latter two exist as a Player prop and as a Game method, and each fires at most once per game lifecycle:

<Player
    story={story}
    onReady={({ liveGame }) => liveGame.newGame()}
    onPreloadComplete={() => setLoadingBarDone(true)}   // menu may still be up
    onFirstSceneReady={() => setLoadingOverlay(false)}  // the scene is painted
/>
// Same points, for code that is not the Player element.
await game.whenPreloadComplete();
await game.whenFirstSceneReady();

game.isPreloadComplete();
game.isFirstSceneReady();

See Game for the full listener set: on*, once*, when* and is* for both points.

Upgrading from 0.16 and earlier

This is a behavioural break, not a new API. onPreloadComplete, oncePreloadComplete, whenPreloadComplete() and event:preloaded.complete all fire before the game is entered, rather than after newGame() has mounted a scene. The names and signatures did not change, so nothing fails to compile, and a host that hid its loading screen there now uncovers an unmounted stage.

  • Used to gate a loading step (a progress bar, a "Start" button that should not be pressable while assets are still coming down): no change needed.
  • Used to mean "the game has content on screen": switch to onFirstSceneReady / whenFirstSceneReady(). Those are unchanged and still require a mounted scene.

The ctx.scene a preloadComplete listener receives may now be the preloading scene rather than a mounted one.

onPreloadedReady is deprecated in favour of onPreloadComplete.

Preloading a scene yourself

To warm a scene other than the entry scene, name it. A save-slot screen that knows which scene the player is about to resume into can call:

gameState.preloadScene(scene);   // or a Story, which uses its entry scene

Calling this before the Player registers the entry scene prevents the automatic registration.

Configuration

These live on GameConfig:

  • preloadAllImages (default true) - Run the two-tier scene pass. With false, the player predicts the next maxPreloadActions actions and preloads their sources instead.
  • preloadConcurrency (default 5) - How many images are fetched at once, in both tiers.
  • preloadDelay (default 100) - Pacing between look-ahead batches. The critical tier is not paced.
  • waitForPreload (default true) - Hold the player's reveal until the critical tier is done.
  • forceClearCache (default false) - Clear the image cache when the scene changes.
  • maxPreloadActions (default 10) - How many actions ahead the preloadAllImages: false path predicts.

On this page