Skip to content

fix(sdl): make frameDuration() idempotent (#437 follow-up) - #444

Merged
apotema merged 1 commit into
mainfrom
fix/sdl-framededuration-idempotent
Jun 30, 2026
Merged

apotema merged 1 commit into
mainfrom
fix/sdl-framededuration-idempotent

Conversation

@apotema

@apotema apotema commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Addresses the gemini-high/codex-P2 cluster on #437: frameDuration() mutated its baseline on every call → a 2nd query/frame returned ~0. Now beginDrawing computes the dt once per frame and caches it; frameDuration() returns the cache (idempotent). Latent today (generated templates use a fixed dt) but correct for the extracted backend's manifest template. backends/sdl tests green.

Summary by CodeRabbit

  • Bug Fixes
    • Improved frame timing consistency so repeated timing reads within the same frame now return the same value.
    • Reset frame timing when reopening a window, helping avoid stale timing behavior after a close-and-reopen cycle.

…nDrawing)

The conformance's frameDuration() advanced its baseline on every call, so a
second query within a frame returned ~0 (flagged by gemini/codex on #437).
Compute the dt once per frame in beginDrawing, cache it, and return the cache —
idempotent. Not exercised by current templates (fixed dt), but correct for when
the extracted backend's manifest template wires window.frameDuration() as dt.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The SDL window backend now computes frame delta time once per frame inside beginDrawing, caching it in a new frame_dur_seconds field. frameDuration() returns this cached value instead of recomputing it from a performance counter each call. initWindow resets the cache to 1/60 on (re)open.

Changes

Frame duration caching

Layer / File(s) Summary
Cache field and reset on init
backends/sdl/src/window.zig
Adds a frame_dur_seconds field initialized to 1/60 and resets it in initWindow on window (re)open.
Per-frame computation and cached return
backends/sdl/src/window.zig
beginDrawing computes frame_dur_seconds once per frame from the performance counter delta against frame_dur_last, with guards for missing frequency/baseline; frameDuration() now returns this cached value instead of recomputing it.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant beginDrawing
  participant frameDuration
  App->>beginDrawing: start frame
  beginDrawing->>beginDrawing: compute dt from perf counter delta
  beginDrawing->>beginDrawing: cache frame_dur_seconds, update frame_dur_last
  App->>frameDuration: query dt
  frameDuration->>App: return cached frame_dur_seconds
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rabbit hops with timer in paw,
No more counting ticks under the floor,
One dt per frame, cached nice and neat,
beginDrawing sets the beat,
frameDuration just echoes back —
Clean and steady, right on track! 🐇⏱️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: making SDL frameDuration() idempotent, and the follow-up note is accurate.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sdl-framededuration-idempotent

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors frame duration tracking in the SDL backend by caching the delta time once per frame in beginDrawing(), making frameDuration() idempotent. Feedback suggests resetting frame_dur_last to 0 in initWindow to prevent the first frame's delta time from being incorrectly overwritten, and adding a guard now >= frame_dur_last to avoid potential integer underflow panics from performance counter drift.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

const now = c.SDL_GetPerformanceCounter();
last_frame_time = now;
frame_dur_last = now; // reset the frameDuration() baseline too
frame_dur_seconds = 1.0 / 60.0; // and its cached value, so a re-open starts clean

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To prevent the first frame's delta time from being overwritten to ~0 in beginDrawing, we should reset frame_dur_last to 0 here. This ensures that the first call to beginDrawing skips the elapsed time calculation and preserves the seeded 1.0 / 60.0 value.

    frame_dur_last = 0; // reset baseline to 0 so the first frame uses the seeded value
    frame_dur_seconds = 1.0 / 60.0; // and its cached value, so a re-open starts clean

Comment on lines +139 to +141
if (freq != 0 and frame_dur_last != 0) {
frame_dur_seconds = @as(f64, @floatFromInt(now - frame_dur_last)) / @as(f64, @floatFromInt(freq));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Add a guard now >= frame_dur_last to prevent potential integer underflow panics in Debug or ReleaseSafe modes if the OS/hardware performance counter ever reports a slightly backward value or drifts.

    if (freq != 0 and frame_dur_last != 0 and now >= frame_dur_last) {
        frame_dur_seconds = @as(f64, @floatFromInt(now - frame_dur_last)) / @as(f64, @floatFromInt(freq));
    }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
backends/sdl/src/window.zig (1)

132-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Per-frame dt computation is correctly idempotent.

Computing the delta once in beginDrawing against frame_dur_last, guarding for the zero-baseline/zero-frequency edge case, and updating the baseline afterward correctly fixes the original bug where a second frameDuration() call within the same frame returned near zero. The separation from last_frame_time (used only for FPS-cap delay in endDrawing, and only updated when target_fps_val > 0) is intentional and avoids coupling the two timers.

Consider adding a regression test asserting two consecutive frameDuration() calls within the same frame return the same value, since this was the exact bug being fixed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backends/sdl/src/window.zig` around lines 132 - 143, The frame delta caching
in beginDrawing and frameDuration is now correct, but the fix should be
protected with a regression test. Add a test around the window timing path in
backends/sdl/src/window.zig that exercises beginDrawing and calls
frameDuration() twice within the same frame, asserting both calls return the
same value; use the existing frame_dur_last/frame_dur_seconds behavior as the
target of the check so the test catches any future drift in idempotence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@backends/sdl/src/window.zig`:
- Around line 132-143: The frame delta caching in beginDrawing and frameDuration
is now correct, but the fix should be protected with a regression test. Add a
test around the window timing path in backends/sdl/src/window.zig that exercises
beginDrawing and calls frameDuration() twice within the same frame, asserting
both calls return the same value; use the existing
frame_dur_last/frame_dur_seconds behavior as the target of the check so the test
catches any future drift in idempotence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f3f4bff-8b77-4a8a-8dfc-d2c1e4d9c182

📥 Commits

Reviewing files that changed from the base of the PR and between a6c497b and fa97448.

📒 Files selected for processing (1)
  • backends/sdl/src/window.zig

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fa97448f64

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const freq = c.SDL_GetPerformanceFrequency();
const now = c.SDL_GetPerformanceCounter();
if (freq != 0 and frame_dur_last != 0) {
frame_dur_seconds = @as(f64, @floatFromInt(now - frame_dur_last)) / @as(f64, @floatFromInt(freq));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the seeded dt through the first frame

When an SDL loop uses the new canonical frameDuration() as its dt source after calling beginDrawing(), the first beginDrawing() overwrites the 1/60 seed with the time since initWindow() before any frame has actually completed. If startup/setup/loading takes noticeable time, that startup interval is reported as the first frame's dt instead of the documented seed, causing an initial simulation jump or clamp; keep the seed until a completed frame has been presented/delayed, or make the first beginDrawing() only establish the baseline.

Useful? React with 👍 / 👎.

@apotema
apotema merged commit 8ab47d3 into main Jun 30, 2026
4 checks passed
@apotema
apotema deleted the fix/sdl-framededuration-idempotent branch June 30, 2026 21:04
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