Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c3dff60
refactor(tick): extract BeginTick (timing phase) from RunTick
Maxaubert Oct 13, 2026
6c812f9
refactor(tick): extract PollConfigReload from RunTick
Maxaubert Oct 13, 2026
3af435c
refactor(tick): extract WatchForegroundAndKbHook from RunTick
Maxaubert Oct 13, 2026
decccab
refactor(tick): extract BindKeyDown/BindModsHeld/BindComboHeld from R…
Maxaubert Oct 13, 2026
2438cc3
refactor(tick): extract ResolveInputAndZoom from RunTick
Maxaubert Oct 13, 2026
6438c34
refactor(tick): extract ResolveTickModes from RunTick
Maxaubert Oct 13, 2026
23f59d2
refactor(tick): extract RetargetSessionMonitor from RunTick
Maxaubert Oct 13, 2026
d40b49a
refactor(tick): extract PickEngineAtZoomIn from RunTick
Maxaubert Oct 13, 2026
6cf9610
refactor(tick): extract EnterSession from RunTick
Maxaubert Oct 13, 2026
0220274
refactor(tick): extract EnterInspect from RunTick
Maxaubert Oct 13, 2026
cc18d4b
refactor(tick): extract ExitInspect from RunTick
Maxaubert Oct 13, 2026
3c3ba7f
refactor(tick): extract ResolvePanDelta from RunTick
Maxaubert Oct 13, 2026
a8fdc83
refactor(tick): extract ReadForegroundAndArmWall from RunTick
Maxaubert Oct 13, 2026
d9e8cca
refactor(tick): extract MapPointer from RunTick
Maxaubert Oct 14, 2026
d717b6e
refactor(tick): extract StepTracking from RunTick
Maxaubert Oct 14, 2026
26eacc6
refactor(tick): extract ProbeClicks from RunTick
Maxaubert Oct 14, 2026
f94cfe2
refactor(tick): extract CommitInspectClicks from RunTick
Maxaubert Oct 14, 2026
a6c224d
refactor(tick): extract InstantHybridSwitch from RunTick
Maxaubert Oct 14, 2026
050cb84
refactor(tick): extract BuildPresentExtras from RunTick
Maxaubert Oct 14, 2026
2cf9f78
refactor(tick): extract PaceAndPresent from RunTick
Maxaubert Oct 14, 2026
314fa1f
refactor(tick): extract RevealAndHandover from RunTick
Maxaubert Oct 14, 2026
9fa4557
refactor(tick): extract AssertGameInspectSteal from RunTick
Maxaubert Oct 14, 2026
87ece43
refactor(tick): extract UpdateCursorBaseline from RunTick
Maxaubert Oct 14, 2026
50ba625
refactor(tick): extract TeardownSession from RunTick
Maxaubert Oct 14, 2026
c878763
refactor(tick): extract IdleTick from RunTick
Maxaubert Oct 14, 2026
e09301d
refactor(tick): extract RunActiveTick from RunTick
Maxaubert Oct 14, 2026
1b748ef
refactor(tick): extract LogHeldState from RunTick
Maxaubert Oct 14, 2026
70b3e82
refactor(tick): extract LogFramePacing from RunTick
Maxaubert Oct 14, 2026
954f84b
refactor(tick): extract WriteTestTelemetry from RunTick
Maxaubert Oct 14, 2026
4dd68a4
refactor(tick): extract ApplyReloadedConfig from PollConfigReload
Maxaubert Oct 14, 2026
d9f5e07
docs: describe RunTick's phase functions (#478)
Maxaubert Oct 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 37 additions & 6 deletions docs/architecture/02-tick-loop.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# 02. The tick loop

