use super::{
super::rate::remote_request_rate,
lane::{
MAX_CONSECUTIVE_REMOTE_FAILURES, REMOTE_RETRY_BACKOFF_INITIAL, REMOTE_RETRY_BACKOFF_MAX,
pause_remote_lane, remote_retry_backoff,
},
run_batch::{
FIRST_EVENT_TIMEOUT, LIVENESS_TIMEOUT, ProgressSpan, liveness_deadline, transfer_allowance,
},
scene_state::scene_state_from_snapshot,
};
use crate::bridge::export_thread::{sample_cursor::SampleCursor, scene_snapshot::SceneSnapshot};
use indicatrix::{geometry::cuts::StandardGemCuts, optics::materials::GemMaterial};
use std::{
sync::atomic::AtomicBool,
time::{Duration, Instant},
};
#[test]
fn scene_state_from_snapshot_carries_the_snapshots_own_bounce_cap_not_a_viewport_default() {
let snapshot = SceneSnapshot {
yaw: 0.6,
pitch: 0.45,
distance: 2.4,
light_yaw: 0.85,
light_pitch: 0.95,
material: GemMaterial::diamond(),
lighting_preset: indicatrix::optics::raytracer::LightingPreset::RingLights,
max_bounces: 64, exposure: 1.0,
backdrop: 0.0,
active_planes: StandardGemCuts::standard_round_brilliant(),
tools: Vec::new(),
fluorescence: std::sync::Arc::default(),
facet_finishes: Vec::new(),
env_map: None,
surface_glare: 1.0,
head_shadow_deg: 16.0,
};
let state = scene_state_from_snapshot(&snapshot, 1920, 1080, snapshot.yaw, snapshot.pitch);
assert_eq!(
state.max_bounces, 64,
"the remote RenderRequest's scene must carry the export's OWN bounce cap"
);
}
#[test]
fn scene_state_from_snapshot_carries_the_snapshots_surface_glare() {
let snapshot = SceneSnapshot {
yaw: 0.6,
pitch: 0.45,
distance: 2.4,
light_yaw: 0.85,
light_pitch: 0.95,
material: GemMaterial::diamond(),
lighting_preset: indicatrix::optics::raytracer::LightingPreset::RingLights,
max_bounces: 12,
exposure: 1.0,
backdrop: 0.0,
active_planes: StandardGemCuts::standard_round_brilliant(),
tools: Vec::new(),
fluorescence: std::sync::Arc::default(),
facet_finishes: Vec::new(),
env_map: None,
surface_glare: 0.25,
head_shadow_deg: 16.0,
};
let state = scene_state_from_snapshot(&snapshot, 64, 64, snapshot.yaw, snapshot.pitch);
assert_eq!(state.surface_glare.to_bits(), 0.25f32.to_bits());
}
#[test]
fn scene_state_from_snapshot_uses_the_explicit_pose_override_not_the_snapshots_own_yaw_pitch() {
let snapshot = SceneSnapshot {
yaw: 0.6,
pitch: 0.45,
distance: 2.4,
light_yaw: 0.85,
light_pitch: 0.95,
material: GemMaterial::diamond(),
lighting_preset: indicatrix::optics::raytracer::LightingPreset::RingLights,
max_bounces: 12,
exposure: 1.0,
backdrop: 0.0,
active_planes: StandardGemCuts::standard_round_brilliant(),
tools: Vec::new(),
fluorescence: std::sync::Arc::default(),
facet_finishes: Vec::new(),
env_map: None,
surface_glare: 1.0,
head_shadow_deg: 16.0,
};
let state = scene_state_from_snapshot(&snapshot, 1920, 1080, 1.23, -0.45);
assert_eq!(
state.yaw, 1.23,
"the override's yaw must win over the snapshot's own"
);
assert_eq!(
state.pitch, -0.45,
"the override's pitch must win over the snapshot's own"
);
assert_eq!(
state.distance, snapshot.distance,
"distance is not swept per frame, so it still comes from the snapshot"
);
}
#[test]
fn liveness_deadline_grants_the_first_event_grace_before_any_update_has_arrived() {
assert_eq!(
liveness_deadline(false, 0),
FIRST_EVENT_TIMEOUT,
"the wait for a dispatch's very first RemoteUpdate (even Connected) must \
use the longer grace, not the steady-state deadline"
);
}
#[test]
fn liveness_deadline_switches_to_the_tighter_steady_state_timeout_once_seen() {
assert_eq!(
liveness_deadline(true, 0),
LIVENESS_TIMEOUT,
"once a dispatch has produced at least one update, every wait after it must \
use the steady-state liveness timeout, not the first-event grace"
);
}
#[test]
fn first_event_timeout_is_strictly_longer_than_liveness_timeout() {
assert!(FIRST_EVENT_TIMEOUT > LIVENESS_TIMEOUT);
}
#[test]
fn liveness_deadline_budgets_a_whole_frame_transfer_on_a_slow_link() {
let bytes_4k = 3840 * 2160 * indicatrix_net::radiance::BYTES_PER_PIXEL as u64;
let bytes_1080p = 1920 * 1080 * indicatrix_net::radiance::BYTES_PER_PIXEL as u64;
let at_4k = liveness_deadline(true, bytes_4k);
let at_1080p = liveness_deadline(true, bytes_1080p);
assert!(at_4k >= LIVENESS_TIMEOUT + Duration::from_secs(20));
assert!(at_4k > at_1080p);
assert_eq!(transfer_allowance(0), Duration::ZERO);
}
#[test]
fn progress_span_has_no_advance_before_two_distinct_reports() {
let t0 = Instant::now();
let mut span = ProgressSpan::default();
assert_eq!(span.advance(), None);
span.note(t0, 0); assert_eq!(span.advance(), None);
span.note(t0 + Duration::from_secs(1), 100);
assert_eq!(span.advance(), None, "one report is a point, not a span");
span.note(t0 + Duration::from_secs(2), 100); assert_eq!(span.advance(), None);
}
#[test]
fn progress_span_measures_from_the_first_to_the_latest_advancing_report() {
let t0 = Instant::now();
let mut span = ProgressSpan::default();
span.note(t0 + Duration::from_secs(1), 100);
span.note(t0 + Duration::from_secs(3), 300);
span.note(t0 + Duration::from_secs(4), 300); span.note(t0 + Duration::from_secs(5), 500);
span.note(t0 + Duration::from_secs(6), 400); assert_eq!(span.advance(), Some((400, Duration::from_secs(4))));
}
#[test]
fn a_single_coarse_progress_jump_yields_the_whole_request_rate() {
let t0 = Instant::now();
let mut span = ProgressSpan::default();
span.note(t0 + Duration::from_secs(20), 1000);
let whole = Some(40.0);
for coordinator in [false, true] {
assert_eq!(
remote_request_rate(coordinator, 1000, Duration::from_secs(25), span.advance()),
whole
);
}
}
#[test]
fn a_worker_with_two_progress_reports_is_measured_between_them_not_to_done() {
let t0 = Instant::now();
let mut span = ProgressSpan::default();
span.note(t0 + Duration::from_secs(2), 100);
span.note(t0 + Duration::from_secs(12), 1100);
assert_eq!(
remote_request_rate(false, 1100, Duration::from_secs(25), span.advance()),
Some(100.0)
);
assert_eq!(
remote_request_rate(true, 1100, Duration::from_secs(25), span.advance()),
Some(44.0)
);
}
#[test]
fn remote_retry_backoff_doubles_from_the_threshold_and_caps() {
assert_eq!(
remote_retry_backoff(MAX_CONSECUTIVE_REMOTE_FAILURES),
REMOTE_RETRY_BACKOFF_INITIAL
);
assert_eq!(
remote_retry_backoff(MAX_CONSECUTIVE_REMOTE_FAILURES + 1),
REMOTE_RETRY_BACKOFF_INITIAL * 2
);
assert_eq!(remote_retry_backoff(50), REMOTE_RETRY_BACKOFF_MAX);
}
#[test]
fn pause_remote_lane_returns_early_once_the_shared_pool_is_dry() {
let cursor = SampleCursor::new(0, 4);
assert_eq!(cursor.claim(4), Some((0, 4)));
let cancel = AtomicBool::new(false);
let started = Instant::now();
assert!(!pause_remote_lane(
&cursor,
&cancel,
Duration::from_secs(30)
));
assert!(started.elapsed() < Duration::from_secs(5));
}