1use std::marker::PhantomData;
68use std::ops::DerefMut;
69use std::time::{Duration, Instant};
70
71use nexus_timer::store::SlabStore;
72
73pub use nexus_timer::{
75 BoundedWheel, BoundedWheelBuilder, Full, TimerHandle, UnboundedWheelBuilder, Wheel,
76 WheelBuilder, WheelEntry,
77};
78
79impl<T: Send + 'static, S: nexus_timer::store::SlabStore<Item = WheelEntry<T>> + 'static>
84 crate::world::Resource for nexus_timer::TimerWheel<T, S>
85{
86}
87
88use crate::Handler;
89use crate::driver::Installer;
90use crate::world::{ResourceId, World, WorldBuilder};
91
92pub type TimerWheel = Wheel<Box<dyn Handler<Instant>>>;
97
98pub type BoundedTimerWheel = BoundedWheel<Box<dyn Handler<Instant>>>;
102
103#[cfg(feature = "smartptr")]
111pub type InlineTimerWheel = Wheel<crate::FlatVirtual<Instant, nexus_smartptr::B256>>;
112
113#[cfg(feature = "smartptr")]
115pub type FlexTimerWheel = Wheel<crate::FlexVirtual<Instant, nexus_smartptr::B256>>;
116
117#[cfg(feature = "smartptr")]
119pub type BoundedInlineTimerWheel = BoundedWheel<crate::FlatVirtual<Instant, nexus_smartptr::B256>>;
120
121#[cfg(feature = "smartptr")]
123pub type BoundedFlexTimerWheel = BoundedWheel<crate::FlexVirtual<Instant, nexus_smartptr::B256>>;
124
125pub trait TimerConfig: Send + 'static {
150 type Storage: DerefMut<Target = dyn Handler<Instant>> + Send + 'static;
152
153 fn wrap(handler: impl Handler<Instant> + 'static) -> Self::Storage;
155}
156
157pub struct BoxedTimers;
162
163impl TimerConfig for BoxedTimers {
164 type Storage = Box<dyn Handler<Instant>>;
165
166 fn wrap(handler: impl Handler<Instant> + 'static) -> Self::Storage {
167 Box::new(handler)
168 }
169}
170
171#[cfg(feature = "smartptr")]
176pub struct InlineTimers;
177
178#[cfg(feature = "smartptr")]
179impl TimerConfig for InlineTimers {
180 type Storage = crate::FlatVirtual<Instant, nexus_smartptr::B256>;
181
182 fn wrap(handler: impl Handler<Instant> + 'static) -> Self::Storage {
183 let ptr: *const dyn Handler<Instant> = &handler;
184 unsafe { nexus_smartptr::Flat::new_raw(handler, ptr) }
186 }
187}
188
189#[cfg(feature = "smartptr")]
194pub struct FlexTimers;
195
196#[cfg(feature = "smartptr")]
197impl TimerConfig for FlexTimers {
198 type Storage = crate::FlexVirtual<Instant, nexus_smartptr::B256>;
199
200 fn wrap(handler: impl Handler<Instant> + 'static) -> Self::Storage {
201 let ptr: *const dyn Handler<Instant> = &handler;
202 unsafe { nexus_smartptr::Flex::new_raw(handler, ptr) }
204 }
205}
206
207pub struct TimerInstaller<
235 S: 'static = Box<dyn Handler<Instant>>,
236 Store: SlabStore<Item = WheelEntry<S>> = nexus_timer::store::UnboundedSlab<WheelEntry<S>>,
237> {
238 wheel: nexus_timer::TimerWheel<S, Store>,
239}
240
241impl<S: 'static, Store: SlabStore<Item = WheelEntry<S>>> TimerInstaller<S, Store> {
242 pub fn new(wheel: nexus_timer::TimerWheel<S, Store>) -> Self {
246 TimerInstaller { wheel }
247 }
248}
249
250impl<S, Store> Installer for TimerInstaller<S, Store>
251where
252 S: Send + 'static,
253 Store: SlabStore<Item = WheelEntry<S>> + 'static,
254{
255 type Poller = TimerPoller<S, Store>;
256
257 fn install(self, world: &mut WorldBuilder) -> TimerPoller<S, Store> {
258 let wheel_id = world.register(self.wheel);
259 TimerPoller {
260 wheel_id,
261 buf: Vec::new(),
262 _marker: PhantomData,
263 }
264 }
265}
266
267pub type BoundedTimerInstaller<S = Box<dyn Handler<Instant>>> =
269 TimerInstaller<S, nexus_timer::store::BoundedSlab<WheelEntry<S>>>;
270
271pub struct TimerPoller<
279 S = Box<dyn Handler<Instant>>,
280 Store = nexus_timer::store::UnboundedSlab<WheelEntry<S>>,
281> {
282 wheel_id: ResourceId,
283 buf: Vec<S>,
284 _marker: PhantomData<fn() -> Store>,
285}
286
287pub type BoundedTimerPoller<S = Box<dyn Handler<Instant>>> =
289 TimerPoller<S, nexus_timer::store::BoundedSlab<WheelEntry<S>>>;
290
291impl<S, Store> std::fmt::Debug for TimerPoller<S, Store> {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 f.debug_struct("TimerPoller")
294 .field("wheel_id", &self.wheel_id)
295 .field("buf_len", &self.buf.len())
296 .finish()
297 }
298}
299
300impl<S, Store> TimerPoller<S, Store>
301where
302 S: DerefMut + Send + 'static,
303 S::Target: Handler<Instant>,
304 Store: SlabStore<Item = WheelEntry<S>> + 'static,
305{
306 pub fn poll(&mut self, world: &mut World, now: Instant) -> usize {
313 let wheel = unsafe { world.get_mut::<nexus_timer::TimerWheel<S, Store>>(self.wheel_id) };
316 wheel.poll(now, &mut self.buf);
317 let fired = self.buf.len();
318
319 for mut handler in self.buf.drain(..) {
320 world.next_sequence();
321 handler.deref_mut().run(world, now);
322 }
323
324 fired
325 }
326
327 pub fn next_deadline(&self, world: &World) -> Option<Instant> {
329 let wheel = unsafe { world.get::<nexus_timer::TimerWheel<S, Store>>(self.wheel_id) };
331 wheel.next_deadline()
332 }
333
334 pub fn len(&self, world: &World) -> usize {
336 let wheel = unsafe { world.get::<nexus_timer::TimerWheel<S, Store>>(self.wheel_id) };
338 wheel.len()
339 }
340
341 pub fn is_empty(&self, world: &World) -> bool {
343 let wheel = unsafe { world.get::<nexus_timer::TimerWheel<S, Store>>(self.wheel_id) };
345 wheel.is_empty()
346 }
347}
348
349pub struct Periodic<
394 H,
395 C: TimerConfig = BoxedTimers,
396 Store: SlabStore<Item = WheelEntry<C::Storage>> = nexus_timer::store::UnboundedSlab<
397 WheelEntry<Box<dyn Handler<Instant>>>,
398 >,
399> {
400 inner: Option<H>,
401 interval: Duration,
402 #[allow(clippy::type_complexity)]
403 _marker: PhantomData<(fn() -> C, fn() -> Store)>,
404}
405
406impl<H, C: TimerConfig, Store: SlabStore<Item = WheelEntry<C::Storage>>> std::fmt::Debug
407 for Periodic<H, C, Store>
408{
409 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410 f.debug_struct("Periodic")
411 .field("has_inner", &self.inner.is_some())
412 .field("interval", &self.interval)
413 .finish()
414 }
415}
416
417impl<H, C: TimerConfig, Store: SlabStore<Item = WheelEntry<C::Storage>>> Periodic<H, C, Store> {
418 pub fn new(handler: H, interval: Duration) -> Self {
437 Periodic {
438 inner: Some(handler),
439 interval,
440 _marker: PhantomData,
441 }
442 }
443
444 pub fn interval(&self) -> Duration {
446 self.interval
447 }
448
449 pub fn into_inner(self) -> Option<H> {
454 self.inner
455 }
456}
457
458impl<H, C, Store> Handler<Instant> for Periodic<H, C, Store>
459where
460 H: Handler<Instant> + 'static,
461 C: TimerConfig,
462 Store: SlabStore<Item = WheelEntry<C::Storage>> + 'static,
463{
464 fn run(&mut self, world: &mut World, now: Instant) {
465 let mut inner = self
466 .inner
467 .take()
468 .expect("periodic handler already consumed");
469
470 inner.run(world, now);
472
473 let next: Periodic<H, C, Store> = Periodic {
477 inner: Some(inner),
478 interval: self.interval,
479 _marker: PhantomData,
480 };
481 let deadline = now + self.interval;
482 let wheel = world.resource_mut::<nexus_timer::TimerWheel<C::Storage, Store>>();
483 wheel.schedule_forget(deadline, C::wrap(next));
484 }
485
486 fn name(&self) -> &'static str {
487 self.inner
488 .as_ref()
489 .map_or("<periodic:consumed>", |inner| inner.name())
490 }
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496 use crate::{IntoCallback, IntoHandler, RegistryRef, ResMut, WorldBuilder};
497 use std::time::Duration;
498
499 #[test]
500 fn install_registers_wheel() {
501 let mut builder = WorldBuilder::new();
502 let _handle: TimerPoller =
503 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
504 let world = builder.build();
505 assert!(world.contains::<TimerWheel>());
506 }
507
508 #[test]
509 fn poll_empty_returns_zero() {
510 let mut builder = WorldBuilder::new();
511 let mut handle: TimerPoller =
512 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
513 let mut world = builder.build();
514 assert_eq!(handle.poll(&mut world, Instant::now()), 0);
515 }
516
517 #[test]
518 fn one_shot_fires() {
519 let mut builder = WorldBuilder::new();
520 builder.register::<bool>(false);
521 let mut timer: TimerPoller =
522 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
523 let mut world = builder.build();
524
525 fn on_timeout(mut flag: ResMut<bool>, _now: Instant) {
526 *flag = true;
527 }
528
529 let handler = on_timeout.into_handler(world.registry());
530 let now = Instant::now();
531 world
532 .resource_mut::<TimerWheel>()
533 .schedule_forget(now, Box::new(handler));
534
535 assert!(!*world.resource::<bool>());
536 let fired = timer.poll(&mut world, now);
537 assert_eq!(fired, 1);
538 assert!(*world.resource::<bool>());
539 }
540
541 #[test]
542 fn expired_timer_fires_accumulated() {
543 let mut builder = WorldBuilder::new();
544 builder.register::<u64>(0);
545 let mut timer: TimerPoller =
546 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
547 let mut world = builder.build();
548
549 fn inc(mut counter: ResMut<u64>, _now: Instant) {
550 *counter += 1;
551 }
552
553 let now = Instant::now();
554 let past = now - Duration::from_millis(10);
555
556 for _ in 0..3 {
557 let h = inc.into_handler(world.registry());
558 world
559 .resource_mut::<TimerWheel>()
560 .schedule_forget(past, Box::new(h));
561 }
562
563 let fired = timer.poll(&mut world, now);
564 assert_eq!(fired, 3);
565 assert_eq!(*world.resource::<u64>(), 3);
566 }
567
568 #[test]
569 fn future_timer_does_not_fire() {
570 let mut builder = WorldBuilder::new();
571 builder.register::<bool>(false);
572 let mut timer: TimerPoller =
573 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
574 let mut world = builder.build();
575
576 fn on_timeout(mut flag: ResMut<bool>, _now: Instant) {
577 *flag = true;
578 }
579
580 let now = Instant::now();
581 let future = now + Duration::from_secs(60);
582 let h = on_timeout.into_handler(world.registry());
583 world
584 .resource_mut::<TimerWheel>()
585 .schedule_forget(future, Box::new(h));
586
587 let fired = timer.poll(&mut world, now);
588 assert_eq!(fired, 0);
589 assert!(!*world.resource::<bool>());
590 }
591
592 #[test]
593 fn next_deadline_reports_earliest() {
594 let mut builder = WorldBuilder::new();
595 let timer: TimerPoller =
596 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
597 let mut world = builder.build();
598
599 let now = Instant::now();
600 let early = now + Duration::from_millis(50);
601 let late = now + Duration::from_millis(200);
602
603 fn noop(_now: Instant) {}
604
605 let h1 = noop.into_handler(world.registry());
606 let h2 = noop.into_handler(world.registry());
607 world
608 .resource_mut::<TimerWheel>()
609 .schedule_forget(late, Box::new(h1));
610 world
611 .resource_mut::<TimerWheel>()
612 .schedule_forget(early, Box::new(h2));
613
614 let deadline = timer.next_deadline(&world);
615 assert!(deadline.is_some());
616 assert!(deadline.unwrap() <= early + Duration::from_millis(1));
618 }
619
620 #[test]
621 fn len_tracks_active_timers() {
622 let mut builder = WorldBuilder::new();
623 let mut timer: TimerPoller =
624 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
625 let mut world = builder.build();
626
627 assert_eq!(timer.len(&world), 0);
628 assert!(timer.is_empty(&world));
629
630 let now = Instant::now();
631 fn noop(_now: Instant) {}
632
633 let h = noop.into_handler(world.registry());
634 world
635 .resource_mut::<TimerWheel>()
636 .schedule_forget(now, Box::new(h));
637
638 assert_eq!(timer.len(&world), 1);
639 assert!(!timer.is_empty(&world));
640
641 timer.poll(&mut world, now);
642 assert_eq!(timer.len(&world), 0);
643 }
644
645 #[test]
646 fn self_rescheduling_callback() {
647 let mut builder = WorldBuilder::new();
648 builder.register::<u64>(0);
649 let mut timer: TimerPoller =
650 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
651 let mut world = builder.build();
652
653 fn periodic(
654 ctx: &mut Duration,
655 mut counter: ResMut<u64>,
656 mut wheel: ResMut<TimerWheel>,
657 reg: RegistryRef,
658 now: Instant,
659 ) {
660 *counter += 1;
661 if *counter < 3 {
662 let interval = *ctx;
663 let next = periodic.into_callback(interval, ®);
664 wheel.schedule_forget(now + interval, Box::new(next));
665 }
666 }
667
668 let now = Instant::now();
669 let interval = Duration::from_millis(1);
670 let cb = periodic.into_callback(interval, world.registry());
671 world
672 .resource_mut::<TimerWheel>()
673 .schedule_forget(now, Box::new(cb));
674
675 timer.poll(&mut world, now);
677 assert_eq!(*world.resource::<u64>(), 1);
678
679 timer.poll(&mut world, now + interval);
681 assert_eq!(*world.resource::<u64>(), 2);
682
683 timer.poll(&mut world, now + interval * 2);
685 assert_eq!(*world.resource::<u64>(), 3);
686
687 assert!(timer.is_empty(&world));
689 }
690
691 #[test]
692 fn cancellable_timer() {
693 let mut builder = WorldBuilder::new();
694 builder.register::<bool>(false);
695 let mut timer: TimerPoller =
696 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
697 let mut world = builder.build();
698
699 fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
700 *flag = true;
701 }
702
703 let now = Instant::now();
704 let deadline = now + Duration::from_millis(100);
705 let h = on_fire.into_handler(world.registry());
706 let cancel_handle = world
707 .resource_mut::<TimerWheel>()
708 .schedule(deadline, Box::new(h));
709
710 let cancelled = world.resource_mut::<TimerWheel>().cancel(cancel_handle);
712 assert!(cancelled.is_some());
713
714 let fired = timer.poll(&mut world, deadline);
716 assert_eq!(fired, 0);
717 assert!(!*world.resource::<bool>());
718 }
719
720 #[test]
721 fn poll_advances_sequence() {
722 let mut builder = WorldBuilder::new();
723 builder.register::<u64>(0);
724 let mut timer: TimerPoller =
725 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
726 let mut world = builder.build();
727
728 fn inc(mut counter: ResMut<u64>, _now: Instant) {
729 *counter += 1;
730 }
731
732 let now = Instant::now();
733 let h1 = inc.into_handler(world.registry());
734 let h2 = inc.into_handler(world.registry());
735 world
736 .resource_mut::<TimerWheel>()
737 .schedule_forget(now, Box::new(h1));
738 world
739 .resource_mut::<TimerWheel>()
740 .schedule_forget(now, Box::new(h2));
741
742 let seq_before = world.current_sequence();
743 timer.poll(&mut world, now);
744 assert_eq!(world.current_sequence().0, seq_before.0 + 2);
746 }
747
748 #[test]
749 fn reschedule_timer() {
750 let mut builder = WorldBuilder::new();
751 builder.register::<u64>(0);
752 let mut timer: TimerPoller =
753 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
754 let mut world = builder.build();
755
756 fn on_fire(mut counter: ResMut<u64>, _now: Instant) {
757 *counter += 1;
758 }
759
760 let now = Instant::now();
761 let h = on_fire.into_handler(world.registry());
762 let handle = world
763 .resource_mut::<TimerWheel>()
764 .schedule(now + Duration::from_millis(100), Box::new(h));
765
766 let handle = world
768 .resource_mut::<TimerWheel>()
769 .reschedule(handle, now + Duration::from_millis(50));
770
771 let fired = timer.poll(&mut world, now + Duration::from_millis(40));
773 assert_eq!(fired, 0);
774 assert_eq!(*world.resource::<u64>(), 0);
775
776 let fired = timer.poll(&mut world, now + Duration::from_millis(55));
778 assert_eq!(fired, 1);
779 assert_eq!(*world.resource::<u64>(), 1);
780
781 world.resource_mut::<TimerWheel>().cancel(handle);
783 }
784
785 #[test]
786 fn periodic_fires_repeatedly() {
787 let mut builder = WorldBuilder::new();
788 builder.register::<u64>(0);
789 let mut timer: TimerPoller =
790 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
791 let mut world = builder.build();
792
793 fn tick(mut counter: ResMut<u64>, _now: Instant) {
794 *counter += 1;
795 }
796
797 let now = Instant::now();
798 let interval = Duration::from_millis(10);
799 let handler = tick.into_handler(world.registry());
800 let periodic: Periodic<_> = Periodic::new(handler, interval);
801 world
802 .resource_mut::<TimerWheel>()
803 .schedule_forget(now, Box::new(periodic));
804
805 timer.poll(&mut world, now);
807 assert_eq!(*world.resource::<u64>(), 1);
808
809 timer.poll(&mut world, now + interval);
811 assert_eq!(*world.resource::<u64>(), 2);
812
813 timer.poll(&mut world, now + interval * 2);
815 assert_eq!(*world.resource::<u64>(), 3);
816
817 assert!(!timer.is_empty(&world));
819 }
820
821 #[test]
822 fn periodic_cancel_drops_inner() {
823 let mut builder = WorldBuilder::new();
824 builder.register::<bool>(false);
825 let mut timer: TimerPoller =
826 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
827 let mut world = builder.build();
828
829 fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
830 *flag = true;
831 }
832
833 let now = Instant::now();
834 let handler = on_fire.into_handler(world.registry());
835 let periodic: Periodic<_> = Periodic::new(handler, Duration::from_millis(50));
836 let handle = world
837 .resource_mut::<TimerWheel>()
838 .schedule(now + Duration::from_millis(50), Box::new(periodic));
839
840 let cancelled = world.resource_mut::<TimerWheel>().cancel(handle);
842 assert!(cancelled.is_some());
843
844 let fired = timer.poll(&mut world, now + Duration::from_millis(100));
846 assert_eq!(fired, 0);
847 assert!(!*world.resource::<bool>());
848 }
849
850 #[test]
851 fn periodic_into_inner_recovers_handler() {
852 let mut builder = WorldBuilder::new();
853 let _timer: TimerPoller =
854 builder.install_driver(TimerInstaller::new(Wheel::unbounded(64, Instant::now())));
855 let world = builder.build();
856
857 fn noop(_now: Instant) {}
858
859 let handler = noop.into_handler(world.registry());
860 let periodic: Periodic<_> = Periodic::new(handler, Duration::from_millis(10));
861 assert!(periodic.into_inner().is_some());
862 }
863
864 #[test]
867 fn bounded_install_registers_wheel() {
868 let mut builder = WorldBuilder::new();
869 let wheel = BoundedTimerWheel::bounded(64, Instant::now());
870 let _handle: BoundedTimerPoller = builder.install_driver(TimerInstaller::new(wheel));
871 let world = builder.build();
872 assert!(world.contains::<BoundedTimerWheel>());
873 }
874
875 #[test]
876 fn bounded_one_shot_fires() {
877 let mut builder = WorldBuilder::new();
878 builder.register::<bool>(false);
879 let wheel = BoundedTimerWheel::bounded(64, Instant::now());
880 let mut timer: BoundedTimerPoller = builder.install_driver(TimerInstaller::new(wheel));
881 let mut world = builder.build();
882
883 fn on_timeout(mut flag: ResMut<bool>, _now: Instant) {
884 *flag = true;
885 }
886
887 let handler = on_timeout.into_handler(world.registry());
888 let now = Instant::now();
889 world
890 .resource_mut::<BoundedTimerWheel>()
891 .try_schedule_forget(now, Box::new(handler))
892 .expect("should not be full");
893
894 assert!(!*world.resource::<bool>());
895 let fired = timer.poll(&mut world, now);
896 assert_eq!(fired, 1);
897 assert!(*world.resource::<bool>());
898 }
899
900 #[test]
901 fn bounded_cancel_and_query() {
902 let mut builder = WorldBuilder::new();
903 let wheel = BoundedTimerWheel::bounded(64, Instant::now());
904 let mut timer: BoundedTimerPoller = builder.install_driver(TimerInstaller::new(wheel));
905 let mut world = builder.build();
906
907 fn noop(_now: Instant) {}
908
909 let now = Instant::now();
910 let h = noop.into_handler(world.registry());
911 let handle = world
912 .resource_mut::<BoundedTimerWheel>()
913 .try_schedule(now + Duration::from_millis(100), Box::new(h))
914 .expect("should not be full");
915
916 assert_eq!(timer.len(&world), 1);
917 assert!(!timer.is_empty(&world));
918 assert!(timer.next_deadline(&world).is_some());
919
920 let cancelled = world.resource_mut::<BoundedTimerWheel>().cancel(handle);
921 assert!(cancelled.is_some());
922
923 let fired = timer.poll(&mut world, now + Duration::from_millis(200));
924 assert_eq!(fired, 0);
925 assert_eq!(timer.len(&world), 0);
926 }
927
928 #[test]
929 fn bounded_full_returns_error() {
930 let mut builder = WorldBuilder::new();
931 let wheel = BoundedTimerWheel::bounded(1, Instant::now());
932 let _timer: BoundedTimerPoller = builder.install_driver(TimerInstaller::new(wheel));
933 let mut world = builder.build();
934
935 fn noop(_now: Instant) {}
936
937 let now = Instant::now();
938 let h1 = noop.into_handler(world.registry());
939 world
940 .resource_mut::<BoundedTimerWheel>()
941 .try_schedule_forget(now + Duration::from_secs(60), Box::new(h1))
942 .expect("first should succeed");
943
944 let h2 = noop.into_handler(world.registry());
945 let result = world
946 .resource_mut::<BoundedTimerWheel>()
947 .try_schedule_forget(now + Duration::from_secs(60), Box::new(h2));
948 assert!(result.is_err());
949 }
950
951 fn assert_unbounded_fires(installer: TimerInstaller) {
956 let mut builder = WorldBuilder::new();
957 builder.register::<bool>(false);
958 let mut timer: TimerPoller = builder.install_driver(installer);
959 let mut world = builder.build();
960
961 fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
962 *flag = true;
963 }
964
965 let now = Instant::now();
966 let h = on_fire.into_handler(world.registry());
967 world
968 .resource_mut::<TimerWheel>()
969 .schedule_forget(now, Box::new(h));
970
971 let fired = timer.poll(&mut world, now);
972 assert_eq!(fired, 1);
973 assert!(*world.resource::<bool>());
974 }
975
976 fn assert_bounded_fires(installer: BoundedTimerInstaller) {
977 let mut builder = WorldBuilder::new();
978 builder.register::<bool>(false);
979 let mut timer: BoundedTimerPoller = builder.install_driver(installer);
980 let mut world = builder.build();
981
982 fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
983 *flag = true;
984 }
985
986 let now = Instant::now();
987 let h = on_fire.into_handler(world.registry());
988 world
989 .resource_mut::<BoundedTimerWheel>()
990 .try_schedule_forget(now, Box::new(h))
991 .expect("should not be full");
992
993 let fired = timer.poll(&mut world, now);
994 assert_eq!(fired, 1);
995 assert!(*world.resource::<bool>());
996 }
997
998 #[test]
1001 fn cfg_unbounded_default() {
1002 let now = Instant::now();
1003 assert_unbounded_fires(TimerInstaller::new(Wheel::unbounded(64, now)));
1004 }
1005
1006 #[test]
1007 fn cfg_unbounded_chunk_capacity() {
1008 let now = Instant::now();
1009 assert_unbounded_fires(TimerInstaller::new(Wheel::unbounded(256, now)));
1010 }
1011
1012 #[test]
1013 fn cfg_unbounded_tick_duration() {
1014 let now = Instant::now();
1015 assert_unbounded_fires(TimerInstaller::new(
1016 WheelBuilder::default()
1017 .tick_duration(Duration::from_micros(100))
1018 .unbounded(64)
1019 .build(now),
1020 ));
1021 }
1022
1023 #[test]
1024 fn cfg_unbounded_slots_per_level() {
1025 let now = Instant::now();
1026 assert_unbounded_fires(TimerInstaller::new(
1027 WheelBuilder::default()
1028 .slots_per_level(32)
1029 .unbounded(64)
1030 .build(now),
1031 ));
1032 }
1033
1034 #[test]
1035 fn cfg_unbounded_clk_shift() {
1036 let now = Instant::now();
1037 assert_unbounded_fires(TimerInstaller::new(
1038 WheelBuilder::default()
1039 .clk_shift(2)
1040 .unbounded(64)
1041 .build(now),
1042 ));
1043 }
1044
1045 #[test]
1046 fn cfg_unbounded_num_levels() {
1047 let now = Instant::now();
1048 assert_unbounded_fires(TimerInstaller::new(
1049 WheelBuilder::default()
1050 .num_levels(4)
1051 .unbounded(64)
1052 .build(now),
1053 ));
1054 }
1055
1056 #[test]
1057 fn cfg_unbounded_full_chain() {
1058 let now = Instant::now();
1059 assert_unbounded_fires(TimerInstaller::new(
1060 WheelBuilder::default()
1061 .tick_duration(Duration::from_micros(500))
1062 .slots_per_level(32)
1063 .clk_shift(2)
1064 .num_levels(5)
1065 .unbounded(128)
1066 .build(now),
1067 ));
1068 }
1069
1070 #[test]
1073 fn cfg_bounded_default() {
1074 let now = Instant::now();
1075 assert_bounded_fires(TimerInstaller::new(
1076 WheelBuilder::default().bounded(64).build(now),
1077 ));
1078 }
1079
1080 #[test]
1081 fn cfg_bounded_tick_duration() {
1082 let now = Instant::now();
1083 assert_bounded_fires(TimerInstaller::new(
1084 WheelBuilder::default()
1085 .tick_duration(Duration::from_micros(100))
1086 .bounded(64)
1087 .build(now),
1088 ));
1089 }
1090
1091 #[test]
1092 fn cfg_bounded_slots_per_level() {
1093 let now = Instant::now();
1094 assert_bounded_fires(TimerInstaller::new(
1095 WheelBuilder::default()
1096 .slots_per_level(32)
1097 .bounded(64)
1098 .build(now),
1099 ));
1100 }
1101
1102 #[test]
1103 fn cfg_bounded_clk_shift() {
1104 let now = Instant::now();
1105 assert_bounded_fires(TimerInstaller::new(
1106 WheelBuilder::default().clk_shift(2).bounded(64).build(now),
1107 ));
1108 }
1109
1110 #[test]
1111 fn cfg_bounded_num_levels() {
1112 let now = Instant::now();
1113 assert_bounded_fires(TimerInstaller::new(
1114 WheelBuilder::default().num_levels(4).bounded(64).build(now),
1115 ));
1116 }
1117
1118 #[test]
1119 fn cfg_bounded_full_chain() {
1120 let now = Instant::now();
1121 assert_bounded_fires(TimerInstaller::new(
1122 WheelBuilder::default()
1123 .tick_duration(Duration::from_micros(500))
1124 .slots_per_level(32)
1125 .clk_shift(2)
1126 .num_levels(5)
1127 .bounded(128)
1128 .build(now),
1129 ));
1130 }
1131
1132 #[cfg(feature = "smartptr")]
1135 mod storage_tests {
1136 use super::*;
1137
1138 #[test]
1139 fn unbounded_inline_storage() {
1140 let mut builder = WorldBuilder::new();
1141 builder.register::<bool>(false);
1142 let wheel = InlineTimerWheel::unbounded(64, Instant::now());
1143 let mut timer = builder.install_driver(TimerInstaller::new(wheel));
1144 let mut world = builder.build();
1145
1146 fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
1147 *flag = true;
1148 }
1149
1150 let now = Instant::now();
1151 let h = on_fire.into_handler(world.registry());
1152 let ptr: *const dyn Handler<Instant> = &h;
1153 let storage = unsafe { nexus_smartptr::Flat::new_raw(h, ptr) };
1155 world
1156 .resource_mut::<InlineTimerWheel>()
1157 .schedule_forget(now, storage);
1158
1159 let fired = timer.poll(&mut world, now);
1160 assert_eq!(fired, 1);
1161 assert!(*world.resource::<bool>());
1162 }
1163
1164 #[test]
1165 fn unbounded_flex_storage() {
1166 let mut builder = WorldBuilder::new();
1167 builder.register::<bool>(false);
1168 let wheel = FlexTimerWheel::unbounded(64, Instant::now());
1169 let mut timer = builder.install_driver(TimerInstaller::new(wheel));
1170 let mut world = builder.build();
1171
1172 fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
1173 *flag = true;
1174 }
1175
1176 let now = Instant::now();
1177 let h = on_fire.into_handler(world.registry());
1178 let ptr: *const dyn Handler<Instant> = &h;
1179 let storage = unsafe { nexus_smartptr::Flex::new_raw(h, ptr) };
1181 world
1182 .resource_mut::<FlexTimerWheel>()
1183 .schedule_forget(now, storage);
1184
1185 let fired = timer.poll(&mut world, now);
1186 assert_eq!(fired, 1);
1187 assert!(*world.resource::<bool>());
1188 }
1189
1190 #[test]
1191 fn bounded_inline_storage() {
1192 let mut builder = WorldBuilder::new();
1193 builder.register::<bool>(false);
1194 let wheel = BoundedInlineTimerWheel::bounded(64, Instant::now());
1195 let mut timer = builder.install_driver(TimerInstaller::new(wheel));
1196 let mut world = builder.build();
1197
1198 fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
1199 *flag = true;
1200 }
1201
1202 let now = Instant::now();
1203 let h = on_fire.into_handler(world.registry());
1204 let ptr: *const dyn Handler<Instant> = &h;
1205 let storage = unsafe { nexus_smartptr::Flat::new_raw(h, ptr) };
1207 world
1208 .resource_mut::<BoundedInlineTimerWheel>()
1209 .try_schedule_forget(now, storage)
1210 .expect("should not be full");
1211
1212 let fired = timer.poll(&mut world, now);
1213 assert_eq!(fired, 1);
1214 assert!(*world.resource::<bool>());
1215 }
1216
1217 #[test]
1218 fn bounded_flex_storage() {
1219 let mut builder = WorldBuilder::new();
1220 builder.register::<bool>(false);
1221 let wheel = BoundedFlexTimerWheel::bounded(64, Instant::now());
1222 let mut timer = builder.install_driver(TimerInstaller::new(wheel));
1223 let mut world = builder.build();
1224
1225 fn on_fire(mut flag: ResMut<bool>, _now: Instant) {
1226 *flag = true;
1227 }
1228
1229 let now = Instant::now();
1230 let h = on_fire.into_handler(world.registry());
1231 let ptr: *const dyn Handler<Instant> = &h;
1232 let storage = unsafe { nexus_smartptr::Flex::new_raw(h, ptr) };
1234 world
1235 .resource_mut::<BoundedFlexTimerWheel>()
1236 .try_schedule_forget(now, storage)
1237 .expect("should not be full");
1238
1239 let fired = timer.poll(&mut world, now);
1240 assert_eq!(fired, 1);
1241 assert!(*world.resource::<bool>());
1242 }
1243
1244 #[test]
1245 fn periodic_inline_fires_repeatedly() {
1246 let mut builder = WorldBuilder::new();
1247 builder.register::<u64>(0);
1248 let wheel = InlineTimerWheel::unbounded(64, Instant::now());
1249 let mut timer = builder.install_driver(TimerInstaller::new(wheel));
1250 let mut world = builder.build();
1251
1252 fn tick(mut counter: ResMut<u64>, _now: Instant) {
1253 *counter += 1;
1254 }
1255
1256 let now = Instant::now();
1257 let interval = Duration::from_millis(10);
1258 let handler = tick.into_handler(world.registry());
1259 let periodic: Periodic<_, InlineTimers, nexus_timer::store::UnboundedSlab<_>> =
1260 Periodic::new(handler, interval);
1261 world
1262 .resource_mut::<InlineTimerWheel>()
1263 .schedule_forget(now, InlineTimers::wrap(periodic));
1264
1265 timer.poll(&mut world, now);
1267 assert_eq!(*world.resource::<u64>(), 1);
1268
1269 timer.poll(&mut world, now + interval);
1271 assert_eq!(*world.resource::<u64>(), 2);
1272
1273 timer.poll(&mut world, now + interval * 2);
1275 assert_eq!(*world.resource::<u64>(), 3);
1276
1277 assert!(!timer.is_empty(&world));
1279 }
1280
1281 #[test]
1282 fn periodic_flex_fires_repeatedly() {
1283 let mut builder = WorldBuilder::new();
1284 builder.register::<u64>(0);
1285 let wheel = FlexTimerWheel::unbounded(64, Instant::now());
1286 let mut timer = builder.install_driver(TimerInstaller::new(wheel));
1287 let mut world = builder.build();
1288
1289 fn tick(mut counter: ResMut<u64>, _now: Instant) {
1290 *counter += 1;
1291 }
1292
1293 let now = Instant::now();
1294 let interval = Duration::from_millis(10);
1295 let handler = tick.into_handler(world.registry());
1296 let periodic: Periodic<_, FlexTimers, nexus_timer::store::UnboundedSlab<_>> =
1297 Periodic::new(handler, interval);
1298 world
1299 .resource_mut::<FlexTimerWheel>()
1300 .schedule_forget(now, FlexTimers::wrap(periodic));
1301
1302 timer.poll(&mut world, now);
1303 assert_eq!(*world.resource::<u64>(), 1);
1304
1305 timer.poll(&mut world, now + interval);
1306 assert_eq!(*world.resource::<u64>(), 2);
1307
1308 timer.poll(&mut world, now + interval * 2);
1309 assert_eq!(*world.resource::<u64>(), 3);
1310
1311 assert!(!timer.is_empty(&world));
1312 }
1313 }
1314}