Skip to main content

oxihuman_core/
schedule_queue.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Time-ordered schedule queue for deferred task execution.
6
7/// A scheduled task entry.
8#[derive(Debug, Clone)]
9pub struct ScheduleTask {
10    pub id: u64,
11    pub name: String,
12    pub due_at: u64,
13    pub repeat_interval: Option<u64>,
14    pub enabled: bool,
15}
16
17/// Queue of tasks ordered by due time.
18pub struct ScheduleQueue {
19    tasks: Vec<ScheduleTask>,
20    now: u64,
21    next_id: u64,
22    fired_count: u64,
23}
24
25#[allow(dead_code)]
26impl ScheduleQueue {
27    pub fn new() -> Self {
28        ScheduleQueue {
29            tasks: Vec::new(),
30            now: 0,
31            next_id: 0,
32            fired_count: 0,
33        }
34    }
35
36    pub fn schedule_once(&mut self, name: &str, due_at: u64) -> u64 {
37        let id = self.next_id;
38        self.next_id += 1;
39        let pos = self.tasks.partition_point(|t| t.due_at <= due_at);
40        self.tasks.insert(
41            pos,
42            ScheduleTask {
43                id,
44                name: name.to_string(),
45                due_at,
46                repeat_interval: None,
47                enabled: true,
48            },
49        );
50        id
51    }
52
53    pub fn schedule_repeating(&mut self, name: &str, due_at: u64, interval: u64) -> u64 {
54        let id = self.next_id;
55        self.next_id += 1;
56        let pos = self.tasks.partition_point(|t| t.due_at <= due_at);
57        self.tasks.insert(
58            pos,
59            ScheduleTask {
60                id,
61                name: name.to_string(),
62                due_at,
63                repeat_interval: Some(interval),
64                enabled: true,
65            },
66        );
67        id
68    }
69
70    pub fn advance(&mut self, dt: u64) -> Vec<ScheduleTask> {
71        self.now += dt;
72        let mut fired: Vec<ScheduleTask> = Vec::new();
73        let mut to_reschedule: Vec<(String, u64, u64)> = Vec::new();
74
75        self.tasks.retain(|t| {
76            if t.enabled && t.due_at <= self.now {
77                fired.push(t.clone());
78                if let Some(interval) = t.repeat_interval {
79                    to_reschedule.push((t.name.clone(), self.now + interval, interval));
80                }
81                false
82            } else {
83                true
84            }
85        });
86
87        self.fired_count += fired.len() as u64;
88
89        for (name, due, interval) in to_reschedule {
90            self.schedule_repeating(&name, due, interval);
91        }
92
93        fired
94    }
95
96    pub fn cancel(&mut self, id: u64) -> bool {
97        let before = self.tasks.len();
98        self.tasks.retain(|t| t.id != id);
99        self.tasks.len() < before
100    }
101
102    pub fn set_enabled(&mut self, id: u64, enabled: bool) -> bool {
103        if let Some(t) = self.tasks.iter_mut().find(|t| t.id == id) {
104            t.enabled = enabled;
105            true
106        } else {
107            false
108        }
109    }
110
111    pub fn task_count(&self) -> usize {
112        self.tasks.len()
113    }
114
115    pub fn now(&self) -> u64 {
116        self.now
117    }
118
119    pub fn fired_count(&self) -> u64 {
120        self.fired_count
121    }
122
123    pub fn next_due(&self) -> Option<u64> {
124        self.tasks
125            .iter()
126            .filter(|t| t.enabled)
127            .map(|t| t.due_at)
128            .min()
129    }
130
131    pub fn is_empty(&self) -> bool {
132        self.tasks.is_empty()
133    }
134
135    pub fn clear(&mut self) {
136        self.tasks.clear();
137    }
138
139    pub fn has_task(&self, id: u64) -> bool {
140        self.tasks.iter().any(|t| t.id == id)
141    }
142}
143
144impl Default for ScheduleQueue {
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150pub fn new_schedule_queue() -> ScheduleQueue {
151    ScheduleQueue::new()
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn schedule_and_fire() {
160        let mut q = new_schedule_queue();
161        q.schedule_once("task1", 10);
162        let fired = q.advance(10);
163        assert_eq!(fired.len(), 1);
164        assert_eq!(fired[0].name, "task1");
165    }
166
167    #[test]
168    fn not_due_yet() {
169        let mut q = new_schedule_queue();
170        q.schedule_once("t", 100);
171        let fired = q.advance(50);
172        assert!(fired.is_empty());
173    }
174
175    #[test]
176    fn repeating_reschedules() {
177        let mut q = new_schedule_queue();
178        q.schedule_repeating("tick", 5, 5);
179        q.advance(5);
180        assert_eq!(q.task_count(), 1);
181        q.advance(5);
182        assert_eq!(q.fired_count(), 2);
183    }
184
185    #[test]
186    fn cancel_task() {
187        let mut q = new_schedule_queue();
188        let id = q.schedule_once("x", 10);
189        assert!(q.cancel(id));
190        assert!(!q.has_task(id));
191    }
192
193    #[test]
194    fn disabled_not_fired() {
195        let mut q = new_schedule_queue();
196        let id = q.schedule_once("t", 5);
197        q.set_enabled(id, false);
198        let fired = q.advance(10);
199        assert!(fired.is_empty());
200    }
201
202    #[test]
203    fn next_due_time() {
204        let mut q = new_schedule_queue();
205        q.schedule_once("a", 50);
206        q.schedule_once("b", 20);
207        assert_eq!(q.next_due(), Some(20));
208    }
209
210    #[test]
211    fn fired_count_tracked() {
212        let mut q = new_schedule_queue();
213        q.schedule_once("a", 1);
214        q.schedule_once("b", 2);
215        q.advance(5);
216        assert_eq!(q.fired_count(), 2);
217    }
218
219    #[test]
220    fn clear_queue() {
221        let mut q = new_schedule_queue();
222        q.schedule_once("a", 10);
223        q.clear();
224        assert!(q.is_empty());
225    }
226
227    #[test]
228    fn multiple_fire_same_tick() {
229        let mut q = new_schedule_queue();
230        q.schedule_once("a", 5);
231        q.schedule_once("b", 5);
232        let fired = q.advance(5);
233        assert_eq!(fired.len(), 2);
234    }
235}