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
|
/// 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
|
/// logical window size (load-modify-save, like the console settings screen) so the
|
||||||
/// next launch opens at it.
|
/// next launch opens at it.
|
||||||
pub(crate) fn match_window(
|
pub(crate) fn match_window(settings: &trust::Settings) -> Option<Box<dyn FnMut(u32, u32)>> {
|
||||||
settings: &trust::Settings,
|
|
||||||
) -> Option<Box<dyn FnMut(u32, u32)>> {
|
|
||||||
settings.match_window.then(|| {
|
settings.match_window.then(|| {
|
||||||
Box::new(|w: u32, h: u32| {
|
Box::new(|w: u32, h: u32| {
|
||||||
let mut s = trust::Settings::load();
|
let mut s = trust::Settings::load();
|
||||||
|
|||||||
@@ -466,17 +466,21 @@ fn pump(
|
|||||||
.is_none_or(|t| now.duration_since(t) >= Duration::from_millis(100))
|
.is_none_or(|t| now.duration_since(t) >= Duration::from_millis(100))
|
||||||
{
|
{
|
||||||
last_kf_req = Some(now);
|
last_kf_req = Some(now);
|
||||||
let _ = connector
|
let _ =
|
||||||
.request_rfi(exp, frame.frame_index.wrapping_sub(1));
|
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)),
|
None => next_expected_index = Some(frame.frame_index.wrapping_add(1)),
|
||||||
}
|
}
|
||||||
match decoder.decode(&frame.data) {
|
match decoder.decode(&frame.data) {
|
||||||
Ok(Some(image)) => {
|
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
|
// 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
|
// 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
|
// (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.
|
// 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!(REANCHOR_MARKS_TO_LIFT, 2);
|
||||||
assert_eq!(lift_at(&[(false, true)]), None);
|
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]
|
#[test]
|
||||||
@@ -919,7 +926,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn the_index_counter_wraps_cleanly() {
|
fn the_index_counter_wraps_cleanly() {
|
||||||
// last frame = u32::MAX, so the next expected wraps to 0.
|
// 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.
|
// 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, 0), Some(1));
|
||||||
assert_eq!(index_gap(u32::MAX, 2), Some(3));
|
assert_eq!(index_gap(u32::MAX, 2), Some(3));
|
||||||
|
|||||||
@@ -420,7 +420,10 @@ mod tests {
|
|||||||
assert!(ctx.settings.match_window, "Native → Match window");
|
assert!(ctx.settings.match_window, "Native → Match window");
|
||||||
assert_eq!((ctx.settings.width, ctx.settings.height), (0, 0));
|
assert_eq!((ctx.settings.width, ctx.settings.height), (0, 0));
|
||||||
assert!(adjust(RowId::Resolution, 1, false, &mut ctx));
|
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));
|
assert_eq!((ctx.settings.width, ctx.settings.height), (1280, 720));
|
||||||
// Stepping back from an explicit size returns to Match window, then Native.
|
// Stepping back from an explicit size returns to Match window, then Native.
|
||||||
assert!(adjust(RowId::Resolution, -1, false, &mut ctx));
|
assert!(adjust(RowId::Resolution, -1, false, &mut ctx));
|
||||||
|
|||||||
@@ -472,7 +472,12 @@ impl SkiaOverlay {
|
|||||||
self.resizing_since = None;
|
self.resizing_since = None;
|
||||||
return 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).
|
/// Make `slots[i]` a render target of exactly `width`×`height` (rebuilt on resize).
|
||||||
|
|||||||
@@ -1149,8 +1149,7 @@ fn resize_tick(
|
|||||||
persist(lw, lh);
|
persist(lw, lh);
|
||||||
let Some((w, h)) = target else { return };
|
let Some((w, h)) = target else { return };
|
||||||
tracing::info!(w, h, "window resized — requesting mode switch");
|
tracing::info!(w, h, "window resized — requesting mode switch");
|
||||||
if c
|
if c.request_mode(Mode {
|
||||||
.request_mode(Mode {
|
|
||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
refresh_hz: m.refresh_hz,
|
refresh_hz: m.refresh_hz,
|
||||||
@@ -1269,7 +1268,10 @@ impl ResizeIndicator {
|
|||||||
/// Timeout safety net: stop showing once [`TIMEOUT`](Self::TIMEOUT) has elapsed with no
|
/// 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).
|
/// matching frame (a rejected or host-capped switch never delivers the exact target).
|
||||||
fn tick(&mut self, now: Instant) {
|
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.target = None;
|
||||||
self.since = None;
|
self.since = None;
|
||||||
}
|
}
|
||||||
@@ -1450,7 +1452,14 @@ mod tests {
|
|||||||
// Equal to the streamed mode → settle (persist) but no request.
|
// Equal to the streamed mode → settle (persist) but no request.
|
||||||
let mut pending = Some(t0);
|
let mut pending = Some(t0);
|
||||||
assert_eq!(
|
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)
|
ResizeAction::Settled(None)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1472,7 +1481,14 @@ mod tests {
|
|||||||
// Tiny windows clamp to the host's floor.
|
// Tiny windows clamp to the host's floor.
|
||||||
let mut pending = Some(t0);
|
let mut pending = Some(t0);
|
||||||
assert_eq!(
|
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)))
|
ResizeAction::Settled(Some((320, 200)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1522,7 +1538,10 @@ mod tests {
|
|||||||
let near = t0 + ResizeIndicator::TIMEOUT - ms(1);
|
let near = t0 + ResizeIndicator::TIMEOUT - ms(1);
|
||||||
ind.steering(1200, 700, near); // new target → timeout re-armed from `near`
|
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
|
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
|
// Re-steering the SAME size does NOT re-arm (so a repeated identical request can't
|
||||||
// hold the scrim open forever).
|
// hold the scrim open forever).
|
||||||
@@ -1530,7 +1549,10 @@ mod tests {
|
|||||||
ind.steering(1000, 600, t0);
|
ind.steering(1000, 600, t0);
|
||||||
ind.steering(1000, 600, t0 + ms(500)); // same target, later — `since` unchanged
|
ind.steering(1000, 600, t0 + ms(500)); // same target, later — `since` unchanged
|
||||||
ind.tick(t0 + ResizeIndicator::TIMEOUT);
|
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) {
|
fn sample() -> (Stats, PresentedWindow) {
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ use crate::packet::FLAG_PROBE;
|
|||||||
use crate::quic::{
|
use crate::quic::{
|
||||||
accept_resync, endpoint, io, wall_clock_ns, window_loss_ppm, BitrateChanged, ClockEcho,
|
accept_resync, endpoint, io, wall_clock_ns, window_loss_ppm, BitrateChanged, ClockEcho,
|
||||||
ClockResync, ColorInfo, HdrMeta, Hello, HidOutput, LossReport, ProbeRequest, ProbeResult,
|
ClockResync, ColorInfo, HdrMeta, Hello, HidOutput, LossReport, ProbeRequest, ProbeResult,
|
||||||
Reconfigure, Reconfigured, RequestKeyframe, RfiRequest, ResyncStep, RichInput, SetBitrate, Start,
|
Reconfigure, Reconfigured, RequestKeyframe, ResyncStep, RfiRequest, RichInput, SetBitrate,
|
||||||
Welcome,
|
Start, Welcome,
|
||||||
};
|
};
|
||||||
use crate::session::{Frame, Session};
|
use crate::session::{Frame, Session};
|
||||||
use crate::transport::UdpTransport;
|
use crate::transport::UdpTransport;
|
||||||
@@ -395,7 +395,9 @@ impl RfiRecovery {
|
|||||||
// Forward gap: [exp, frame_index-1] lost. Advance past this frame so the same
|
// 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.
|
// gap isn't re-detected, then fire a throttled RFI for the lost range.
|
||||||
self.next_expected = Some(frame_index.wrapping_add(1));
|
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 {
|
if send {
|
||||||
self.last_req = Some(now);
|
self.last_req = Some(now);
|
||||||
}
|
}
|
||||||
@@ -968,7 +970,11 @@ impl NativeClient {
|
|||||||
/// signal and shares one throttle across RFI + keyframe requests.)
|
/// signal and shares one throttle across RFI + keyframe requests.)
|
||||||
pub fn note_frame_index(&self, frame_index: u32) -> bool {
|
pub fn note_frame_index(&self, frame_index: u32) -> bool {
|
||||||
// Decide (and update state) under the lock; fire the request after releasing it.
|
// 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 {
|
if let Some((first, last)) = rfi_range {
|
||||||
let _ = self.request_rfi(first, last);
|
let _ = self.request_rfi(first, last);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -466,8 +466,8 @@ pub mod dxgi;
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[path = "capture/windows/idd_push.rs"]
|
#[path = "capture/windows/idd_push.rs"]
|
||||||
pub mod idd_push;
|
pub mod idd_push;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod linux;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[path = "capture/windows/synthetic_nv12.rs"]
|
#[path = "capture/windows/synthetic_nv12.rs"]
|
||||||
pub mod synthetic_nv12;
|
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
|
// SAFETY: a self-contained builder owning every handle it creates; each COM call is checked
|
||||||
// and the returned owners drop with their wrappers.
|
// and the returned owners drop with their wrappers.
|
||||||
unsafe {
|
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 (device, context) = make_device(&adapter).context("create D3D11 device")?;
|
||||||
let default_tex = create_nv12(
|
let default_tex = create_nv12(
|
||||||
&device,
|
&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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,14 +72,18 @@ pub fn run(opts: Options) -> Result<()> {
|
|||||||
);
|
);
|
||||||
Box::new(
|
Box::new(
|
||||||
capture::synthetic_nv12::SyntheticNv12Capturer::new(
|
capture::synthetic_nv12::SyntheticNv12Capturer::new(
|
||||||
opts.width, opts.height, opts.fps,
|
opts.width,
|
||||||
|
opts.height,
|
||||||
|
opts.fps,
|
||||||
)
|
)
|
||||||
.context("open synthetic NV12 capturer")?,
|
.context("open synthetic NV12 capturer")?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[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 => {
|
Source::Portal => {
|
||||||
|
|||||||
Reference in New Issue
Block a user