# okee-wheel-timer
Deterministic hashed wheel timer with keyed deduplication.
## Features
- `HashedWheelTimer<T>`: core single-level hashed wheel timer.
- `KeyedHashedWheelTimer<T, K, F>`: keyed dedup wrapper over the core timer.
- Stable pop ordering by `(tick, delta_tick, event_id)`.
## Installation
```toml
[dependencies]
okee-wheel-timer = "0.1.0"
```
## How It Works
```rust
use okee_wheel_timer::HashedWheelTimer;
// 8 buckets in one wheel cycle.
let mut wheel = HashedWheelTimer::new(8);
```
- `new(8)` means one wheel cycle has 8 buckets.
- Time is tracked by `tick`.
- `step()` advances one tick and moves to the next bucket.
- Events are stored with `(tick, delta_tick)`.
- `tick` is the absolute time when the event is eligible.
- `delta_tick` is the wave index inside the same tick.
`delta_tick` prevents processing newly scheduled "same tick" events in the same wave.
## Core Timer Example
```rust
use okee_wheel_timer::HashedWheelTimer;
let mut wheel = HashedWheelTimer::new(8);
let created = wheel.schedule(0, "payload");
let first_wave = wheel.pop_events();
assert_eq!(first_wave.len(), 1);
assert_eq!(first_wave[0].id(), created.id);
assert_eq!(first_wave[0].tick(), 0);
assert_eq!(first_wave[0].data(), &"payload");
```
## Keyed Timer Example
`HashedWheelTimer` always creates a new event ID. `KeyedHashedWheelTimer` can replace an existing event by your dedup key.
```rust
use okee_wheel_timer::KeyedHashedWheelTimer;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Job {
user_id: u64,
queue: u8,
payload: i32,
}
let first = wheel.schedule(
10,
Job {
user_id: 42,
queue: 1,
payload: 100,
},
);
let second = wheel.schedule(
12,
Job {
user_id: 42, // same user_id and queue as previous
queue: 1,
payload: 200,
},
);
assert_eq!(second.replaced_id, Some(first.id));
assert!(!wheel.contains_by_id(first.id));
assert!(wheel.contains_by_id(second.id));
```
## Dedup Key Design
- In keyed mode, you are responsible for dedup semantics.
- The dedup key is produced from `&T` (`Fn(&T) -> K`).
- If timing should affect uniqueness, include timing fields in your payload `T` and key them explicitly.
## Processing Contract
- Call `pop_events()` while `has_events_in_current_tick()` is `true`.
- Call `step()` to move to the next tick.
- Typical loop:
```rust
while wheel.has_events_in_current_tick() {
let events = wheel.pop_events();
// process events
}
wheel.step();
```
## Benchmarks
Benchmarks are implemented with Criterion in `benches/wheel.rs`.
```bash
# run all timer benchmarks
cargo bench --bench wheel
# save current results as baseline
cargo bench --bench wheel -- --save-baseline v1
# compare current run with a saved baseline
cargo bench --bench wheel -- --baseline v1
```
HTML reports are generated in `target/criterion/report/index.html`.