Skip to content

fix(win32): game-loop pacing so the event loop isn't starved#79

Open
KoalaHao wants to merge 1 commit into
Norbert515:mainfrom
marsup-space:fix/windows-game-loop-pacing
Open

fix(win32): game-loop pacing so the event loop isn't starved#79
KoalaHao wants to merge 1 commit into
Norbert515:mainfrom
marsup-space:fix/windows-game-loop-pacing

Conversation

@KoalaHao
Copy link
Copy Markdown

@KoalaHao KoalaHao commented Jun 5, 2026

ReadConsoleInputW is a blocking native call. In the old code, the Dart event loop had no chance to run between input events, so periodic redraws (cursor blink, animations, status ticks) never fired. The framework appeared frozen on Windows until an event arrived.

Switched to game-loop pacing: capture a 16ms deadline at the start of each pass, drain the console queue, then sleep only for the remaining time. A slow pass (big redraw, burst of events) skips the sleep entirely and runs the next pass immediately, so we never double-charge a 16ms wait on top of work and the input loop stays responsive regardless of workload.

GetNumberOfConsoleInputEvents is a non-blocking peek, used to size the drain loop. Subsequent ReadConsoleInputW calls return immediately when events are pending, so the isolate is never blocked waiting for input. The drain loop also means a burst of events (fast typing, fast mouse) is processed in a single tick instead of throttled to one record per poll.

16ms ~= 60Hz, matching the Linux path's responsiveness. Idle CPU is ~0% because Future.delayed is timer-backed, not busy-polled.

ReadConsoleInputW is a blocking native call. In the old code, the
Dart event loop had no chance to run between input events, so
periodic redraws (cursor blink, animations, status ticks) never
fired. The framework appeared frozen on Windows until an event
arrived.

Switched to game-loop pacing: capture a 16ms deadline at the start
of each pass, drain the console queue, then sleep only for the
remaining time. A slow pass (big redraw, burst of events) skips
the sleep entirely and runs the next pass immediately, so we
never double-charge a 16ms wait on top of work and the input
loop stays responsive regardless of workload.

GetNumberOfConsoleInputEvents is a non-blocking peek, used to
size the drain loop. Subsequent ReadConsoleInputW calls return
immediately when events are pending, so the isolate is never
blocked waiting for input. The drain loop also means a burst of
events (fast typing, fast mouse) is processed in a single tick
instead of throttled to one record per poll.

16ms ~= 60Hz, matching the Linux path's responsiveness. Idle
CPU is ~0% because Future.delayed is timer-backed, not
busy-polled.
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