use std::marker::PhantomData;
use std::ops::DerefMut;
use std::time::{Duration, Instant};
use nexus_timer::store::SlabStore;
pub use nexus_timer::{
BoundedWheel, BoundedWheelBuilder, Full, TimerHandle, UnboundedWheelBuilder, Wheel,
WheelBuilder, WheelEntry,
};
impl<T: Send + 'static, S: nexus_timer::store::SlabStore<Item = WheelEntry<T>> + 'static>
crate::world::Resource for nexus_timer::TimerWheel<T, S>
{
}
use crate::Handler;
use crate::driver::Installer;
use crate::world::{ResourceId, World, WorldBuilder};
pub type TimerWheel = Wheel<Box<dyn Handler<Instant>>>;
pub type BoundedTimerWheel = BoundedWheel<Box<dyn Handler<Instant>>>;
#[cfg(feature = "smartptr")]
pub type InlineTimerWheel = Wheel<crate::FlatVirtual<Instant, nexus_smartptr::B256>>;
#[cfg(feature = "smartptr")]
pub type FlexTimerWheel = Wheel<crate::FlexVirtual<Instant, nexus_smartptr::B256>>;
#[cfg(feature = "smartptr")]
pub type BoundedInlineTimerWheel = BoundedWheel<crate::FlatVirtual<Instant, nexus_smartptr::B256>>;
#[cfg(feature = "smartptr")]
pub type BoundedFlexTimerWheel = BoundedWheel<crate::FlexVirtual<Instant, nexus_smartptr::B256>>;
pub trait TimerConfig: Send + 'static {
type Storage: DerefMut<Target = dyn Handler<Instant>> + Send + 'static;
fn wrap(handler: impl Handler<Instant> + 'static) -> Self::Storage;
}
pub struct BoxedTimers;
impl TimerConfig for BoxedTimers {
type Storage = Box<dyn Handler<Instant>>;
fn wrap(handler: impl Handler<Instant> + 'static) -> Self::Storage {
Box::new(handler)
}
}
#[cfg(feature = "smartptr")]
pub struct InlineTimers;
#[cfg(feature = "smartptr")]
impl TimerConfig for InlineTimers {
type Storage = crate::FlatVirtual<Instant, nexus_smartptr::B256>;
fn wrap(handler: impl Handler<Instant> + 'static) -> Self::Storage {
let ptr: *const dyn Handler<Instant> = &handler;
unsafe { nexus_smartptr::Flat::new_raw(handler, ptr) }
}
}
#[cfg(feature = "smartptr")]
pub struct FlexTimers;
#[cfg(feature = "smartptr")]
impl TimerConfig for FlexTimers {
type Storage = crate::FlexVirtual<Instant, nexus_smartptr::B256>;
fn wrap(handler: impl Handler<Instant> + 'static) -> Self::Storage {
let ptr: *const dyn Handler<Instant> = &handler;
unsafe { nexus_smartptr::Flex::new_raw(handler, ptr) }
}
}
pub struct TimerInstaller<
S: 'static = Box<dyn Handler<Instant>>,
Store: SlabStore<Item = WheelEntry<S>> = nexus_timer::store::UnboundedSlab<WheelEntry<S>>,
> {
wheel: nexus_timer::TimerWheel<S, Store>,
}
impl<S: 'static, Store: SlabStore<Item = WheelEntry<S>>> TimerInstaller<S, Store> {
pub fn new(wheel: nexus_timer::TimerWheel<S, Store>) -> Self {
TimerInstaller { wheel }
}
}
impl<S, Store> Installer for TimerInstaller<S, Store>
where
S: Send + 'static,
Store: SlabStore<Item = WheelEntry<S>> + 'static,
{
type Poller = TimerPoller<S, Store>;
fn install(self, world: &mut WorldBuilder) -> TimerPoller<S, Store> {
let wheel_id = world.register(self.wheel);
TimerPoller {
wheel_id,
buf: Vec::new(),
_marker: PhantomData,
}
}
}
pub type BoundedTimerInstaller<S = Box<dyn Handler<Instant>>> =
TimerInstaller<S, nexus_timer::store::BoundedSlab<WheelEntry<S>>>;
pub struct TimerPoller<
S = Box<dyn Handler<Instant>>,
Store = nexus_timer::store::UnboundedSlab<WheelEntry<S>>,
> {
wheel_id: ResourceId,
buf: Vec<S>,
_marker: PhantomData<fn() -> Store>,
}
pub type BoundedTimerPoller<S = Box<dyn Handler<Instant>>> =
TimerPoller<S, nexus_timer::store::BoundedSlab<WheelEntry<S>>>;
impl<S, Store> std::fmt::Debug for TimerPoller<S, Store> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TimerPoller")
.field("wheel_id", &self.wheel_id)
.field("buf_len", &self.buf.len())
.finish()
}
}
impl<S, Store> TimerPoller<S, Store>
where
S: DerefMut + Send + 'static,
S::Target: Handler<Instant>,
Store: SlabStore<Item = WheelEntry<S>> + 'static,
{
pub fn poll(&mut self, world: &mut World, now: Instant) -> usize {
let wheel = unsafe { world.get_mut::<nexus_timer::TimerWheel<S, Store>>(self.wheel_id) };
wheel.poll(now, &mut self.buf);
let fired = self.buf.len();
for mut handler in self.buf.drain(..) {
world.next_sequence();
handler.deref_mut().run(world, now);
}
fired
}
pub fn next_deadline(&self, world: &World) -> Option<Instant> {
let wheel = unsafe { world.get::<nexus_timer::TimerWheel<S, Store>>(self.wheel_id) };
wheel.next_deadline()
}
pub fn len(&self, world: &World) -> usize {
let wheel = unsafe { world.get::<nexus_timer::TimerWheel<S, Store>>(self.wheel_id) };
wheel.len()
}
pub fn is_empty(&self, world: &World) -> bool {
let wheel = unsafe { world.get::<nexus_timer::TimerWheel<S, Store>>(self.wheel_id) };
wheel.is_empty()
}
}
pub struct Periodic<
H,
C: TimerConfig = BoxedTimers,
Store: SlabStore<Item = WheelEntry<C::Storage>> = nexus_timer::store::UnboundedSlab<
WheelEntry<Box<dyn Handler<Instant>>>,
>,
> {
inner: Option<H>,
interval: Duration,
#[allow(clippy::type_complexity)]
_marker: PhantomData<(fn() -> C, fn() -> Store)>,
}
impl<H, C: TimerConfig, Store: SlabStore<Item = WheelEntry<C::Storage>>> std::fmt::Debug
for Periodic<H, C, Store>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Periodic")
.field("has_inner", &self.inner.is_some())
.field("interval", &self.interval)
.finish()
}
}
impl<H, C: TimerConfig, Store: SlabStore<Item = WheelEntry<C::Storage>>> Periodic<H, C, Store> {
pub fn new(handler: H, interval: Duration) -> Self {
Periodic {
inner: Some(handler),
interval,
_marker: PhantomData,
}
}
pub fn interval(&self) -> Duration {
self.interval
}
pub fn into_inner(self) -> Option<H> {
self.inner
}
}
impl<H, C, Store> Handler<Instant> for Periodic<H, C, Store>
where
H: Handler<Instant> + 'static,
C: TimerConfig,
Store: SlabStore<Item = WheelEntry<C::Storage>> + 'static,
{
fn run(&mut self, world: &mut World, now: Instant) {
let mut inner = self
.inner
.take()
.expect("periodic handler already consumed");
inner.run(world, now);
let next: Periodic<H, C, Store> = Periodic {
inner: Some(inner),
interval: self.interval,
_marker: PhantomData,
};
let deadline = now + self.interval;
let wheel = world.resource_mut::<nexus_timer::TimerWheel<C::Storage, Store>>();
wheel.schedule_forget(deadline, C::wrap(next));
}
fn name(&self) -> &'static str {
self.inner
.as_ref()
.map_or("<periodic:consumed>", |inner| inner.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{IntoCallback, IntoHandler, RegistryRef, ResMut, WorldBuilder};
use std::time::Duration;
#[test]
fn install_registers_wheel() {
let mut builder = WorldBuilder::new();
let _handle: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let world = builder.build();
assert!(world.contains::<TimerWheel>());
}
#[test]
fn poll_empty_returns_zero() {
let mut builder = WorldBuilder::new();
let mut handle: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
assert_eq!(handle.poll(&mut world, Instant::now()), 0);
}
#[test]
fn one_shot_fires() {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let mut timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
fn on_timeout(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let handler = on_timeout.into_handler(world.registry());
let now = Instant::now();
world
.resource_mut::<TimerWheel>()
.schedule_forget(now, Box::new(handler));
assert!(!*world.resource::<bool>());
let fired = timer.poll(&mut world, now);
assert_eq!(fired, 1);
assert!(*world.resource::<bool>());
}
#[test]
fn expired_timer_fires_accumulated() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
fn inc(mut counter: ResMut<u64>, _now: Instant) {
*counter += 1;
}
let now = Instant::now();
let past = now - Duration::from_millis(10);
for _ in 0..3 {
let h = inc.into_handler(world.registry());
world
.resource_mut::<TimerWheel>()
.schedule_forget(past, Box::new(h));
}
let fired = timer.poll(&mut world, now);
assert_eq!(fired, 3);
assert_eq!(*world.resource::<u64>(), 3);
}
#[test]
fn future_timer_does_not_fire() {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let mut timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
fn on_timeout(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let now = Instant::now();
let future = now + Duration::from_secs(60);
let h = on_timeout.into_handler(world.registry());
world
.resource_mut::<TimerWheel>()
.schedule_forget(future, Box::new(h));
let fired = timer.poll(&mut world, now);
assert_eq!(fired, 0);
assert!(!*world.resource::<bool>());
}
#[test]
fn next_deadline_reports_earliest() {
let mut builder = WorldBuilder::new();
let timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
let now = Instant::now();
let early = now + Duration::from_millis(50);
let late = now + Duration::from_millis(200);
fn noop(_now: Instant) {}
let h1 = noop.into_handler(world.registry());
let h2 = noop.into_handler(world.registry());
world
.resource_mut::<TimerWheel>()
.schedule_forget(late, Box::new(h1));
world
.resource_mut::<TimerWheel>()
.schedule_forget(early, Box::new(h2));
let deadline = timer.next_deadline(&world);
assert!(deadline.is_some());
assert!(deadline.unwrap() <= early + Duration::from_millis(1));
}
#[test]
fn len_tracks_active_timers() {
let mut builder = WorldBuilder::new();
let mut timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
assert_eq!(timer.len(&world), 0);
assert!(timer.is_empty(&world));
let now = Instant::now();
fn noop(_now: Instant) {}
let h = noop.into_handler(world.registry());
world
.resource_mut::<TimerWheel>()
.schedule_forget(now, Box::new(h));
assert_eq!(timer.len(&world), 1);
assert!(!timer.is_empty(&world));
timer.poll(&mut world, now);
assert_eq!(timer.len(&world), 0);
}
#[test]
fn self_rescheduling_callback() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
fn periodic(
ctx: &mut Duration,
mut counter: ResMut<u64>,
mut wheel: ResMut<TimerWheel>,
reg: RegistryRef,
now: Instant,
) {
*counter += 1;
if *counter < 3 {
let interval = *ctx;
let next = periodic.into_callback(interval, ®);
wheel.schedule_forget(now + interval, Box::new(next));
}
}
let now = Instant::now();
let interval = Duration::from_millis(1);
let cb = periodic.into_callback(interval, world.registry());
world
.resource_mut::<TimerWheel>()
.schedule_forget(now, Box::new(cb));
timer.poll(&mut world, now);
assert_eq!(*world.resource::<u64>(), 1);
timer.poll(&mut world, now + interval);
assert_eq!(*world.resource::<u64>(), 2);
timer.poll(&mut world, now + interval * 2);
assert_eq!(*world.resource::<u64>(), 3);
assert!(timer.is_empty(&world));
}
#[test]
fn cancellable_timer() {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let mut timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let now = Instant::now();
let deadline = now + Duration::from_millis(100);
let h = on_fire.into_handler(world.registry());
let cancel_handle = world
.resource_mut::<TimerWheel>()
.schedule(deadline, Box::new(h));
let cancelled = world.resource_mut::<TimerWheel>().cancel(cancel_handle);
assert!(cancelled.is_some());
let fired = timer.poll(&mut world, deadline);
assert_eq!(fired, 0);
assert!(!*world.resource::<bool>());
}
#[test]
fn poll_advances_sequence() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
fn inc(mut counter: ResMut<u64>, _now: Instant) {
*counter += 1;
}
let now = Instant::now();
let h1 = inc.into_handler(world.registry());
let h2 = inc.into_handler(world.registry());
world
.resource_mut::<TimerWheel>()
.schedule_forget(now, Box::new(h1));
world
.resource_mut::<TimerWheel>()
.schedule_forget(now, Box::new(h2));
let seq_before = world.current_sequence();
timer.poll(&mut world, now);
assert_eq!(world.current_sequence().0, seq_before.0 + 2);
}
#[test]
fn reschedule_timer() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
fn on_fire(mut counter: ResMut<u64>, _now: Instant) {
*counter += 1;
}
let now = Instant::now();
let h = on_fire.into_handler(world.registry());
let handle = world
.resource_mut::<TimerWheel>()
.schedule(now + Duration::from_millis(100), Box::new(h));
let handle = world
.resource_mut::<TimerWheel>()
.reschedule(handle, now + Duration::from_millis(50));
let fired = timer.poll(&mut world, now + Duration::from_millis(40));
assert_eq!(fired, 0);
assert_eq!(*world.resource::<u64>(), 0);
let fired = timer.poll(&mut world, now + Duration::from_millis(55));
assert_eq!(fired, 1);
assert_eq!(*world.resource::<u64>(), 1);
world.resource_mut::<TimerWheel>().cancel(handle);
}
#[test]
fn periodic_fires_repeatedly() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
fn tick(mut counter: ResMut<u64>, _now: Instant) {
*counter += 1;
}
let now = Instant::now();
let interval = Duration::from_millis(10);
let handler = tick.into_handler(world.registry());
let periodic: Periodic<_> = Periodic::new(handler, interval);
world
.resource_mut::<TimerWheel>()
.schedule_forget(now, Box::new(periodic));
timer.poll(&mut world, now);
assert_eq!(*world.resource::<u64>(), 1);
timer.poll(&mut world, now + interval);
assert_eq!(*world.resource::<u64>(), 2);
timer.poll(&mut world, now + interval * 2);
assert_eq!(*world.resource::<u64>(), 3);
assert!(!timer.is_empty(&world));
}
#[test]
fn periodic_cancel_drops_inner() {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let mut timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let mut world = builder.build();
fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let now = Instant::now();
let handler = on_fire.into_handler(world.registry());
let periodic: Periodic<_> = Periodic::new(handler, Duration::from_millis(50));
let handle = world
.resource_mut::<TimerWheel>()
.schedule(now + Duration::from_millis(50), Box::new(periodic));
let cancelled = world.resource_mut::<TimerWheel>().cancel(handle);
assert!(cancelled.is_some());
let fired = timer.poll(&mut world, now + Duration::from_millis(100));
assert_eq!(fired, 0);
assert!(!*world.resource::<bool>());
}
#[test]
fn periodic_into_inner_recovers_handler() {
let mut builder = WorldBuilder::new();
let _timer: TimerPoller =
builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
let world = builder.build();
fn noop(_now: Instant) {}
let handler = noop.into_handler(world.registry());
let periodic: Periodic<_> = Periodic::new(handler, Duration::from_millis(10));
assert!(periodic.into_inner().is_some());
}
#[test]
fn bounded_install_registers_wheel() {
let mut builder = WorldBuilder::new();
let wheel = BoundedTimerWheel::bounded(64, Instant::now());
let _handle: BoundedTimerPoller = builder.install_driver(TimerInstaller::new(wheel));
let world = builder.build();
assert!(world.contains::<BoundedTimerWheel>());
}
#[test]
fn bounded_one_shot_fires() {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let wheel = BoundedTimerWheel::bounded(64, Instant::now());
let mut timer: BoundedTimerPoller = builder.install_driver(TimerInstaller::new(wheel));
let mut world = builder.build();
fn on_timeout(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let handler = on_timeout.into_handler(world.registry());
let now = Instant::now();
world
.resource_mut::<BoundedTimerWheel>()
.try_schedule_forget(now, Box::new(handler))
.expect("should not be full");
assert!(!*world.resource::<bool>());
let fired = timer.poll(&mut world, now);
assert_eq!(fired, 1);
assert!(*world.resource::<bool>());
}
#[test]
fn bounded_cancel_and_query() {
let mut builder = WorldBuilder::new();
let wheel = BoundedTimerWheel::bounded(64, Instant::now());
let mut timer: BoundedTimerPoller = builder.install_driver(TimerInstaller::new(wheel));
let mut world = builder.build();
fn noop(_now: Instant) {}
let now = Instant::now();
let h = noop.into_handler(world.registry());
let handle = world
.resource_mut::<BoundedTimerWheel>()
.try_schedule(now + Duration::from_millis(100), Box::new(h))
.expect("should not be full");
assert_eq!(timer.len(&world), 1);
assert!(!timer.is_empty(&world));
assert!(timer.next_deadline(&world).is_some());
let cancelled = world.resource_mut::<BoundedTimerWheel>().cancel(handle);
assert!(cancelled.is_some());
let fired = timer.poll(&mut world, now + Duration::from_millis(200));
assert_eq!(fired, 0);
assert_eq!(timer.len(&world), 0);
}
#[test]
fn bounded_full_returns_error() {
let mut builder = WorldBuilder::new();
let wheel = BoundedTimerWheel::bounded(1, Instant::now());
let _timer: BoundedTimerPoller = builder.install_driver(TimerInstaller::new(wheel));
let mut world = builder.build();
fn noop(_now: Instant) {}
let now = Instant::now();
let h1 = noop.into_handler(world.registry());
world
.resource_mut::<BoundedTimerWheel>()
.try_schedule_forget(now + Duration::from_secs(60), Box::new(h1))
.expect("first should succeed");
let h2 = noop.into_handler(world.registry());
let result = world
.resource_mut::<BoundedTimerWheel>()
.try_schedule_forget(now + Duration::from_secs(60), Box::new(h2));
assert!(result.is_err());
}
fn assert_unbounded_fires(installer: TimerInstaller) {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let mut timer: TimerPoller = builder.install_driver(installer);
let mut world = builder.build();
fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let now = Instant::now();
let h = on_fire.into_handler(world.registry());
world
.resource_mut::<TimerWheel>()
.schedule_forget(now, Box::new(h));
let fired = timer.poll(&mut world, now);
assert_eq!(fired, 1);
assert!(*world.resource::<bool>());
}
fn assert_bounded_fires(installer: BoundedTimerInstaller) {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let mut timer: BoundedTimerPoller = builder.install_driver(installer);
let mut world = builder.build();
fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let now = Instant::now();
let h = on_fire.into_handler(world.registry());
world
.resource_mut::<BoundedTimerWheel>()
.try_schedule_forget(now, Box::new(h))
.expect("should not be full");
let fired = timer.poll(&mut world, now);
assert_eq!(fired, 1);
assert!(*world.resource::<bool>());
}
#[test]
fn cfg_unbounded_default() {
let now = Instant::now();
assert_unbounded_fires(TimerInstaller::new(Wheel::unbounded(64, now)));
}
#[test]
fn cfg_unbounded_chunk_capacity() {
let now = Instant::now();
assert_unbounded_fires(TimerInstaller::new(Wheel::unbounded(256, now)));
}
#[test]
fn cfg_unbounded_tick_duration() {
let now = Instant::now();
assert_unbounded_fires(TimerInstaller::new(
WheelBuilder::default()
.tick_duration(Duration::from_micros(100))
.unbounded(64)
.build(now),
));
}
#[test]
fn cfg_unbounded_slots_per_level() {
let now = Instant::now();
assert_unbounded_fires(TimerInstaller::new(
WheelBuilder::default()
.slots_per_level(32)
.unbounded(64)
.build(now),
));
}
#[test]
fn cfg_unbounded_clk_shift() {
let now = Instant::now();
assert_unbounded_fires(TimerInstaller::new(
WheelBuilder::default()
.clk_shift(2)
.unbounded(64)
.build(now),
));
}
#[test]
fn cfg_unbounded_num_levels() {
let now = Instant::now();
assert_unbounded_fires(TimerInstaller::new(
WheelBuilder::default()
.num_levels(4)
.unbounded(64)
.build(now),
));
}
#[test]
fn cfg_unbounded_full_chain() {
let now = Instant::now();
assert_unbounded_fires(TimerInstaller::new(
WheelBuilder::default()
.tick_duration(Duration::from_micros(500))
.slots_per_level(32)
.clk_shift(2)
.num_levels(5)
.unbounded(128)
.build(now),
));
}
#[test]
fn cfg_bounded_default() {
let now = Instant::now();
assert_bounded_fires(TimerInstaller::new(
WheelBuilder::default().bounded(64).build(now),
));
}
#[test]
fn cfg_bounded_tick_duration() {
let now = Instant::now();
assert_bounded_fires(TimerInstaller::new(
WheelBuilder::default()
.tick_duration(Duration::from_micros(100))
.bounded(64)
.build(now),
));
}
#[test]
fn cfg_bounded_slots_per_level() {
let now = Instant::now();
assert_bounded_fires(TimerInstaller::new(
WheelBuilder::default()
.slots_per_level(32)
.bounded(64)
.build(now),
));
}
#[test]
fn cfg_bounded_clk_shift() {
let now = Instant::now();
assert_bounded_fires(TimerInstaller::new(
WheelBuilder::default().clk_shift(2).bounded(64).build(now),
));
}
#[test]
fn cfg_bounded_num_levels() {
let now = Instant::now();
assert_bounded_fires(TimerInstaller::new(
WheelBuilder::default().num_levels(4).bounded(64).build(now),
));
}
#[test]
fn cfg_bounded_full_chain() {
let now = Instant::now();
assert_bounded_fires(TimerInstaller::new(
WheelBuilder::default()
.tick_duration(Duration::from_micros(500))
.slots_per_level(32)
.clk_shift(2)
.num_levels(5)
.bounded(128)
.build(now),
));
}
#[cfg(feature = "smartptr")]
mod storage_tests {
use super::*;
#[test]
fn unbounded_inline_storage() {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let wheel = InlineTimerWheel::unbounded(64, Instant::now());
let mut timer = builder.install_driver(TimerInstaller::new(wheel));
let mut world = builder.build();
fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let now = Instant::now();
let h = on_fire.into_handler(world.registry());
let ptr: *const dyn Handler<Instant> = &h;
let storage = unsafe { nexus_smartptr::Flat::new_raw(h, ptr) };
world
.resource_mut::<InlineTimerWheel>()
.schedule_forget(now, storage);
let fired = timer.poll(&mut world, now);
assert_eq!(fired, 1);
assert!(*world.resource::<bool>());
}
#[test]
fn unbounded_flex_storage() {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let wheel = FlexTimerWheel::unbounded(64, Instant::now());
let mut timer = builder.install_driver(TimerInstaller::new(wheel));
let mut world = builder.build();
fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let now = Instant::now();
let h = on_fire.into_handler(world.registry());
let ptr: *const dyn Handler<Instant> = &h;
let storage = unsafe { nexus_smartptr::Flex::new_raw(h, ptr) };
world
.resource_mut::<FlexTimerWheel>()
.schedule_forget(now, storage);
let fired = timer.poll(&mut world, now);
assert_eq!(fired, 1);
assert!(*world.resource::<bool>());
}
#[test]
fn bounded_inline_storage() {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let wheel = BoundedInlineTimerWheel::bounded(64, Instant::now());
let mut timer = builder.install_driver(TimerInstaller::new(wheel));
let mut world = builder.build();
fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let now = Instant::now();
let h = on_fire.into_handler(world.registry());
let ptr: *const dyn Handler<Instant> = &h;
let storage = unsafe { nexus_smartptr::Flat::new_raw(h, ptr) };
world
.resource_mut::<BoundedInlineTimerWheel>()
.try_schedule_forget(now, storage)
.expect("should not be full");
let fired = timer.poll(&mut world, now);
assert_eq!(fired, 1);
assert!(*world.resource::<bool>());
}
#[test]
fn bounded_flex_storage() {
let mut builder = WorldBuilder::new();
builder.register::<bool>(false);
let wheel = BoundedFlexTimerWheel::bounded(64, Instant::now());
let mut timer = builder.install_driver(TimerInstaller::new(wheel));
let mut world = builder.build();
fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
*flag = true;
}
let now = Instant::now();
let h = on_fire.into_handler(world.registry());
let ptr: *const dyn Handler<Instant> = &h;
let storage = unsafe { nexus_smartptr::Flex::new_raw(h, ptr) };
world
.resource_mut::<BoundedFlexTimerWheel>()
.try_schedule_forget(now, storage)
.expect("should not be full");
let fired = timer.poll(&mut world, now);
assert_eq!(fired, 1);
assert!(*world.resource::<bool>());
}
#[test]
fn periodic_inline_fires_repeatedly() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let wheel = InlineTimerWheel::unbounded(64, Instant::now());
let mut timer = builder.install_driver(TimerInstaller::new(wheel));
let mut world = builder.build();
fn tick(mut counter: ResMut<u64>, _now: Instant) {
*counter += 1;
}
let now = Instant::now();
let interval = Duration::from_millis(10);
let handler = tick.into_handler(world.registry());
let periodic: Periodic<_, InlineTimers, nexus_timer::store::UnboundedSlab<_>> =
Periodic::new(handler, interval);
world
.resource_mut::<InlineTimerWheel>()
.schedule_forget(now, InlineTimers::wrap(periodic));
timer.poll(&mut world, now);
assert_eq!(*world.resource::<u64>(), 1);
timer.poll(&mut world, now + interval);
assert_eq!(*world.resource::<u64>(), 2);
timer.poll(&mut world, now + interval * 2);
assert_eq!(*world.resource::<u64>(), 3);
assert!(!timer.is_empty(&world));
}
#[test]
fn periodic_flex_fires_repeatedly() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let wheel = FlexTimerWheel::unbounded(64, Instant::now());
let mut timer = builder.install_driver(TimerInstaller::new(wheel));
let mut world = builder.build();
fn tick(mut counter: ResMut<u64>, _now: Instant) {
*counter += 1;
}
let now = Instant::now();
let interval = Duration::from_millis(10);
let handler = tick.into_handler(world.registry());
let periodic: Periodic<_, FlexTimers, nexus_timer::store::UnboundedSlab<_>> =
Periodic::new(handler, interval);
world
.resource_mut::<FlexTimerWheel>()
.schedule_forget(now, FlexTimers::wrap(periodic));
timer.poll(&mut world, now);
assert_eq!(*world.resource::<u64>(), 1);
timer.poll(&mut world, now + interval);
assert_eq!(*world.resource::<u64>(), 2);
timer.poll(&mut world, now + interval * 2);
assert_eq!(*world.resource::<u64>(), 3);
assert!(!timer.is_empty(&world));
}
}
}