Everything Wind does at runtime happens in one function, `RunTick` in `src/main.cpp`. Each tick
samples input, advances the zoom, resolves a pan and asks the current engine to present. This
chapter lists the phases in source order, then covers pacing, idle sleep and the threads that sit
beside the loop.
Everything Wind does at runtime happens in one tick, `RunTick` in `src/main.cpp`. `RunTick` is a
short sequence of calls to static phase functions in the same file (see
[Phase functions](#phase-functions)). Each tick samples input, advances the zoom, resolves a pan and
asks the current engine to present. This chapter lists the phases in source order, then covers
pacing, idle sleep and the threads that sit beside the loop.

## Why one loop

Expand Down Expand Up @@ -92,13 +93,43 @@ builds its magnification context and cursor lens at 1x and keeps them warm (#369
[05](05-transform-engine.md)); it no longer releases them after a zoom. The tick ends with the stuck-input
timeline and, under `diagnostics=1`, the 2 s frame-pacing window.

### Phase functions

`RunTick` calls these in order; locals that a later phase reads come back in small structs
(`TickClock`, `TickInput`, `TickModes`, `PanDelta`, `TickForeground`, `PointerMap`). Everything
else lives in `TickState`. Add new per-tick work to the phase it belongs to, not to `RunTick`.

| Phase | Function |
|---|---|
| Timing, idle colour filter, zoom timeline step | `BeginTick` |
| Config hot-reload (apply: `ApplyReloadedConfig`) | `PollConfigReload` |
| Launch-cover watch, `noSwallowApps`, keyboard-hook watchdog | `WatchForegroundAndKbHook` |
| Held binds, zoom step, recenter and Inspect edges, quick zoom | `ResolveInputAndZoom` (bind state: `BindComboHeld`) |
| Raw motion, zoomed / Inspect / active, pan arm | `ResolveTickModes` |
| Active tick (zoomed or Inspect) | `RunActiveTick`, which calls the rows below |
| Session enter: resets, retarget, engine pick, seeds | `EnterSession` (`RetargetSessionMonitor`, `PickEngineAtZoomIn`) |
| Inspect edges | `EnterInspect`, `ExitInspect` |
| Pan delta | `ResolvePanDelta` |
| Foreground facts, pan wall, launch cover | `ReadForegroundAndArmWall` |
| Free cursor, pan glide, game-drawn cursor, mapper step | `MapPointer` |
| Tracking, keyboard pan, view owner | `StepTracking` |
| Dead-zone probe, Inspect clicks | `ProbeClicks`, `CommitInspectClicks` |
| Hybrid instant switch | `InstantHybridSwitch` |
| `PresentExtras` | `BuildPresentExtras` |
| Game pacing and present | `PaceAndPresent` |
| Reveal gating and handover overlap | `RevealAndHandover` |
| Game-inspect foreground steal | `AssertGameInspectSteal` |
| Baseline | `UpdateCursorBaseline` |
| Teardown / idle | `TeardownSession` / `IdleTick` |
| Diagnostics and telemetry | `LogHeldState`, `LogFramePacing`, `WriteTestTelemetry` |

## Config hot-reload

There is no settings IPC. `WindConfig.exe` writes `magnifier.ini` and the core notices:

- `wWinMain` arms `FindFirstChangeNotificationW` on the ini's directory (`LAST_WRITE` +
`FILE_NAME`, so rename-based saves fire it too).
- `RunTick` polls the handle with a zero timeout about four times a second. Per-tick polling is a
- The tick (`PollConfigReload`) polls the handle with a zero timeout about four times a second. Per-tick polling is a
kernel transition 144 times a second for a file a human changes. Without a watch handle the loop
falls back to a ~1 s timed poll.
- Only a changed mtime (`ConfigMTime`) proceeds to a reload.
Expand Down Expand Up @@ -156,7 +187,7 @@ The main loop in `wWinMain` paces the tick:
| Render, vsync (default) | `Present(1,0)` blocks to the refresh; the timer is skipped |
| Render, `dwmFlush=1` | `Present(0,0)`, then `DwmFlush()` after the tick |
| Transform | Always `DwmFlush` while zoomed (the write and the pointer land in one composite); an unpaced loop would flood DWM's transform queue until the view lags. Idle at 1x uses the timer or the idle sleep |
| Game pacing modes | Paced inside `RunTick` (vblank waits or the present accumulator) |
| Game pacing modes | Paced inside the tick, `PaceAndPresent` (vblank waits or the present accumulator) |

`DetectRefreshHz` reads the current monitor's real rate and is re-read on retarget. **Tick counts
are refresh-rate dependent**, so every tick-tuned constant derives from the detected rate:
Expand Down
5 changes: 3 additions & 2 deletions docs/architecture/04-render-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ for it ([03](03-engines.md)).

`RenderEngine` (`src/render_engine.h`) is a PIMPL class; D3D and DXGI headers stay in
`src/render_engine.cpp`. `RenderModel` (`src/render_model.cpp`) adapts it to `IMagnifierModel`.
`RunTick` owns activation and reveal, because they need facts the engine does not have (does the
foreground cover the monitor, is this the idle-to-active edge).
`RunTick` owns activation and reveal (`EnterSession`, `RevealAndHandover` in `src/main.cpp`),
because they need facts the engine does not have (does the foreground cover the monitor, is this
the idle-to-active edge).

Per frame, `renderFrame(RenderFrameParams)` captures if the desktop changed, draws three passes
(magnify, outline, cursor) and presents. The view is a float source rect (`srcLeft`/`srcTop` plus
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/05-transform-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ channels and on the plain desktop too. With MPO disabled
Smooth sampling takes a float path and survives the same corner, as does the built-in Magnifier,
which samples smooth.

Defences (wall arming in `RunTick`, write clamp in `TransformModel::writeSiteTransform`):
Defences (wall arming in `RunTick`'s `ReadForegroundAndArmWall`, write clamp in `TransformModel::writeSiteTransform`):

| MPO at boot | Sampling | Pan walls (`setMaxSourceLeft/Top`, `|src*level| <= 32000`) |
|---|---|---|
Expand Down
Loading
Loading