okee-wheel-timer 0.1.0

Deterministic hashed wheel timer with keyed deduplication.
Documentation
use super::{HashedWheelTimer, TimeWheelError};

#[test]
fn schedule_returns_incremental_ids() {
    let mut wheel = HashedWheelTimer::new(4);

    let first = wheel.schedule(0, 42);
    let second = wheel.schedule(10, 42);

    assert_eq!(first.id, 1);
    assert_eq!(second.id, 2);
    assert_eq!(wheel.count_all(), 2);
    assert_eq!(wheel.count_in_bucket(0).unwrap(), 1);
    assert_eq!(wheel.count_in_bucket(1).unwrap(), 0);
}

#[test]
fn pop_events_works_by_delta_waves_on_same_tick() {
    let mut wheel = HashedWheelTimer::new(4);

    let _ = wheel.schedule(0, 1);
    let _ = wheel.schedule(0, 1);
    let first_wave = wheel.pop_events();

    let _ = wheel.schedule(0, 3);
    let second_wave = wheel.pop_events();

    assert_eq!(first_wave.len(), 2);
    assert_eq!(first_wave[0].id(), 1);
    assert_eq!(first_wave[1].id(), 2);
    assert_eq!(second_wave.len(), 1);
    assert_eq!(second_wave[0].id(), 3);
}

#[test]
fn remove_update_and_reschedule_by_id() {
    let mut wheel = HashedWheelTimer::new(8);
    let created = wheel.schedule(5, 10);
    let id = created.id;

    let updated = wheel.update(id, 0, 20).expect("event should exist");
    assert_eq!(updated.id, id);
    assert_eq!(wheel.get(id).map(|e| e.data()), Some(&20));
    assert_eq!(wheel.get(id).map(|e| e.tick()), Some(0));

    let moved = wheel.reschedule(id, 7).expect("event should exist");
    assert_eq!(moved.id, id);
    assert_eq!(wheel.get(id).map(|e| e.tick()), Some(7));

    let removed = wheel.remove(id).expect("event should be removed");
    assert_eq!(removed.id(), id);
    assert!(wheel.get(id).is_none());
    assert!(wheel.is_empty());
}

#[test]
fn step_advances_time_and_unlocks_future_event() {
    let mut wheel = HashedWheelTimer::new(4);
    let scheduled = wheel.schedule(2, 1);

    assert!(wheel.pop_events().is_empty());
    wheel.step();
    assert!(wheel.pop_events().is_empty());
    wheel.step();

    let events = wheel.pop_events();
    assert_eq!(events.len(), 1);
    assert_eq!(events[0].id(), scheduled.id);
}

#[test]
fn invalid_bucket_index_returns_error() {
    let wheel = HashedWheelTimer::<u32>::new(2);

    let err = wheel.count_in_bucket(10).unwrap_err();
    assert_eq!(
        err,
        TimeWheelError::InvalidBucketIndex {
            index: 10,
            buckets: 2,
        }
    );
}

#[test]
fn is_empty_bucket_reflects_bucket_state() {
    let mut wheel = HashedWheelTimer::new(2);
    assert!(wheel.is_empty_bucket(0).unwrap());

    let _ = wheel.schedule(0, 99);
    assert!(!wheel.is_empty_bucket(0).unwrap());
}

#[test]
fn has_events_in_current_tick_ignores_future_events_in_same_bucket() {
    let mut wheel = HashedWheelTimer::new(2);

    // tick=2 maps to bucket 0 when buckets_num=2, same as current bucket at tick=0.
    let _ = wheel.schedule(2, 10);
    assert!(!wheel.has_events_in_current_tick());

    let _ = wheel.schedule(0, 20);
    assert!(wheel.has_events_in_current_tick());

    let popped = wheel.pop_events();
    assert_eq!(popped.len(), 1);
    assert!(!wheel.has_events_in_current_tick());
}