Skip to main content

oxihuman_core/
task_scheduler.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Cron-style task scheduler stub.
6
7use std::collections::HashMap;
8
9/// Recurrence rule for scheduled tasks.
10#[derive(Debug, Clone)]
11pub enum RecurrenceRule {
12    Once,
13    EveryMs(u64),
14}
15
16/// A single scheduled task.
17#[derive(Debug, Clone)]
18pub struct SchedulerTask {
19    pub id: u64,
20    pub name: String,
21    pub next_run_ms: u64,
22    pub rule: RecurrenceRule,
23    pub enabled: bool,
24    pub run_count: u64,
25}
26
27/// Cron-style task scheduler.
28#[derive(Debug, Default)]
29pub struct TaskScheduler {
30    tasks: HashMap<u64, SchedulerTask>,
31    next_id: u64,
32    current_time_ms: u64,
33}
34
35impl TaskScheduler {
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    pub fn schedule(&mut self, name: &str, first_run_ms: u64, rule: RecurrenceRule) -> u64 {
41        let id = self.next_id;
42        self.next_id += 1;
43        self.tasks.insert(
44            id,
45            SchedulerTask {
46                id,
47                name: name.to_string(),
48                next_run_ms: first_run_ms,
49                rule,
50                enabled: true,
51                run_count: 0,
52            },
53        );
54        id
55    }
56
57    pub fn advance(&mut self, time_ms: u64) -> Vec<u64> {
58        /* returns IDs of tasks that fired */
59        self.current_time_ms = time_ms;
60        let mut fired = Vec::new();
61        for task in self.tasks.values_mut() {
62            if !task.enabled || task.next_run_ms > time_ms {
63                continue;
64            }
65            fired.push(task.id);
66            task.run_count += 1;
67            match &task.rule {
68                RecurrenceRule::Once => {
69                    task.enabled = false;
70                }
71                RecurrenceRule::EveryMs(interval) => {
72                    task.next_run_ms = time_ms + interval;
73                }
74            }
75        }
76        fired
77    }
78
79    pub fn cancel(&mut self, id: u64) -> bool {
80        if let Some(t) = self.tasks.get_mut(&id) {
81            t.enabled = false;
82            true
83        } else {
84            false
85        }
86    }
87
88    pub fn task_count(&self) -> usize {
89        self.tasks.len()
90    }
91
92    pub fn enabled_count(&self) -> usize {
93        self.tasks.values().filter(|t| t.enabled).count()
94    }
95
96    pub fn run_count(&self, id: u64) -> u64 {
97        self.tasks.get(&id).map(|t| t.run_count).unwrap_or(0)
98    }
99}
100
101pub fn new_task_scheduler() -> TaskScheduler {
102    TaskScheduler::new()
103}
104
105pub fn ts_schedule(
106    sched: &mut TaskScheduler,
107    name: &str,
108    first_ms: u64,
109    rule: RecurrenceRule,
110) -> u64 {
111    sched.schedule(name, first_ms, rule)
112}
113
114pub fn ts_advance(sched: &mut TaskScheduler, time_ms: u64) -> Vec<u64> {
115    sched.advance(time_ms)
116}
117
118pub fn ts_cancel(sched: &mut TaskScheduler, id: u64) -> bool {
119    sched.cancel(id)
120}
121
122pub fn ts_task_count(sched: &TaskScheduler) -> usize {
123    sched.task_count()
124}
125
126pub fn ts_run_count(sched: &TaskScheduler, id: u64) -> u64 {
127    sched.run_count(id)
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_schedule_once() {
136        let mut s = new_task_scheduler();
137        let id = ts_schedule(&mut s, "job", 100, RecurrenceRule::Once);
138        let fired = ts_advance(&mut s, 200);
139        assert!(fired.contains(&id));
140    }
141
142    #[test]
143    fn test_once_fires_only_once() {
144        let mut s = new_task_scheduler();
145        let id = ts_schedule(&mut s, "job", 50, RecurrenceRule::Once);
146        ts_advance(&mut s, 100);
147        let fired2 = ts_advance(&mut s, 200);
148        assert!(!fired2.contains(&id));
149    }
150
151    #[test]
152    fn test_repeating_fires_multiple_times() {
153        let mut s = new_task_scheduler();
154        let id = ts_schedule(&mut s, "tick", 100, RecurrenceRule::EveryMs(100));
155        ts_advance(&mut s, 100);
156        ts_advance(&mut s, 200);
157        assert_eq!(ts_run_count(&s, id), 2);
158    }
159
160    #[test]
161    fn test_before_first_run_not_fired() {
162        let mut s = new_task_scheduler();
163        let id = ts_schedule(&mut s, "future", 1000, RecurrenceRule::Once);
164        let fired = ts_advance(&mut s, 500);
165        assert!(!fired.contains(&id));
166    }
167
168    #[test]
169    fn test_cancel_prevents_firing() {
170        let mut s = new_task_scheduler();
171        let id = ts_schedule(&mut s, "j", 100, RecurrenceRule::Once);
172        ts_cancel(&mut s, id);
173        let fired = ts_advance(&mut s, 200);
174        assert!(!fired.contains(&id));
175    }
176
177    #[test]
178    fn test_task_count() {
179        let mut s = new_task_scheduler();
180        ts_schedule(&mut s, "a", 0, RecurrenceRule::Once);
181        ts_schedule(&mut s, "b", 0, RecurrenceRule::Once);
182        assert_eq!(ts_task_count(&s), 2);
183    }
184
185    #[test]
186    fn test_enabled_count_decreases_after_once() {
187        let mut s = new_task_scheduler();
188        ts_schedule(&mut s, "x", 10, RecurrenceRule::Once);
189        assert_eq!(s.enabled_count(), 1);
190        ts_advance(&mut s, 100);
191        assert_eq!(s.enabled_count(), 0);
192    }
193
194    #[test]
195    fn test_run_count_starts_at_zero() {
196        let mut s = new_task_scheduler();
197        let id = ts_schedule(&mut s, "z", 1000, RecurrenceRule::Once);
198        assert_eq!(ts_run_count(&s, id), 0);
199    }
200
201    #[test]
202    fn test_unknown_id_run_count_zero() {
203        let s = new_task_scheduler();
204        assert_eq!(ts_run_count(&s, 999), 0);
205    }
206}