Skip to content

fix(core): sleep to the next tick boundary, not past it - #1

Merged
instinkt900 merged 1 commit into
mainfrom
fix/ticker-frame-pacing
Sep 20, 2026
Merged

instinkt900 merged 1 commit into
mainfrom
fix/ticker-frame-pacing

Conversation

@instinkt900

@instinkt900 instinkt900 commented Sep 20, 2026 •

Copy link
Copy Markdown
Owner

Problem

Ticker::TickSync computed its sleep from a delta measured before Tick() ran:

auto deltaTicks = now - m_lastUpdateTicks;          // line 41
...
Tick(...);                                          // the whole frame's work
auto const sleepFor = m_updateTicks - deltaTicks;   // stale delta

period - delta is the time left until the next tick boundary as of line 41, but by the time it
is used the frame's work has already consumed part of that period. None of it is deducted, so
every iteration sleeps the full remainder on top of the work.

Iteration time becomes work + (period - delta) instead of max(work, period), and the overshoot
feeds back: a long iteration leaves a large delta next time round, so that sleep is short, so the
next iteration is quick, so the delta is small and the sleep is full again. The loop oscillates
rather than settling at the fixed rate.

Measured in TinCan

Profiler capture on a worst frame, 60 Hz (16.67 ms period):

ms
Tick (draw, present) 13.99
TickFixed x2 (updates) 4.12
untracked (the sleep) 14.83
frame 32.95

18.11 ms of work followed by a further 14.83 ms of sleep. The frame graph showed alternating tall
and short bars, which is the oscillation above.

Fix

Read the clock after Tick() returns and sleep until the next tick is actually due. A frame that
overruns the period falls through with no sleep, and the existing catch-up loop absorbs the slip.

Testing

Run under TinCan: frame pacing is visibly smoother and the alternating long/short frames are gone.
The existing ticker tests cover period arithmetic and start/stop only; none assert sleep behaviour,
so none needed changing.

Summary by CodeRabbit

  • Bug Fixes
    • Improved fixed-timestep update scheduling so each cycle better follows its intended interval.
    • Prevented unnecessary additional delay after update processing.
    • Allows the next update to proceed immediately when processing exceeds the scheduled interval.

Ticker computed its sleep from a delta measured before Tick() ran, so
every iteration slept the full remainder of the period on top of the
time the frame's work had already taken. The overshoot fed back into
the next iteration's delta, making the loop oscillate between long and
short frames instead of settling at the fixed rate.

Measure the clock after Tick() and sleep until the next tick is due, so
a frame that overruns the period falls through without sleeping.
@coderabbitai

coderabbitai Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 6acfa887-3ea2-4461-95b0-04283eacd59d

📥 Commits

Reviewing files that changed from the base of the PR and between 55453ed and a9729bb.

📒 Files selected for processing (1)
  • modules/core/include/moth/core/ticker.h

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Ticker::TickSync() now includes Tick() execution time in each fixed-tick period. It sleeps only for the remaining time before the next deadline and skips sleeping when the frame overruns.

Changes

Ticker timing

Layer / File(s) Summary
Fixed-tick deadline sleep
modules/core/include/moth/core/ticker.h
The loop computes the next tick deadline, measures time after Tick(), and sleeps only when time remains before that deadline.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: correcting Ticker::TickSync sleep timing to stop at the next tick boundary.
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@instinkt900
instinkt900 merged commit c151b9d into main Sep 20, 2026
11 checks passed
@instinkt900
instinkt900 deleted the fix/ticker-frame-pacing branch September 20, 2026 04:16
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