From 6d11a7570b5860390567e93bfd9bd89eebab2d94 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 14 Apr 2026 20:51:55 +0200 Subject: [PATCH] Add simulation clock and playing state management in App.tsx --- src/app/App.tsx | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/app/App.tsx b/src/app/App.tsx index f257c4fc..8de3332c 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -2944,6 +2944,94 @@ export function App({ store, initialStatusMessage }: AppProps) { setProjectTimeDuskDurationHoursDraft(String(projectTime.duskDurationHours)); }, [editorState.projectDocument.time]); + useEffect(() => { + setEditorSimulationClockOverride((currentClock) => { + if (currentClock === null) { + return null; + } + + const reconfiguredClock = reconfigureRuntimeClockState( + currentClock, + editorState.projectDocument.time + ); + + return areRuntimeClockStatesEqual(currentClock, reconfiguredClock) + ? currentClock + : reconfiguredClock; + }); + }, [editorState.projectDocument.time.dayLengthMinutes]); + + useEffect(() => { + if (editorState.toolMode !== "play") { + return; + } + + setEditorSimulationPlaying(false); + }, [editorState.toolMode]); + + useEffect(() => { + if (editorState.toolMode === "play" || !editorSimulationPlaying) { + return; + } + + let animationFrame = 0; + let previousFrameTime: number | null = null; + + const tick = (timestamp: number) => { + if (previousFrameTime !== null) { + const dtSeconds = Math.min(0.25, (timestamp - previousFrameTime) / 1000); + setEditorSimulationClockOverride((currentClock) => + advanceRuntimeClockState( + currentClock ?? createRuntimeClockState(editorState.projectDocument.time), + dtSeconds + ) + ); + } + + previousFrameTime = timestamp; + animationFrame = window.requestAnimationFrame(tick); + }; + + animationFrame = window.requestAnimationFrame(tick); + + return () => { + if (animationFrame !== 0) { + window.cancelAnimationFrame(animationFrame); + } + }; + }, [ + editorSimulationPlaying, + editorState.projectDocument.time, + editorState.toolMode + ]); + + useEffect(() => { + if (editorState.toolMode === "play") { + return; + } + + try { + const nextEditorSimulationScene = applyResolvedControlStateToRuntimeScene( + buildRuntimeSceneFromDocument(editorState.document, { + loadedModelAssets, + runtimeClock: editorSimulationClock + }) + ); + setEditorSimulationScene(nextEditorSimulationScene); + setEditorSimulationMessage(null); + } catch (error) { + setEditorSimulationScene(null); + setEditorSimulationMessage(getErrorMessage(error)); + } + }, [ + editorSimulationClock.dayCount, + editorSimulationClock.dayLengthMinutes, + editorSimulationClock.timeOfDayHours, + editorState.document, + editorState.toolMode, + loadedModelAssets + ]); + useEffect(() => { if (selectedScheduleRoutineId === null) { return;