Skip to content

Commit 58339c6

Browse files
wass08claude
andauthored
fix(plugin-trees): consume dirty marks so scene-ready fires for plant scenes (#476)
Instanced plant kinds (trees/grass/flowers) never cleared their dirty marks: FloorElevationSystem deliberately leaves the mark for kinds with a def.system, expecting that system to clear it after its own work, but InstancedKindSystem never participated in the dirty protocol. The marks lived forever, hasPendingSceneBuildWork() never went false, and the Viewer's scene-ready signal stalled at SCENE_READY_MAX_WAIT_FRAMES on every plant-containing scene — measured on the headless bake worker as ~190s (180 frames x ~1s SwiftShader frames) of pure cap-wait per bake. Clear the marks in a priority-2 useFrame pass: after the priority-1 floor-elevation lift in the same frame, and only for nodes whose proxy is registered (instances rebuild synchronously from the store, so a rendered node is already built). Validated by baking Wawa House locally: settle 22.5s -> 11.6s (the remainder is genuine asset loading), exported GLB byte-identical with and without the fix. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent a477c4a commit 58339c6

1 file changed

Lines changed: 21 additions & 0 deletions

File tree

packages/plugin-trees/src/instanced.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
useScene,
1010
} from '@pascal-app/core'
1111
import { useNodeEvents, useViewer } from '@pascal-app/viewer'
12+
import { useFrame } from '@react-three/fiber'
1213
import { useLayoutEffect, useMemo, useRef } from 'react'
1314
import { type BufferGeometry, type InstancedMesh, type Material, Matrix4, Object3D } from 'three'
1415
import { toStaticMaterial } from './wind-node'
@@ -73,6 +74,26 @@ export function InstancedKindSystem<N extends Placeable>({
7374
(n) => (n.type as string) === kind && !active.has(n.id as string),
7475
) as unknown as N[]
7576
}, [scene, kind, activeKey])
77+
78+
// Consume the dirty marks for this kind. Instances rebuild synchronously
79+
// from the store (the memos above), so a rendered node is already "built" —
80+
// but `FloorElevationSystem` deliberately leaves the mark for kinds with a
81+
// `def.system`, expecting that system to clear it. Without this pass the
82+
// marks live forever: `hasPendingSceneBuildWork` never goes false, so the
83+
// scene-ready signal (and every headless bake) stalls at its frame cap.
84+
// Priority 2 = after the priority-1 floor-elevation lift in the same frame;
85+
// clearing only registered nodes leaves unmounted proxies for a later frame.
86+
useFrame(() => {
87+
const { dirtyNodes, nodes: sceneNodes, clearDirty } = useScene.getState()
88+
if (dirtyNodes.size === 0) return
89+
for (const id of dirtyNodes) {
90+
const node = sceneNodes[id]
91+
if (!node || (node.type as string) !== kind) continue
92+
if (!sceneRegistry.nodes.has(id)) continue
93+
clearDirty(id)
94+
}
95+
}, 2)
96+
7697
return <InstancedNodes getVariant={getVariant} nodes={nodes} variantKeyOf={variantKeyOf} />
7798
}
7899

0 commit comments

Comments
 (0)