style: rustfmt the recovery + resize changes (Windows CI fmt check)
ci / web (push) Successful in 59s
ci / docs-site (push) Successful in 1m1s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
decky / build-publish (push) Successful in 18s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 59s
apple / swift (push) Successful in 4m26s
ci / bench (push) Successful in 5m53s
ci / rust (push) Failing after 7m35s
windows-host / package (push) Failing after 8m56s
flatpak / build-publish (push) Failing after 8m12s
arch / build-publish (push) Successful in 11m41s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m54s
android / android (push) Successful in 13m28s
deb / build-publish (push) Successful in 13m42s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m16s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m42s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m33s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m0s
docker / deploy-docs (push) Successful in 20s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m48s
release / apple (push) Successful in 25m4s
apple / screenshots (push) Successful in 19m32s
ci / web (push) Successful in 59s
ci / docs-site (push) Successful in 1m1s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
decky / build-publish (push) Successful in 18s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 59s
apple / swift (push) Successful in 4m26s
ci / bench (push) Successful in 5m53s
ci / rust (push) Failing after 7m35s
windows-host / package (push) Failing after 8m56s
flatpak / build-publish (push) Failing after 8m12s
arch / build-publish (push) Successful in 11m41s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m54s
android / android (push) Successful in 13m28s
deb / build-publish (push) Successful in 13m42s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m16s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m42s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m33s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m0s
docker / deploy-docs (push) Successful in 20s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m48s
release / apple (push) Successful in 25m4s
apple / screenshots (push) Successful in 19m32s
The `cargo fmt --check` step on the x86_64-pc-windows-msvc job was failing: the mid-stream loss-recovery and resize-overlay commits landed with unformatted wraps across pf-presenter, pf-client-core, punktfunk-core, pf-console-ui, and a few host files. Applied `cargo fmt`, and hand-relocated two trailing comments in session.rs (a decoded-frame note and the wrap-counter note) to their own lines so rustfmt no longer column-aligns the following comment block to a deep indent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -177,9 +177,7 @@ mod session_main {
|
||||
/// debounced resize→`Reconfigure` machinery on; the callback stores each resize-end's
|
||||
/// logical window size (load-modify-save, like the console settings screen) so the
|
||||
/// next launch opens at it.
|
||||
pub(crate) fn match_window(
|
||||
settings: &trust::Settings,
|
||||
) -> Option<Box<dyn FnMut(u32, u32)>> {
|
||||
pub(crate) fn match_window(settings: &trust::Settings) -> Option<Box<dyn FnMut(u32, u32)>> {
|
||||
settings.match_window.then(|| {
|
||||
Box::new(|w: u32, h: u32| {
|
||||
let mut s = trust::Settings::load();
|
||||
|
||||
@@ -466,17 +466,21 @@ fn pump(
|
||||
.is_none_or(|t| now.duration_since(t) >= Duration::from_millis(100))
|
||||
{
|
||||
last_kf_req = Some(now);
|
||||
let _ = connector
|
||||
.request_rfi(exp, frame.frame_index.wrapping_sub(1));
|
||||
let _ =
|
||||
connector.request_rfi(exp, frame.frame_index.wrapping_sub(1));
|
||||
}
|
||||
tracing::trace!(gap, "frame gap — RFI recovery, holding last frame until re-anchor");
|
||||
tracing::trace!(
|
||||
gap,
|
||||
"frame gap — RFI recovery, holding last frame until re-anchor"
|
||||
);
|
||||
}
|
||||
}
|
||||
None => next_expected_index = Some(frame.frame_index.wrapping_add(1)),
|
||||
}
|
||||
match decoder.decode(&frame.data) {
|
||||
Ok(Some(image)) => {
|
||||
no_output_streak = 0; // a decoded frame — the anchor holds
|
||||
// A decoded frame — the anchor holds.
|
||||
no_output_streak = 0;
|
||||
// Host-signalled intra-refresh recovery mark: on an IDR-free intra-refresh
|
||||
// stream this wave-boundary flag is the only clean point the client can honor
|
||||
// (the decoder never flags the re-anchor — the coded frame stays `P`). A live
|
||||
@@ -851,7 +855,10 @@ mod tests {
|
||||
// The first wave boundary after a loss is only half-healed — one mark must hold the freeze.
|
||||
assert_eq!(REANCHOR_MARKS_TO_LIFT, 2);
|
||||
assert_eq!(lift_at(&[(false, true)]), None);
|
||||
assert_eq!(lift_at(&[(false, false), (false, true), (false, false)]), None);
|
||||
assert_eq!(
|
||||
lift_at(&[(false, false), (false, true), (false, false)]),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -919,7 +926,8 @@ mod tests {
|
||||
#[test]
|
||||
fn the_index_counter_wraps_cleanly() {
|
||||
// last frame = u32::MAX, so the next expected wraps to 0.
|
||||
assert_eq!(index_gap(0, 0), None); // contiguous across the wrap
|
||||
// Contiguous across the wrap.
|
||||
assert_eq!(index_gap(0, 0), None);
|
||||
// waiting on u32::MAX, frame 0 arrived → MAX was skipped.
|
||||
assert_eq!(index_gap(u32::MAX, 0), Some(1));
|
||||
assert_eq!(index_gap(u32::MAX, 2), Some(3));
|
||||
|
||||
@@ -420,7 +420,10 @@ mod tests {
|
||||
assert!(ctx.settings.match_window, "Native → Match window");
|
||||
assert_eq!((ctx.settings.width, ctx.settings.height), (0, 0));
|
||||
assert!(adjust(RowId::Resolution, 1, false, &mut ctx));
|
||||
assert!(!ctx.settings.match_window, "explicit size clears the policy");
|
||||
assert!(
|
||||
!ctx.settings.match_window,
|
||||
"explicit size clears the policy"
|
||||
);
|
||||
assert_eq!((ctx.settings.width, ctx.settings.height), (1280, 720));
|
||||
// Stepping back from an explicit size returns to Match window, then Native.
|
||||
assert!(adjust(RowId::Resolution, -1, false, &mut ctx));
|
||||
|
||||
@@ -472,7 +472,12 @@ impl SkiaOverlay {
|
||||
self.resizing_since = None;
|
||||
return None;
|
||||
}
|
||||
Some(self.resizing_since.get_or_insert_with(Instant::now).elapsed().as_secs_f64())
|
||||
Some(
|
||||
self.resizing_since
|
||||
.get_or_insert_with(Instant::now)
|
||||
.elapsed()
|
||||
.as_secs_f64(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Make `slots[i]` a render target of exactly `width`×`height` (rebuilt on resize).
|
||||
|
||||
@@ -1149,13 +1149,12 @@ fn resize_tick(
|
||||
persist(lw, lh);
|
||||
let Some((w, h)) = target else { return };
|
||||
tracing::info!(w, h, "window resized — requesting mode switch");
|
||||
if c
|
||||
.request_mode(Mode {
|
||||
width: w,
|
||||
height: h,
|
||||
refresh_hz: m.refresh_hz,
|
||||
})
|
||||
.is_err()
|
||||
if c.request_mode(Mode {
|
||||
width: w,
|
||||
height: h,
|
||||
refresh_hz: m.refresh_hz,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
tracing::warn!("mode-switch request dropped — control channel closed");
|
||||
}
|
||||
@@ -1269,7 +1268,10 @@ impl ResizeIndicator {
|
||||
/// Timeout safety net: stop showing once [`TIMEOUT`](Self::TIMEOUT) has elapsed with no
|
||||
/// matching frame (a rejected or host-capped switch never delivers the exact target).
|
||||
fn tick(&mut self, now: Instant) {
|
||||
if self.since.is_some_and(|s| now.duration_since(s) >= Self::TIMEOUT) {
|
||||
if self
|
||||
.since
|
||||
.is_some_and(|s| now.duration_since(s) >= Self::TIMEOUT)
|
||||
{
|
||||
self.target = None;
|
||||
self.since = None;
|
||||
}
|
||||
@@ -1450,7 +1452,14 @@ mod tests {
|
||||
// Equal to the streamed mode → settle (persist) but no request.
|
||||
let mut pending = Some(t0);
|
||||
assert_eq!(
|
||||
resize_decision(t0 + ms(400), &mut pending, None, None, (1280, 720), (1280, 720)),
|
||||
resize_decision(
|
||||
t0 + ms(400),
|
||||
&mut pending,
|
||||
None,
|
||||
None,
|
||||
(1280, 720),
|
||||
(1280, 720)
|
||||
),
|
||||
ResizeAction::Settled(None)
|
||||
);
|
||||
|
||||
@@ -1472,7 +1481,14 @@ mod tests {
|
||||
// Tiny windows clamp to the host's floor.
|
||||
let mut pending = Some(t0);
|
||||
assert_eq!(
|
||||
resize_decision(t0 + ms(400), &mut pending, None, None, (1280, 720), (100, 80)),
|
||||
resize_decision(
|
||||
t0 + ms(400),
|
||||
&mut pending,
|
||||
None,
|
||||
None,
|
||||
(1280, 720),
|
||||
(100, 80)
|
||||
),
|
||||
ResizeAction::Settled(Some((320, 200)))
|
||||
);
|
||||
}
|
||||
@@ -1522,7 +1538,10 @@ mod tests {
|
||||
let near = t0 + ResizeIndicator::TIMEOUT - ms(1);
|
||||
ind.steering(1200, 700, near); // new target → timeout re-armed from `near`
|
||||
ind.tick(t0 + ResizeIndicator::TIMEOUT + ms(1)); // past A's window, within B's
|
||||
assert!(ind.active(), "retarget re-armed the timeout — no mid-drag flicker");
|
||||
assert!(
|
||||
ind.active(),
|
||||
"retarget re-armed the timeout — no mid-drag flicker"
|
||||
);
|
||||
|
||||
// Re-steering the SAME size does NOT re-arm (so a repeated identical request can't
|
||||
// hold the scrim open forever).
|
||||
@@ -1530,7 +1549,10 @@ mod tests {
|
||||
ind.steering(1000, 600, t0);
|
||||
ind.steering(1000, 600, t0 + ms(500)); // same target, later — `since` unchanged
|
||||
ind.tick(t0 + ResizeIndicator::TIMEOUT);
|
||||
assert!(!ind.active(), "an unchanged target keeps the original timeout");
|
||||
assert!(
|
||||
!ind.active(),
|
||||
"an unchanged target keeps the original timeout"
|
||||
);
|
||||
}
|
||||
|
||||
fn sample() -> (Stats, PresentedWindow) {
|
||||
|
||||
@@ -19,8 +19,8 @@ use crate::packet::FLAG_PROBE;
|
||||
use crate::quic::{
|
||||
accept_resync, endpoint, io, wall_clock_ns, window_loss_ppm, BitrateChanged, ClockEcho,
|
||||
ClockResync, ColorInfo, HdrMeta, Hello, HidOutput, LossReport, ProbeRequest, ProbeResult,
|
||||
Reconfigure, Reconfigured, RequestKeyframe, RfiRequest, ResyncStep, RichInput, SetBitrate, Start,
|
||||
Welcome,
|
||||
Reconfigure, Reconfigured, RequestKeyframe, ResyncStep, RfiRequest, RichInput, SetBitrate,
|
||||
Start, Welcome,
|
||||
};
|
||||
use crate::session::{Frame, Session};
|
||||
use crate::transport::UdpTransport;
|
||||
@@ -395,7 +395,9 @@ impl RfiRecovery {
|
||||
// Forward gap: [exp, frame_index-1] lost. Advance past this frame so the same
|
||||
// gap isn't re-detected, then fire a throttled RFI for the lost range.
|
||||
self.next_expected = Some(frame_index.wrapping_add(1));
|
||||
let send = self.last_req.is_none_or(|t| now.duration_since(t) >= RFI_THROTTLE);
|
||||
let send = self
|
||||
.last_req
|
||||
.is_none_or(|t| now.duration_since(t) >= RFI_THROTTLE);
|
||||
if send {
|
||||
self.last_req = Some(now);
|
||||
}
|
||||
@@ -968,7 +970,11 @@ impl NativeClient {
|
||||
/// signal and shares one throttle across RFI + keyframe requests.)
|
||||
pub fn note_frame_index(&self, frame_index: u32) -> bool {
|
||||
// Decide (and update state) under the lock; fire the request after releasing it.
|
||||
let (gap, rfi_range) = self.rfi.lock().unwrap().observe(frame_index, Instant::now());
|
||||
let (gap, rfi_range) = self
|
||||
.rfi
|
||||
.lock()
|
||||
.unwrap()
|
||||
.observe(frame_index, Instant::now());
|
||||
if let Some((first, last)) = rfi_range {
|
||||
let _ = self.request_rfi(first, last);
|
||||
}
|
||||
@@ -2097,7 +2103,7 @@ mod rfi_recovery_tests {
|
||||
let mut r = RfiRecovery::default();
|
||||
let t = base();
|
||||
r.observe(100, t); // expecting 101 next
|
||||
// 101..=104 were lost; 105 arrived. The RFI must name exactly the missing span.
|
||||
// 101..=104 were lost; 105 arrived. The RFI must name exactly the missing span.
|
||||
assert_eq!(r.observe(105, t), (true, Some((101, 104))));
|
||||
// The expectation advances past the delivered frame so the same gap can't re-fire.
|
||||
assert_eq!(r.next_expected, Some(106));
|
||||
@@ -2134,8 +2140,8 @@ mod rfi_recovery_tests {
|
||||
let t = base();
|
||||
r.observe(100, t);
|
||||
r.observe(105, t); // expecting 106 next
|
||||
// A reordered late arrival (103, well behind 106) is neither a gap nor a request, and it
|
||||
// must not rewind the expectation — otherwise the next in-order frame would false-gap.
|
||||
// A reordered late arrival (103, well behind 106) is neither a gap nor a request, and it
|
||||
// must not rewind the expectation — otherwise the next in-order frame would false-gap.
|
||||
assert_eq!(r.observe(103, t), (false, None));
|
||||
assert_eq!(r.next_expected, Some(106));
|
||||
}
|
||||
@@ -2156,7 +2162,7 @@ mod rfi_recovery_tests {
|
||||
let mut r = RfiRecovery::default();
|
||||
let t = base();
|
||||
r.observe(u32::MAX - 1, t); // expecting u32::MAX next
|
||||
// u32::MAX was lost and 1 arrived → the lost span wraps: [u32::MAX, 0].
|
||||
// u32::MAX was lost and 1 arrived → the lost span wraps: [u32::MAX, 0].
|
||||
assert_eq!(r.observe(1, t), (true, Some((u32::MAX, 0))));
|
||||
assert_eq!(r.next_expected, Some(2));
|
||||
}
|
||||
|
||||
@@ -466,8 +466,8 @@ pub mod dxgi;
|
||||
#[cfg(target_os = "windows")]
|
||||
#[path = "capture/windows/idd_push.rs"]
|
||||
pub mod idd_push;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "windows")]
|
||||
#[path = "capture/windows/synthetic_nv12.rs"]
|
||||
pub mod synthetic_nv12;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
|
||||
@@ -50,7 +50,8 @@ impl SyntheticNv12Capturer {
|
||||
// SAFETY: a self-contained builder owning every handle it creates; each COM call is checked
|
||||
// and the returned owners drop with their wrappers.
|
||||
unsafe {
|
||||
let adapter = resolve_render_adapter().context("resolve render adapter for NV12 source")?;
|
||||
let adapter =
|
||||
resolve_render_adapter().context("resolve render adapter for NV12 source")?;
|
||||
let (device, context) = make_device(&adapter).context("create D3D11 device")?;
|
||||
let default_tex = create_nv12(
|
||||
&device,
|
||||
|
||||
@@ -2112,7 +2112,9 @@ impl Encoder for AmfEncoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
inner.pending.push_back((captured.pts_ns, forced, recovery_anchor));
|
||||
inner
|
||||
.pending
|
||||
.push_back((captured.pts_ns, forced, recovery_anchor));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -4767,7 +4767,7 @@ mod tests {
|
||||
assert!(!mark_recovery_boundary(&mut pos, false, period)); // pos 1
|
||||
assert!(!mark_recovery_boundary(&mut pos, false, period)); // pos 2
|
||||
assert!(!mark_recovery_boundary(&mut pos, true, period)); // IDR → pos 0, no mark
|
||||
// Now a fresh full period is needed, not just the 2 remaining frames.
|
||||
// Now a fresh full period is needed, not just the 2 remaining frames.
|
||||
assert!(!mark_recovery_boundary(&mut pos, false, period)); // pos 1
|
||||
assert!(!mark_recovery_boundary(&mut pos, false, period)); // pos 2
|
||||
assert!(!mark_recovery_boundary(&mut pos, false, period)); // pos 3
|
||||
|
||||
@@ -72,14 +72,18 @@ pub fn run(opts: Options) -> Result<()> {
|
||||
);
|
||||
Box::new(
|
||||
capture::synthetic_nv12::SyntheticNv12Capturer::new(
|
||||
opts.width, opts.height, opts.fps,
|
||||
opts.width,
|
||||
opts.height,
|
||||
opts.fps,
|
||||
)
|
||||
.context("open synthetic NV12 capturer")?,
|
||||
)
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
anyhow::bail!("--source synthetic-nv12 is Windows-only (native AMF / D3D11 encoders)");
|
||||
anyhow::bail!(
|
||||
"--source synthetic-nv12 is Windows-only (native AMF / D3D11 encoders)"
|
||||
);
|
||||
}
|
||||
}
|
||||
Source::Portal => {
|
||||
|
||||
Reference in New Issue
Block a user