Files
horizon-airdef/docs/debounce-queue.md
T

84 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Debounce queue experiment
The local profile showed substantial allocation on the distant-terrain chunk
update queue thread. Direct per-thread allocation counters confirmed activity
there; sampled stacks repeatedly entered player-position reads and queue
reinsertion. Inspection of the pinned DH 3.3.4 bytecode explains a mechanism:
an update younger than its existing 250 ms debounce deadline is removed and
reinserted, while the outer queue loop sleeps only when both queues are empty.
A nonempty pending queue can therefore retry continuously before it can do work.
The optional `queue-wait` profile keeps balanced graphics, distances, simulation
settings, mods and worker budgets. Its original hook performs the existing
debounce reinsertion, then requests a wait of up to 1 ms, bounded by the remaining
deadline, if no preprocessing work is pending. It only waits on the queue worker,
never a caller such as the render thread. Incoming queued work unparks a debounce
wait; the original empty-queue sleep remains unchanged. OS scheduling determines
when the worker resumes.
The same update object, priority queue, debounce timestamp, overload policy,
executor dispatch and normal simulation remain in use. Executor-unavailable and
rejected-dispatch retries are outside this hook.
`adaptive-queue` combines this hook with the existing adaptive terrain budget
and precise worker pacing. Use `queue-wait` versus `m2-balanced` to isolate the
queue change, and `adaptive-queue` versus `adaptive-pacing` to evaluate the
combination. Each profile explicitly enables only its requested experiments.
Set `enabled=false` in `config/horizon-queue-wait.properties` and restart to
disable the queue hook. It is disabled by default and requires exact DH 3.3.4.
Requested waits are not hard latency bounds: operating-system timers can
overshoot, and arrival/worker scheduling affect responsiveness. Parking leaves
the interruption flag intact, so the existing shutdown loop still handles it.
New arrival wakeups reduce the chance of delaying useful preprocessing, but
real terrain completion, block-change visibility and platform behavior need
evaluation alongside CPU, allocations and frame pacing.
## Isolated measurement
Run `python -c "from tools.build_mod import benchmark_debounce; benchmark_debounce()"`
after building the mod. It uses the actual publisher's `ChunkPosQueue`, with
synthetic entries and a 250 ms pending-debounce interval. The upstream mode
repeatedly pops and reinserts the nearest update; the waiting mode inserts the
same bounded-wait policy between retries. Warm-ups precede three alternating
trials at one and 64 queued entries. Each trial checks update identity, nearest
priority and queue count, then reports wall time, worker CPU and worker allocation.
This benchmark isolates redundant polling; it does not simulate game chunk
lighting, rendering, network traffic or terrain completion. Thread CPU counters
can be coarse, particularly on Windows, so a zero reading is below measurement
resolution, not proof of zero CPU cost. Allocation counts are cumulative bytes
allocated during the interval, not retained heap or process memory. Hardware,
timer behavior and workload can change results. The CI publishes raw output
for Linux, Windows and Apple Silicon macOS. No game-FPS claim follows from this
microbenchmark alone.
## Hosted results, 3 October 2026
Raw output is checked in under
[`benchmarks/debounce-queue-2026-10-03`](../benchmarks/debounce-queue-2026-10-03/).
The tables show medians of three trials. Allocations and CPU refer only to this
synthetic queue polling interval, not the entire game. The macOS runner is hosted
Apple Silicon with three logical CPUs; it is not the user's M2 Pro.
| Platform / queued entries | Upstream allocation | Waiting allocation | Upstream worker CPU | Waiting worker CPU |
| --- | ---: | ---: | ---: | ---: |
| macOS ARM64 / 1 | 80,066,144 B | 768 B | 247.157 ms | 0.873 ms |
| macOS ARM64 / 64 | 34,344,320 B | 2,112 B | 248.550 ms | 1.154 ms |
| Linux x64 / 1 | 99,456,064 B | 7,520 B | 248.446 ms | 2.960 ms |
| Linux x64 / 64 | 31,971,072 B | 7,488 B | 249.987 ms | 2.890 ms |
| Windows x64 / 1 | 95,728,256 B | 5,536 B | 250.000 ms | Below counter resolution |
| Windows x64 / 64 | 32,051,200 B | 5,376 B | 250.000 ms | Below counter resolution |
Each upstream interval lasted about 250 ms. Waiting medians were 250.068 ms on
Linux, 250.593–250.856 ms on Windows, and 251.431–252.426 ms on macOS. One macOS
trial lasted 258.012 ms, demonstrating timer overshoot. These are interval-ending
times; they are not measurements of a real block change reaching the screen.
The real 0.7.0 adaptive-queue demo world renders and records with the hook enabled.
Thread inspection reached the injected debounce parking path. A 120-second run
wrote 643 frame intervals and 2,408 server ticks with matching CSV counts/means,
and 119–120 RSS samples. Distant-terrain data continued updating; game worlds and
DH databases closed cleanly. A separate 60-second profile confirmed allocation
counter activity on the queue thread. Its workload/cache state differs from the
earlier profile, so those profiles are not a numerical before/after comparison.