# Warmup

Discover a scene, prepare its GPU resources, and restore every temporary mutation before play.



Warmup moves resource discovery, uploads, and representative shader compilation
into an explicit startup phase. You provide the renderer operations through a
small driver. The application keeps its renderer, assets, loop, and loading UI.

<DemoPreview name="Warmup" />

## Usage [#usage]

```ts
import { Warmup } from '@joycostudio/xyz/three'

// Initialize your existing WebGPURenderer before preparing the scene.
await renderer.init()

// Connect your renderer, scene, camera, and animation callback.
const warmup = new Warmup({
  pause: () => renderer.setAnimationLoop(null),
  resume: () => renderer.setAnimationLoop(animate),
  initTexture: (texture) => renderer.initTexture(texture),
  compile: async (scene, camera) => {
    await renderer.compileAsync(scene, camera)
  },
  render: () => renderer.render(scene, camera),
})

await warmup.start(scene, camera) // Prepare resources, then resume animation.
warmup.audit(scene) // Check for resources added after warmup.

// When the scene owner is removed:
warmup.dispose()
```

## Warmup lifecycle [#warmup-lifecycle]

Load your assets and initialize the renderer before calling `start(scene, camera)`. Warmup prepares the resources already available in your scene; it does not load assets or create a renderer. The demo's renderer-initialization steps happen before this lifecycle begins.

```flow title="Warmup lifecycle"
flow TD
    assets["Assets loaded and renderer ready"] --> pause["Pause driver"]
    pause --> hooks["Await hooks in order"]
    hooks --> discovery["Discover and expose one scene"]
    discovery --> textures["Initialize textures"]
    textures --> compile["Compile representative programs"]
    compile --> first["Draw exposed state"]
    first --> restore["Restore scene and reverse hook cleanup"]
    restore --> second["Draw restored state"]
    second --> resume["Resume driver"]
```

### Preparation and playback [#preparation-and-playback]

1. **Pause animation.** `pause()` stops your normal loop so application state stays still while Warmup temporarily changes the scene.
2. **Expose resources.** Registered `Warmable.warmup()` hooks run in registration order and may return promises. Each hook settles before the next starts, so custom asynchronous work finishes while the driver remains paused. Warmup then inventories the scene and temporarily exposes hidden resources, saving the original visibility, culling, layer, and draw state for restoration.
3. **Initialize textures.** `initTexture(texture)` runs for each discovered texture. Warmup waits for all returned promises before continuing; a procedural scene without textures skips these calls.
4. **Compile shaders.** `compile(scene, camera)` prepares the exposed scene's shaders and pipelines. Warmup awaits it before drawing. In this demo, that includes both the background and cube shaders.
5. **Render warmup frame 1.** `render()` draws the exposed scene to exercise uploads and draw-time setup that compilation alone may not cover. These resources are recorded for later auditing.
6. **Restore the scene.** Temporary scene changes are undone, then cleanup functions returned by the registered hooks run in reverse registration order. The scene is back in its intended playback state before the next draw.
7. **Render warmup frame 2.** `render()` draws the restored scene, replacing the temporary preparation view.
8. **Resume animation.** `resume()` hands control back to your normal loop. After a successful run, the `start()` promise resolves and `audit(scene)` can report resources introduced later.

### Completion and reuse [#completion-and-reuse]

One `Warmup` instance runs once. Repeated calls to `start()` return the same promise, including after a failed run; they do not restart preparation. Register exceptional entities with `.add()` before starting. After success, `.audit()` only warns about new resources—it does not warm them automatically.

### Failure and disposal [#failure-and-disposal]

If preparation fails after pausing, Warmup restores the scene and runs the registered cleanups, attempts the restored-scene draw, and calls `resume()` before rejecting `start()`. Handle that rejection in the scene owner. If `pause()` itself throws, preparation never starts and `resume()` is not called.

Calling `.dispose()` prevents later hooks and preparation stages from starting. If a hook is pending, Warmup waits for it to settle, then runs all returned cleanups in reverse order (including a cleanup returned after disposal) and calls `resume()` once. Otherwise, temporary state is restored immediately and the paused driver resumes once. Pending uploads or compilation may still finish, but subsequent stages are skipped. Your `resume()` callback should check whether the scene owner has been removed before restarting animation.

Disposal is idempotent and does not free your renderer, materials, or geometry. The scene owner remains responsible for stopping animation and releasing those resources after pending GPU preparation has settled.

## API [#api]

### new Warmup [#new-warmup]

```ts
new Warmup(driver: WarmupDriver)
```

Creates one renderer-scoped warmup owner. Your application owns the renderer and animation loop; all callbacks below are required to connect preparation to that setup.

| Argument             | Description                                                                                                                                                                                                            |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `driver.pause`       | `() => void`. Stops normal animation before Warmup temporarily exposes hidden scene resources, keeping application state still during preparation.                                                                     |
| `driver.resume`      | `() => void`. Returns control to your animation loop after restoration, including after failure or disposal while paused. Respect your application's visibility, reduced-motion, and teardown state before restarting. |
| `driver.initTexture` | `(texture: THREE.Texture) => void \| Promise<void>`. Receives each discovered texture. Connect to `renderer.initTexture(texture)` to allocate and upload textures ahead of their first draw.                           |
| `driver.compile`     | `(scene: THREE.Scene, camera: THREE.Camera) => Promise<void>`. Receives the exposed scene and camera. Connect to `renderer.compileAsync(scene, camera)` and await completion to prepare shaders and pipelines.         |
| `driver.render`      | `() => void`. Draws your existing scene and camera without advancing animation, exercising uploads and draw-time setup. Also redraws the scene after restoration.                                                      |

