Skip to content

Fixed-rate graphs: timer, Versioned history, yield points, default priority - #2

Merged
Andriy06 merged 3 commits into
masterfrom
fixed-rate-graphs
Sep 14, 2026
Merged

Andriy06 merged 3 commits into
masterfrom
fixed-rate-graphs

Conversation

@Andriy06

Copy link
Copy Markdown
Collaborator

A subsystem on its own clock becomes a second compiled graph beside the frame graph, bounded by a Deferred for inputs and a Versioned for outputs. The library supplies the mechanisms; the composition lives in samples (guide §6.6, design §6.1).

  • Timers (ts/timer.h): ts::sleep, ts::sleep_until, ts::Periodic. One timer thread; wakeups are delivered as tasks, never inline; high-resolution waitable timer on Windows.
  • Versioned<T, History::current_and_previous>: keeps the previous version; auto [previous, current, stamps] = v.read_last_versions(), or co_await ts::read_last_versions(v).
  • ts::yield(): runs a queued high task inline on the yielding worker's stack; parallel_for yields between chunks.
  • Static_task_graph::set_default_priority.
  • Samples: sample/fixed_rate.cpp (--fixed-rate), and game_frame's fixed_rate variant (physics at 60 Hz, networking at 30 Hz), with a third --trace SVG and --bench rows.

Local verification: Release suite 3/3 clean on 2 workers; CMake windows-msvc 913 checks and windows-shipping 720 checks, 0 failures; pre-push TSan clean; negative checks for the yield path, the history rotation, and the timer's deadlock-net registration.

The fixed-rate variant is neutral on frame time (17.12 ms vs 16.93 ms optimised, 8 workers), because the optimised frame is already core-bound; its gain there is structural.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QTtvN1mH7K6MWzpYiyJ88w

Andriy06 and others added 3 commits September 14, 2026 11:59
…iority

A subsystem on its own clock becomes a second compiled graph beside the frame
graph, bounded by a Deferred for inputs and a Versioned for outputs. The
library supplies the mechanisms; the composition stays in samples.

- ts/timer.h, src/timer.cpp: ts::sleep, ts::sleep_until and ts::Periodic. One
  lazily created timer thread with a min-heap delivers each wakeup as a task at
  the sleep's priority, never inline. Cancellation settles promptly. An armed
  sleep holds an External_wait. On Windows the thread waits on a
  high-resolution waitable timer. Worker-less mode is fatal, and
  destroy_scheduler stops the thread first (a live sleep is fatal under
  TS_SAFETY_CHECKS).
- Versioned<T, History::current_and_previous>: a third replica rotated at the
  swap, publish stamps, copy resync by default (replay is a construction
  fatal). read_last_versions() returns a Version_view, the read grant, lent
  when the context already grants the front; co_await ts::read_last_versions(v)
  is the awaitable form. Access_awaiter's resume bookkeeping is factored into
  finish_acquire().
- ts::yield(): when a high task is queued, runs it inline on the yielding
  worker's stack under a scope that resets the thread's task state, then
  returns. One relaxed load otherwise. parallel_for yields at every chunk
  claim, and the trace excludes the nested span from the yielding body's time.
- Static_task_graph::set_default_priority.
- sample/fixed_rate.cpp (--fixed-rate [n]) and game_frame's fixed_rate variant:
  physics on a 60 Hz graph and networking on a 30 Hz graph beside the frame,
  with a third --trace SVG and --bench rows.

Tests: a timer group, plus versioned, yield, parallel_for, graph and
integration additions, and a death scenario for each new fatal. Negative
checks were done for the yield path, the rotation and the External_wait.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTtvN1mH7K6MWzpYiyJ88w
- guide.md: 6.6 fixed-rate graphs, 10.6 timers, yield points in 10.1 and 7,
  the previous-version history in 9.2, set_default_priority, tool-table rows,
  and two limitations.
- design.md: yield points and the timer thread in 3, the history rotation in
  6, and 6.1 on fixed-rate graphs as the logical-execution-time model.
- example-frame-optimization.md 5.1: the fixed-rate variant measured against
  the baseline and optimised frames. It is neutral on frame time because the
  optimised frame is already core-bound.
- Status updates in TODO 2.11, the timer design study, pattern-farming 2.2,
  the two read_pair mentions, CLAUDE.md and show_graph.bat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTtvN1mH7K6MWzpYiyJ88w
…ation timer

Yield points
- `detail::yield_signal`: the queued `high` and global-`normal` counts, alone on one
  cache line; incremented before a global push, decremented after every global pop.
  Local-deque pushes stay uncounted.
- `yield_to_higher` runs one entry of a higher class: `normal` -> `high`; `low` ->
  `high`, then the global `normal` queue; `high` never yields.
- The nested dispatch detaches the resume and inline-dispatch trampolines (by value,
  out of line, a spare buffer keeps the nested drain allocation-free) and reattaches
  them afterwards, so a coroutine or inline node the nested task releases runs at the
  yield point, also inside a resumed segment or an inline drain. `Yield_nesting`
  makes yield points inside the nested task no-ops (one level deep).

Timer
- A wait is one `Timed_executable` block: `Executable` plus the timer bookkeeping.
  A fire or a cancel submits that block itself; a passed deadline or a requested
  token runs it in the call. `Periodic::next` is no longer a coroutine.
- mem_profile rows for sleep and `Periodic::next`; TODO 4.9 for allocation-free ticks.

Versioned
- Document `Version_view`'s access-context contribution (one or two entries).

Samples and tests
- fixed_rate checks the version stamps and that the clock kept up with the grid.
- The engine fixed-rate test runs 60 frames and checks both clocks ticked.
- New tests: low yields to normal, normal does not yield to normal, a resume caused
  at a yield point runs there, an inline node released at a yield point runs there,
  no nested yielding; TSan stage `yield resumes`.

Docs: guide 7, 9.2, 10.1, 10.6, 13; design 3 (yield points, timer thread); the
timer design status; TODO; CLAUDE.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTtvN1mH7K6MWzpYiyJ88w
@Andriy06
Andriy06 merged commit 5e9ed67 into master Sep 14, 2026
9 checks passed
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