use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SnapDirection {
Exact,
Before,
After,
}
impl SnapDirection {
pub fn name(&self) -> &'static str {
match self {
SnapDirection::Exact => "exact",
SnapDirection::Before => "before",
SnapDirection::After => "after",
}
}
}
broadcast_common::impl_spec_display!(SnapDirection);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConditionedSplicePoint {
pub requested_pts: u64,
pub snapped_pts: u64,
pub delta_ticks: u64,
pub direction: SnapDirection,
}
impl ConditionedSplicePoint {
pub fn is_exact(&self) -> bool {
self.delta_ticks == 0
}
}
pub fn condition_splice_point(
requested_pts: u64,
candidates: &[u64],
max_delta_ticks: u64,
) -> Result<ConditionedSplicePoint> {
let mut nearest: Option<(u64, u64)> = None; for &c in candidates {
let delta = c.abs_diff(requested_pts);
if nearest.is_none_or(|(_, best)| delta < best) {
nearest = Some((c, delta));
}
}
let (snapped_pts, delta_ticks) = nearest.ok_or(Error::NoCandidates)?;
if delta_ticks > max_delta_ticks {
return Err(Error::NoAlignedBoundary {
requested_pts,
tolerance_ticks: max_delta_ticks,
nearest_delta_ticks: delta_ticks,
});
}
let direction = if snapped_pts == requested_pts {
SnapDirection::Exact
} else if snapped_pts < requested_pts {
SnapDirection::Before
} else {
SnapDirection::After
};
Ok(ConditionedSplicePoint {
requested_pts,
snapped_pts,
delta_ticks,
direction,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snaps_to_nearest_candidate() {
let result = condition_splice_point(1_000, &[500, 1_050, 2_000], 100).unwrap();
assert_eq!(result.snapped_pts, 1_050);
assert_eq!(result.delta_ticks, 50);
assert_eq!(result.direction, SnapDirection::After);
assert!(!result.is_exact());
}
#[test]
fn exact_match_reports_zero_delta() {
let result = condition_splice_point(1_000, &[1_000], 0).unwrap();
assert_eq!(result.delta_ticks, 0);
assert_eq!(result.direction, SnapDirection::Exact);
assert!(result.is_exact());
}
#[test]
fn before_direction_when_candidate_precedes_request() {
let result = condition_splice_point(1_000, &[900], 200).unwrap();
assert_eq!(result.snapped_pts, 900);
assert_eq!(result.direction, SnapDirection::Before);
}
#[test]
fn rejects_a_candidate_outside_tolerance() {
let err = condition_splice_point(1_000, &[2_000], 500).unwrap_err();
match err {
Error::NoAlignedBoundary {
requested_pts,
tolerance_ticks,
nearest_delta_ticks,
} => {
assert_eq!(requested_pts, 1_000);
assert_eq!(tolerance_ticks, 500);
assert_eq!(nearest_delta_ticks, 1_000);
}
other => panic!("expected NoAlignedBoundary, got {other:?}"),
}
}
#[test]
fn rejects_empty_candidates() {
let err = condition_splice_point(1_000, &[], 500).unwrap_err();
assert!(matches!(err, Error::NoCandidates));
}
#[test]
fn direction_labels() {
assert_eq!(SnapDirection::Exact.name(), "exact");
assert_eq!(alloc::format!("{}", SnapDirection::After), "after");
}
}