Skip to main content

yog_api/
scheduler.rs

1//! Tasks a mod schedules in server ticks: kept by the mod itself, run by
2//! its subscription to the server's tick (after the tick's other handlers).
3
4use yog_sync::{Arc, Mutex, Once};
5
6use yog_abi::priority;
7use yog_event::server::game::Tick;
8
9use crate::registry::Registry;
10
11/// A scheduled task.
12type Task = Arc<dyn Fn() + Send + Sync>;
13
14/// The tasks waiting for their tick.
15struct Tasks<T> {
16    /// Ticks left, and the task.
17    once: Vec<(u64, T)>,
18    /// Period, ticks left, and the task.
19    repeating: Vec<(u64, u64, T)>,
20}
21
22impl<T: Clone> Tasks<T> {
23    const fn new() -> Self {
24        Self {
25            once: Vec::new(),
26            repeating: Vec::new(),
27        }
28    }
29
30    /// One tick: the tasks due now — once tasks whose delay ran out (`d`
31    /// ticks later, 0 and 1 meaning the next tick), repeating ones whose
32    /// period came round (every `p` ticks, 0 meaning every tick). Once
33    /// tasks fired are gone.
34    fn due(&mut self) -> Vec<T> {
35        let mut out = Vec::new();
36        self.once.retain_mut(|(left, task)| {
37            if *left <= 1 {
38                out.push(task.clone());
39                false
40            } else {
41                *left -= 1;
42                true
43            }
44        });
45        for (period, left, task) in &mut self.repeating {
46            *left = left.saturating_sub(1);
47            if *left == 0 {
48                out.push(task.clone());
49                *left = (*period).max(1);
50            }
51        }
52        out
53    }
54}
55
56static TASKS: Mutex<Tasks<Task>> = Mutex::new(Tasks::new());
57static TICKING: Once = Once::new();
58
59impl Registry {
60    /// Runs `handler` once, `delay_ticks` server ticks from now.
61    pub fn schedule_once<F>(&self, delay_ticks: u64, handler: F)
62    where
63        F: Fn() + Send + Sync + 'static,
64    {
65        TASKS.lock().once.push((delay_ticks, Arc::new(handler)));
66        self.tick_tasks();
67    }
68
69    /// Runs `handler` every `period_ticks` server ticks.
70    pub fn schedule_repeating<F>(&self, period_ticks: u64, handler: F)
71    where
72        F: Fn() + Send + Sync + 'static,
73    {
74        TASKS
75            .lock()
76            .repeating
77            .push((period_ticks, period_ticks.max(1), Arc::new(handler)));
78        self.tick_tasks();
79    }
80
81    /// Subscribes the tasks to the server's tick, once.
82    fn tick_tasks(&self) {
83        TICKING.call_once(|| {
84            self.after_at::<Tick, _, _>(priority::LATE, |_, _| {
85                let due = TASKS.lock().due();
86                for task in due {
87                    crate::registry::guard((), &*task);
88                }
89            });
90        });
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn scheduled_tasks_run_when_due() {
100        let mut tasks = Tasks::new();
101        tasks.once.extend([(0, 'a'), (3, 'b')]);
102        tasks.repeating.extend([(2, 2, 'c'), (0, 1, 'd')]);
103        let per_tick: Vec<usize> = (0..6).map(|_| tasks.due().len()).collect();
104        // tick 1: once(0), every tick; 2: every 2, every tick; 3: once(3), every tick; …
105        assert_eq!(per_tick, [2, 2, 2, 2, 1, 2]);
106        assert!(tasks.once.is_empty(), "once tasks are gone once run");
107    }
108}