Skip to content

fix(react): StrictMode-safe Sky disposal — defer one cancelable tick - #6

Open
DennisSmolek wants to merge 1 commit into
mainfrom
fix/react-strictmode-dispose
Open

fix(react): StrictMode-safe Sky disposal — defer one cancelable tick#6
DennisSmolek wants to merge 1 commit into
mainfrom
fix/react-strictmode-dispose

Conversation

@DennisSmolek

Copy link
Copy Markdown
Member

The bug

The react binding's attach effect ran sky.detach(); sky.dispose() synchronously in its cleanup. React StrictMode runs every effect as mount → cleanup → mount in dev, so the cleanup disposed the useMemo'd instance's internals — sky-scene dome mesh, LUT/cube render targets — and then re-attached the husk.

Symptom (zero console errors): the sky renders its construction-time bake forever. Every live setter (setTimeOfDay, setLatitude, setTurbidity, mirror…) updates instance state and clears the dirty flags — but the cube bake renders an empty sky scene into a lazily recreated texture that no long-lived pipeline samples, while the screen keeps showing the original bake through a cached GPU binding. Uniform-backed knobs (setExposure, setHazeStrength) keep working, which disguises it as "some sliders are dead".

Diagnosed in a Next.js (StrictMode) R3F v10 WebGPU app by instrumenting renderer.render during a manual baker.update(): all six cube-face renders executed against a skyScene with zero children. The vanilla demos can never hit this (no React), and production builds without StrictMode don't either — it's a dev-only trap, but dev is where everyone lives.

The fix

Disposal is deferred one tick on a cancelable timer (scheduleDispose / cancelScheduledDispose, one pending timer per instance). The StrictMode remount re-runs the effect synchronously after cleanup, which cancels the pending disposal; a real unmount or a construction-time instance swap lets it fire. No API change.

Also adds a CLAUDE.md gotcha entry so the pattern is mirrored for any future memoized GPU-resource owner in the react bindings.

Verified

In the consuming app (paris-mini-site hero demo): before — TOD slider inert at any value; after — TOD 20.5 → 12 flips dusk to full daylight instantly, and latitude/dayOfYear/turbidity/mirror all re-bake live.

🤖 Generated with Claude Code

StrictMode's dev mount→cleanup→mount cycle ran the attach effect's
synchronous `sky.dispose()` against the useMemo'd instance, gutting its
internals (dome mesh, LUT/cube targets) before re-attaching the husk.
The sky then rendered its construction-time bake forever: every live
setter re-baked an empty sky scene into a recreated texture nothing
samples, with zero errors — while uniform-backed knobs (exposure, haze
strength) kept working. Disposal is now scheduled on a 0ms timer and
canceled if the same instance re-attaches within the tick; real
unmounts and instance swaps still dispose.

Diagnosed in the paris-mini-site hero demo by instrumenting
renderer.render during a manual bake: all six cube-face renders
executed against a skyScene with zero children.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant