tempa 0.1.17

A no_std temporal system for animation and simulation, providing time, duration, time ranges, frame rates, and deterministic frame-index stepping.
Documentation
#![cfg(test)]

use crate::{FrameIndex, FrameRate, Time};

mod construction {
    use super::*;

    #[test]
    fn new_preserves_raw_value() {
        let frame = FrameIndex::new(42_u32);
        assert_eq!(frame.index(), 42);
        assert_eq!(frame.into_index(), 42);
    }

    #[test]
    fn from_and_into_conversions() {
        let frame = FrameIndex::from(24_u32);
        let raw: u32 = frame.into();
        assert_eq!(raw, 24);
    }

    #[test]
    fn frame_and_time_conversions_match_frame_rate() {
        let rate = FrameRate::new(24.0_f32);
        let frame = FrameIndex::<u32>::from_time(Time::new(1.75_f32), rate);
        assert_eq!(frame, FrameIndex::new(42_u32));

        let t = frame.to_time(rate);
        assert_eq!(t, Time::new(1.75_f32));
    }
}

mod numeric {
    use rinia::numeric::{
        BoundedMax, BoundedMin, Cast, LossyCast, MinMax, SaturatingCast, TryCast, TryExactCast,
        Zero,
    };

    use super::*;

    #[test]
    fn zero_and_default_are_consistent() {
        assert_eq!(FrameIndex::<u32>::ZERO, FrameIndex::new(0_u32));
        assert_eq!(FrameIndex::<u32>::default(), FrameIndex::<u32>::ZERO);
        assert_eq!(<FrameIndex<u32> as Zero>::ZERO, FrameIndex::<u32>::ZERO);
    }

    #[test]
    fn bounded_values_match_underlying_type() {
        assert_eq!(FrameIndex::<u32>::MIN.index(), u32::MIN);
        assert_eq!(FrameIndex::<u32>::MAX.index(), u32::MAX);
        assert_eq!(<FrameIndex<u32> as BoundedMin>::MIN, FrameIndex::<u32>::MIN);
        assert_eq!(<FrameIndex<u32> as BoundedMax>::MAX, FrameIndex::<u32>::MAX);
    }

    #[test]
    fn min_max_and_minimum_maximum_behave_as_expected() {
        let a = FrameIndex::new(1_u32);
        let b = FrameIndex::new(2_u32);

        assert_eq!(a.min(b), a);
        assert_eq!(a.max(b), b);
        assert_eq!(a.minimum(b), a);
        assert_eq!(a.maximum(b), b);
    }

    #[test]
    fn cast_variants_are_forwarded_to_inner_type() {
        let f_u8 = FrameIndex::new(42_u8);

        let cast_u32: FrameIndex<u32> = f_u8.cast();
        assert_eq!(cast_u32, FrameIndex::new(42_u32));
        let cast_u32_trait: FrameIndex<u32> = <FrameIndex<u8> as Cast<FrameIndex<u32>>>::cast(f_u8);
        assert_eq!(cast_u32_trait, FrameIndex::new(42_u32));
        assert_eq!(FrameIndex::<u32>::cast_from(f_u8), FrameIndex::new(42_u32));

        let lossy_u8: FrameIndex<u8> = f_u8.lossy_cast();
        assert_eq!(lossy_u8, FrameIndex::new(42_u8));
        let lossy_u8_trait: FrameIndex<u8> =
            <FrameIndex<u8> as LossyCast<FrameIndex<u8>>>::lossy_cast(f_u8);
        assert_eq!(lossy_u8_trait, FrameIndex::new(42_u8));
        assert_eq!(FrameIndex::<u8>::lossy_cast_from(f_u8), FrameIndex::new(42_u8));

        let sat_u8: FrameIndex<u8> = f_u8.saturating_cast();
        assert_eq!(sat_u8, FrameIndex::new(42_u8));
        let sat_u8_trait: FrameIndex<u8> =
            <FrameIndex<u8> as SaturatingCast<FrameIndex<u8>>>::saturating_cast(f_u8);
        assert_eq!(sat_u8_trait, FrameIndex::new(42_u8));
        assert_eq!(FrameIndex::<u8>::saturating_cast_from(f_u8), FrameIndex::new(42_u8));

        let try_u32: FrameIndex<u32> = f_u8.try_cast().expect("u8 to u32 should work");
        assert_eq!(try_u32, FrameIndex::new(42_u32));
        let try_u32_trait: FrameIndex<u32> =
            <FrameIndex<u8> as TryCast<FrameIndex<u32>>>::try_cast(f_u8)
                .expect("u8 to u32 should work");
        assert_eq!(try_u32_trait, FrameIndex::new(42_u32));
        assert_eq!(
            FrameIndex::<u32>::try_cast_from(f_u8).expect("u8 to u32 should work"),
            FrameIndex::new(42_u32)
        );

        let exact_u32: FrameIndex<u32> = f_u8.try_exact_cast().expect("exact cast should work");
        assert_eq!(exact_u32, FrameIndex::new(42_u32));
        let exact_u32_trait: FrameIndex<u32> =
            <FrameIndex<u8> as TryExactCast<FrameIndex<u32>>>::try_exact_cast(f_u8)
                .expect("exact cast should work");
        assert_eq!(exact_u32_trait, FrameIndex::new(42_u32));
        assert_eq!(
            FrameIndex::<u32>::try_exact_cast_from(f_u8).expect("exact cast should work"),
            FrameIndex::new(42_u32)
        );
    }
}

mod ops {
    use rinia::numeric::{SaturatingAdd, SaturatingSub};

    use super::*;

    #[test]
    fn add_sub_with_frames() {
        let a = FrameIndex::new(10_u32);
        let b = FrameIndex::new(3_u32);

        assert_eq!(a + b, FrameIndex::new(13_u32));
        assert_eq!(a - b, FrameIndex::new(7_u32));
    }

    #[test]
    fn add_sub_with_scalars() {
        let a = FrameIndex::new(10_u32);

        assert_eq!(a + 5_u32, FrameIndex::new(15_u32));
        assert_eq!(a - 2_u32, FrameIndex::new(8_u32));
    }

    #[test]
    fn add_assign_and_sub_assign_with_frames_and_scalars() {
        let mut frame = FrameIndex::new(10_u32);
        frame += FrameIndex::new(5_u32);
        assert_eq!(frame, FrameIndex::new(15_u32));

        frame -= FrameIndex::new(2_u32);
        assert_eq!(frame, FrameIndex::new(13_u32));

        frame += 7_u32;
        assert_eq!(frame, FrameIndex::new(20_u32));

        frame -= 4_u32;
        assert_eq!(frame, FrameIndex::new(16_u32));
    }

    #[test]
    fn saturating_add_and_sub_work_for_frames_and_scalars() {
        let max = FrameIndex::new(u32::MAX);
        assert_eq!(
            FrameIndex::saturating_add(max, FrameIndex::new(1_u32)),
            FrameIndex::new(u32::MAX)
        );
        assert_eq!(
            <FrameIndex<u32> as SaturatingAdd>::saturating_add(max, FrameIndex::new(1_u32)),
            FrameIndex::new(u32::MAX)
        );
        assert_eq!(FrameIndex::saturating_add_t(max, 1_u32), FrameIndex::new(u32::MAX));
        assert_eq!(
            <FrameIndex<u32> as SaturatingAdd<u32>>::saturating_add(max, 1_u32),
            FrameIndex::new(u32::MAX)
        );

        let zero = FrameIndex::new(0_u32);
        assert_eq!(
            FrameIndex::saturating_sub(zero, FrameIndex::new(1_u32)),
            FrameIndex::new(0_u32)
        );
        assert_eq!(
            <FrameIndex<u32> as SaturatingSub>::saturating_sub(zero, FrameIndex::new(1_u32)),
            FrameIndex::new(0_u32)
        );
        assert_eq!(FrameIndex::saturating_sub_t(zero, 1_u32), FrameIndex::new(0_u32));
        assert_eq!(
            <FrameIndex<u32> as SaturatingSub<u32>>::saturating_sub(zero, 1_u32),
            FrameIndex::new(0_u32)
        );
    }

    #[test]
    fn offset_next_and_previous_work() {
        let a = FrameIndex::new(10_u32);
        let b = FrameIndex::new(7_u32);

        assert_eq!(a.offset_from(b), 3_i32);
        assert_eq!(b.offset_from(a), -3_i32);

        assert_eq!(a.next(), FrameIndex::new(11_u32));
        assert_eq!(FrameIndex::new(u32::MAX).next(), FrameIndex::new(u32::MAX));

        assert_eq!(a.previous(), FrameIndex::new(9_u32));
        assert_eq!(FrameIndex::new(0_u32).previous(), FrameIndex::new(0_u32));
    }
}

mod approx {
    use rinia::{
        assert_approx_eq_abs, assert_approx_eq_rel, assert_approx_ne_abs, assert_approx_ne_rel,
    };

    use super::*;

    #[test]
    fn approx_eq_abs_and_rel_forward_to_inner_value() {
        let a = FrameIndex::new(1.0_f32);
        let b = FrameIndex::new(1.0008_f32);

        assert_approx_eq_abs!(a, b, 0.001_f32);
        assert_approx_ne_abs!(a, b, 0.0001_f32);

        let c = FrameIndex::new(100.0_f32);
        let d = FrameIndex::new(100.4_f32);

        assert_approx_eq_rel!(c, d, 0.01_f32);
        assert_approx_ne_rel!(c, d, 0.001_f32);
    }
}

mod compat {
    #[allow(unused_imports)]
    use super::*;

    #[cfg(feature = "bytemuck")]
    #[test]
    fn bytemuck_roundtrip() {
        let frame = FrameIndex::new(123_i32);
        let bytes = bytemuck::bytes_of(&frame);
        let out = bytemuck::pod_read_unaligned::<FrameIndex<i32>>(bytes);
        assert_eq!(out, frame);
    }

    #[cfg(feature = "zerocopy")]
    #[test]
    fn zerocopy_roundtrip() {
        let frame = FrameIndex::new(45_u8);
        let bytes = <FrameIndex<u8> as zerocopy::IntoBytes>::as_bytes(&frame);
        let out = <FrameIndex<u8> as zerocopy::FromBytes>::read_from_bytes(bytes)
            .expect("valid frame index bytes");
        assert_eq!(out, frame);
    }

    #[cfg(feature = "sakka")]
    #[test]
    fn sakka_roundtrip() {
        let frame = FrameIndex::new(789_i32);

        let mut writer = sakka::Writer::new(sakka::Endian::Little, ());
        <FrameIndex<i32> as sakka::Encode>::encode(&frame, &mut writer)
            .expect("encode frame index");
        let bytes = writer.finish();

        let mut reader = sakka::Reader::new(&bytes, sakka::Endian::Little, ());
        let out =
            <FrameIndex<i32> as sakka::Decode>::decode(&mut reader).expect("decode frame index");

        assert_eq!(out, frame);
        assert!(reader.is_eof());
    }
}