use crate::settings::{LiveComputeTarget, LiveTransfer};
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct LiveRemoteAllowedInputs {
pub(super) tab_visible: bool,
pub(super) paused: bool,
pub(super) export_active: bool,
}
#[must_use]
pub(super) const fn live_remote_may_run(inputs: LiveRemoteAllowedInputs) -> bool {
inputs.tab_visible && !inputs.paused && !inputs.export_active
}
#[must_use]
pub(super) const fn wants_display_only(
target: LiveComputeTarget,
transfer: LiveTransfer,
refused_on_this_connection: bool,
) -> bool {
matches!(transfer, LiveTransfer::FinalPicture)
&& !matches!(target, LiveComputeTarget::LocalOnly)
&& !refused_on_this_connection
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct RedispatchInputs {
pub(super) handoff_idle: bool,
pub(super) tab_visible: bool,
pub(super) epoch_live: bool,
pub(super) scene_stable: bool,
pub(super) gave_up_for_this_scene: bool,
pub(super) suspended: bool,
pub(super) drag_held: bool,
}
#[must_use]
pub(super) fn drag_held_effective(held: bool, quiet_for: Duration, watchdog: Duration) -> bool {
held && quiet_for < watchdog
}
#[must_use]
pub(super) fn pose_settle_due(
previewing: bool,
drag_held: bool,
quiet_for: Duration,
debounce: Duration,
) -> bool {
previewing && !drag_held && quiet_for >= debounce
}
#[must_use]
pub(super) const fn should_redispatch(inputs: RedispatchInputs) -> bool {
inputs.handoff_idle
&& inputs.tab_visible
&& !inputs.suspended
&& !inputs.drag_held
&& !inputs.epoch_live
&& inputs.scene_stable
&& !inputs.gave_up_for_this_scene
}
#[must_use]
pub(super) fn served_by_label(
mode: LiveComputeTarget,
handoff_says_remote: bool,
combined_remote_samples: u32,
worker_label: &str,
) -> Option<String> {
match mode {
LiveComputeTarget::Both => (combined_remote_samples > 0).then(|| {
if worker_label.is_empty() {
"Local + Remote".to_string()
} else {
format!("Local + Remote ({worker_label})")
}
}),
LiveComputeTarget::RemoteOnly => handoff_says_remote.then(|| worker_label.to_string()),
LiveComputeTarget::LocalOnly => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
const READY: RedispatchInputs = RedispatchInputs {
handoff_idle: true,
tab_visible: true,
epoch_live: false,
scene_stable: true,
gave_up_for_this_scene: false,
suspended: false,
drag_held: false,
};
#[test]
fn a_settled_visible_view_with_a_released_epoch_redispatches() {
assert!(should_redispatch(READY));
}
#[test]
fn each_blocking_condition_alone_prevents_a_redispatch() {
let blocked = [
RedispatchInputs {
handoff_idle: false,
..READY
},
RedispatchInputs {
tab_visible: false,
..READY
},
RedispatchInputs {
epoch_live: true,
..READY
},
RedispatchInputs {
scene_stable: false,
..READY
},
RedispatchInputs {
gave_up_for_this_scene: true,
..READY
},
RedispatchInputs {
suspended: true,
..READY
},
RedispatchInputs {
drag_held: true,
..READY
},
];
for inputs in blocked {
assert!(!should_redispatch(inputs), "{inputs:?}");
}
}
const DEBOUNCE: Duration = Duration::from_millis(250);
const WATCHDOG: Duration = Duration::from_secs(20);
#[test]
fn a_held_button_blocks_the_settle_however_long_the_pose_rests() {
assert!(!pose_settle_due(
true,
true,
Duration::from_secs(10),
DEBOUNCE
));
}
#[test]
fn a_released_button_settles_after_the_debounce() {
assert!(pose_settle_due(true, false, DEBOUNCE, DEBOUNCE));
assert!(!pose_settle_due(
true,
false,
Duration::from_millis(100),
DEBOUNCE
));
}
#[test]
fn an_idle_machine_never_settles() {
assert!(!pose_settle_due(
false,
false,
Duration::from_secs(10),
DEBOUNCE
));
}
#[test]
fn the_watchdog_releases_a_stale_held_flag() {
assert!(!drag_held_effective(true, WATCHDOG, WATCHDOG));
assert!(drag_held_effective(true, Duration::from_secs(19), WATCHDOG));
assert!(!drag_held_effective(false, Duration::ZERO, WATCHDOG));
}
#[test]
fn a_given_up_scene_is_not_retried_but_a_new_scene_is() {
let same_scene = RedispatchInputs {
gave_up_for_this_scene: true,
..READY
};
assert!(!should_redispatch(same_scene));
assert!(should_redispatch(RedispatchInputs {
gave_up_for_this_scene: false,
..same_scene
}));
}
#[test]
fn a_scene_change_while_suspended_does_not_redispatch_until_unsuspended() {
let while_suspended = RedispatchInputs {
suspended: true,
..READY
};
assert!(!should_redispatch(while_suspended));
assert!(should_redispatch(RedispatchInputs {
suspended: false,
..while_suspended
}));
}
#[test]
fn remote_may_run_only_when_visible_unpaused_and_not_exporting() {
assert!(live_remote_may_run(LiveRemoteAllowedInputs {
tab_visible: true,
paused: false,
export_active: false,
}));
}
#[test]
fn each_condition_alone_blocks_remote_from_running() {
let blocked = [
LiveRemoteAllowedInputs {
tab_visible: false,
paused: false,
export_active: false,
},
LiveRemoteAllowedInputs {
tab_visible: true,
paused: true,
export_active: false,
},
LiveRemoteAllowedInputs {
tab_visible: true,
paused: false,
export_active: true,
},
];
for inputs in blocked {
assert!(!live_remote_may_run(inputs), "{inputs:?}");
}
}
#[test]
fn both_mode_names_the_combination_and_never_flips_to_remote_only() {
let label = served_by_label(LiveComputeTarget::Both, false, 40, "GPU (X)");
assert_eq!(label.as_deref(), Some("Local + Remote (GPU (X))"));
let finished = served_by_label(LiveComputeTarget::Both, true, 256, "GPU (X)");
assert_eq!(finished.as_deref(), Some("Local + Remote (GPU (X))"));
assert_eq!(
served_by_label(LiveComputeTarget::Both, true, 0, "GPU (X)"),
None
);
assert_eq!(
served_by_label(LiveComputeTarget::Both, false, 3, "").as_deref(),
Some("Local + Remote")
);
}
#[test]
fn display_only_follows_the_live_transfer_until_the_connection_refuses_it() {
use LiveComputeTarget::{Both, LocalOnly, RemoteOnly};
use LiveTransfer::{FinalPicture, FullData};
assert!(wants_display_only(Both, FinalPicture, false));
assert!(wants_display_only(RemoteOnly, FinalPicture, false));
assert!(!wants_display_only(LocalOnly, FinalPicture, false));
assert!(!wants_display_only(Both, FullData, false));
assert!(
!wants_display_only(Both, FinalPicture, true),
"an UNSUPPORTED_REQUEST refusal falls back to full data"
);
}
#[test]
fn remote_only_and_local_only_keep_their_labels() {
assert_eq!(
served_by_label(LiveComputeTarget::RemoteOnly, true, 0, "CPU, 8 threads").as_deref(),
Some("CPU, 8 threads")
);
assert_eq!(
served_by_label(LiveComputeTarget::RemoteOnly, false, 99, "CPU"),
None
);
assert_eq!(
served_by_label(LiveComputeTarget::LocalOnly, true, 99, "CPU"),
None
);
}
}