Migrate 31 genuinely-constant operator/dispatch env::var sites onto HostConfig, so the
capture/topology/encoder decision reads ONE owner instead of being recomputed at each call
site (the latent bug where capture and encode could disagree on the resolved backend, plan §2.4):
idd_push x7, no_wgc, capture_backend, render_adapter, encoder_pref (Linux open_video +
linux_zero_copy_is_vaapi), the Windows vdisplay-backend select, plus the plan-named
secure_dda/idd_depth/zerocopy/ten_bit and the multi-site perf x4 / compositor x5 /
video_source x3 / gamepad. Each HostConfig field's parser is byte-identical to the read it
replaced, so old==new by construction (the plan's "a flipped bool is a silent regression" guard).
Scope correction — the plan's "~64 sites / Linux XDG+compositor included / grep env::var -> 0"
was unsafe as written. Two classes are deliberately KEPT as live reads and documented in config.rs:
* Runtime-mutated session vars. vdisplay::apply_session_env REWRITES the process env on every
connect (the Bazzite Gaming<->Desktop follow): WAYLAND_DISPLAY, XDG_CURRENT_DESKTOP,
XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS, and the derived PUNKTFUNK_INPUT_BACKEND,
GAMESCOPE_SESSION/NODE, KWIN/MUTTER_VIRTUAL_PRIMARY, FORCE_SHM. Parsing these once would
freeze them at startup and silently break session-following — they are NOT constant.
* Single-use local tuning with no resolve-once benefit (and FEC_PCT even has two different
semantics): FEC_PCT, VIDEO_DROP, VBV_FRAMES, SPLIT_ENCODE, PACE_BURST_KB, the dxgi timing
knobs, the *_LIVE/test gates, plus path/dynamic reads (config-dir, PATH search,
env-forward-to-child). PUNKTFUNK_ZEROCOPY is split on purpose: Windows presence-semantics
moved to the field; Linux keeps its own truthy (1|true|yes|on) parser.
Verified: Linux cargo check + clippy (-D warnings) + fmt clean on the touched files. The
Windows-only edits are 1:1 substitutions; they get a real Windows compile on the box with Stage 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.6 KiB
Goal-1 (clean, layered host architecture) — staged execution plan
The design is in windows-host-rewrite.md §2.2–2.4. This file is the ordered,
independently-shippable execution plan, because the host is live-validated (GameStream + punktfunk/1,
NVENC + IDD-push on-glass) and Goal-1 rewires its session/config/dispatch flow — so every stage must
preserve behavior, compile + box-verify on its own, and be committed before the next starts. The plan's
own §14 makes the §1 preservation checklist a mandatory per-module assert contract; honour it.
Why staged (not one big rewrite)
main is at parity and shipping. A monolithic rewrite would put the validated host in a broken
intermediate state for a long window and make a regression impossible to bisect. Each stage below is a
behaviour-preserving transform with its own verification, so a regression is caught at the stage that
introduced it.
Stages (ordered; each = goal · files · risk · verify)
Stage 1 — HostConfig foundation. ✅ DONE (this commit).
config.rs: typed HostConfig parsed ONCE from env (idd_push/encoder_pref/no_helper/force_helper).
Migrated the two highest-churn dispatch reads onto it (encode::windows_resolved_backend,
punktfunk1::should_use_helper). Risk: low (env constant at runtime → identical behaviour). Verify: box
cargo check --features nvenc.
Stage 2 — finish HostConfig + resolve-once. ✅ DONE (this commit).
Migrated 31 genuinely-constant operator/dispatch sites onto HostConfig: idd_push ×7 (the
capture/topology disagreement knob), no_wgc, capture_backend, render_adapter, encoder_pref (Linux),
the Windows vdisplay-backend select, plus the plan-named secure_dda/idd_depth/zerocopy/ten_bit and the
multi-site perf ×4 / compositor ×5 / video_source ×3 / gamepad. Each HostConfig field's parser is
byte-identical to the read it replaced, so old == new by construction (the §1 "flipped bool" guard).
Scope correction (the plan's "~64 sites / Linux XDG+compositor / grep→0" was unsafe as written): two
classes of env::var read are deliberately kept live and documented in config.rs:
- Runtime-mutated session vars. On Linux,
vdisplay::apply_session_envrewrites the process env on every connect (the Bazzite Gaming↔Desktop follow):WAYLAND_DISPLAY,XDG_CURRENT_DESKTOP,XDG_RUNTIME_DIR,DBUS_SESSION_BUS_ADDRESS, and the derivedPUNKTFUNK_INPUT_BACKEND,PUNKTFUNK_GAMESCOPE_SESSION/NODE,PUNKTFUNK_KWIN/MUTTER_VIRTUAL_PRIMARY,PUNKTFUNK_FORCE_SHM. Parse-once would freeze them at startup → silent session-following regression. They are NOT constant. - Single-use local tuning (no resolve-once benefit, call-site-local default/clamp, and
FEC_PCTeven has two different semantics):FEC_PCT,VIDEO_DROP,VBV_FRAMES,SPLIT_ENCODE,PACE_BURST_KB, thecapture/dxgi.rstiming knobs, the*_LIVE/test gates, plus path/dynamic reads (config-dir,PATHsearch, env-forward-to-child).PUNKTFUNK_ZEROCOPYis split on purpose: Windows presence-semantics moved to the field; Linux keeps its own truthy parser.
Risk: medium (semantics-preservation). Verify: Linux cargo check/clippy/fmt green (the Windows-only
edits are 1:1 substitutions, compile-verified on the box as part of Stage 3's build).
Stage 3 — SessionPlan (the single biggest clarity lever, plan §2.4).
Resolve display/capture/topology/encoder/format/hdr/bit_depth ONCE from HostConfig into a typed
SessionPlan; replace the 3-place dispatch (capture_virtual_output, should_use_helper/virtual_stream,
windows_resolved_backend) with reads off the plan. Fixes the capture/encode backend-disagreement bug
class. Risk: medium-high (rewires the deployed decision). Verify: box build + on-glass re-test (NVENC +
IDD-push + a mode switch).
Stage 4 — SessionContext + SessionFactory/Session.
Bundle the 12–13-arg #[allow(too_many_arguments)] signatures into SessionContext; SessionFactory.build()
owns the RAII chain vdm.lease(mode) → open_capturer(vout, fmt) → open_encoder(plan) → spawn pipeline, with
Session::drop the ONLY teardown path. Risk: high (teardown ordering — the §1 RAII asserts are mandatory).
Verify: box build + on-glass (connect/disconnect/reconnect, no leaked monitors/threads).
Stage 5 — seam-trait tightenings (plan §2.3).
Capturer::open_capturer(vout, want: OutputFormat) takes the format IN (kills the
capture → encode::windows_resolved_backend() back-reference recomputed in dxgi.rs); HDR/release become
VirtualLease methods (session glue names no concrete backend, contains no unsafe); optional encoder
features move to EncoderCaps. Risk: medium. Verify: box build + on-glass.
Stage 6 — src/windows/ tree (cfg-sprawl confinement, plan §2.2).
Move the Windows backends under src/windows/ + capture/windows/, encode/windows/, inject/windows/,
audio/windows/, vdisplay/windows.rs behind one #[cfg(windows)] mod windows; seam. Pure file move +
mod/use-path updates — behaviour-identical. Risk: low-but-huge (dozens of files; compile-verify catches all).
Do LAST so the earlier semantic stages don't fight path churn. Verify: Linux + box build.
Guardrails (mandatory, plan §14)
- Each stage is its own commit; box-verify before moving on.
- Stages 3–5 touch the deployed path → on-glass re-test (NVENC + IDD-push, a mode switch, a connect/disconnect cycle) before the next stage.
- Preserve every
PUNKTFUNK_*var's exact semantics; when in doubt, assert old==new at the call site.