```ts
// After renderer.init(), connect your existing scene, camera, and loop.
const warmup = new Warmup({
  pause: () => renderer.setAnimationLoop(null),
  resume: () => renderer.setAnimationLoop(animate),
  initTexture: (texture) => renderer.initTexture(texture),
  compile: async (scene, camera) => {
    await renderer.compileAsync(scene, camera)
  },
  render: () => renderer.render(scene, camera),
})
```

### add [#add]

```ts
warmup.add<T extends Warmable>(entity: T): T
```

Registers an exceptional entity and returns it unchanged. Call before `start()`. Adding to a disposed instance throws.

| Argument | Description                                                                                                                                                                                                                                      |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `entity` | An object with `warmup(): void \| WarmupCleanup \| Promise<void \| WarmupCleanup>`. Hooks may expose representative state or prepare external resources. Each is awaited in order; returned cleanup functions run in reverse registration order. |

```ts
// Expose a material variant that is not normally assigned to the mesh.
warmup.add({
  warmup() {
    const original = mesh.material
    mesh.material = alternateMaterial
    return () => {
      mesh.material = original
    }
  },
})
```

#### Asynchronous hooks [#asynchronous-hooks]

A hook can await custom preparation that scene discovery cannot perform. For
example, compile a separate scene used only by a later transition:

```ts
warmup.add({
  async warmup() {
    await renderer.compileAsync(transitionScene, transitionCamera)
  },
})
```

Hooks finish in registration order before discovery begins. An async hook may
also return a cleanup function; Warmup runs returned cleanups in reverse order
after restoring the scene. If a hook changes state and then throws before
returning a cleanup, it must undo that partial change itself.

Calling `dispose()` does not interrupt an asynchronous hook's own operation.
Use that operation's cancellation API when available, and await the `start()`
promise before releasing resources it still uses.

### start [#start]

```ts
warmup.start(scene: THREE.Scene, camera: THREE.Camera): Promise<void>
```

Runs preparation once: expose, upload, compile, render, restore, and resume. Initialize the renderer and load assets first. Repeated calls return the same promise, including after failure; calls after disposal resolve without running preparation.

| Argument | Description                                                                                                                           |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `scene`  | Scene containing the resources to prepare. Temporary visibility, culling, layer, and draw-state changes are restored before playback. |
| `camera` | Camera used for compilation. Its layers are temporarily enabled and then restored.                                                    |

```ts
await warmup.start(scene, camera)
// Temporary scene changes are restored and animation has resumed.
```

### audit [#audit]

```ts
warmup.audit(scene: THREE.Scene): void
```

After successful preparation, warns once for each newly discovered geometry, material, or texture. Does nothing before success or after disposal, and does not upload or prepare new resources.

| Argument | Description                                             |
| -------- | ------------------------------------------------------- |
| `scene`  | Scene to inspect for resources introduced after warmup. |

```ts
await warmup.start(scene, camera)
scene.add(lateMesh)
warmup.audit(scene) // Warns about resources that were not prepared.
```

### dispose [#dispose]

```ts
warmup.dispose(): void
```

Cancels subsequent work and clears registered entities. Pending hooks settle before cleanup and resume; otherwise restoration is immediate. Resumes the driver once if paused. Takes no arguments and is idempotent. Await the in-flight `start()` promise before releasing renderer resources used by asynchronous work.

## Representative state and first-use limits [#representative-state-and-first-use-limits]

Discovery inventories the scene supplied to `start()`: geometry and materials,
texture references in material slots, uniforms and TSL object graphs, and scene
background or environment textures. Render-target textures belong to their render
passes. Discovery does not enumerate every shader variant or effect-graph branch.

Use hooks to expose exceptional state before discovery, returning a cleanup that
restores exactly what changed. For states that cannot be exposed together, a hook
can perform representative draws and restore its state with `try/finally`. Keep
one startup owner in control of a renderer's pause/resume sequence.

Preparation draws can advance temporal effects and Three-managed time nodes.
Reset effect history through the effect's own API if needed. Keep a loading cover
active until `start()` resolves when temporarily exposed content must stay hidden;
Warmup does not create a loading UI.

New shader variants, larger buffers, or later assets can still require first-use
work. `audit()` diagnoses newly discovered resources after a successful run; it
does not perform another warmup.

## Low-level scene discovery [#low-level-scene-discovery]

`discoverScene(scene, camera)` is also exported for specialized startup drivers.
It returns `{ geometries, materials, textures, restore }`. The first three are
maps recording the discovered resource and its owner; `restore()` reverses the
temporary exposure and is idempotent. Discovery itself performs no uploads,
compilation, or rendering.

```ts
import { discoverScene } from '@joycostudio/xyz/three'

const discovery = discoverScene(scene, camera)
try {
  await Promise.all([...discovery.textures.keys()].map((texture) => renderer.initTexture(texture)))
  await renderer.compileAsync(scene, camera)
  // Perform your representative draw with the appropriate renderer/target.
} finally {
  discovery.restore()
}
```

On this path, the application owns pausing, target selection, the representative
draw, and resuming. Prefer `Warmup` unless you need to integrate discovery into an
existing preparation lifecycle. Never leave the exposed state active across
normal application animation.
