Skip to main content

ruvix_types/
scheduler.rs

1//! Scheduler types for coherence-aware task scheduling.
2//!
3//! The RuVix scheduler combines deadline pressure, novelty signal,
4//! and structural risk to determine task priority.
5
6/// Scheduler score combining multiple signals.
7///
8/// The final priority is computed from:
9/// - Deadline urgency (inverse of time remaining)
10/// - Novelty boost (for tasks processing new information)
11/// - Risk penalty (for tasks that would lower coherence)
12#[derive(Debug, Clone, Copy, PartialEq)]
13#[repr(C)]
14pub struct SchedulerScore {
15    /// Combined score (higher = higher priority).
16    /// Range: typically 0.0 to 10.0, but can exceed bounds.
17    pub score: f32,
18
19    /// Deadline urgency component (0.0 to 5.0).
20    pub deadline_urgency: f32,
21
22    /// Novelty boost component (0.0 to 1.0).
23    pub novelty_boost: f32,
24
25    /// Risk penalty component (0.0 to 2.0, subtracted from score).
26    pub risk_penalty: f32,
27}
28
29impl SchedulerScore {
30    /// Creates a new scheduler score with explicit components.
31    #[inline]
32    #[must_use]
33    pub const fn new(
34        deadline_urgency: f32,
35        novelty_boost: f32,
36        risk_penalty: f32,
37    ) -> Self {
38        Self {
39            score: deadline_urgency + novelty_boost - risk_penalty,
40            deadline_urgency,
41            novelty_boost,
42            risk_penalty,
43        }
44    }
45
46    /// Creates a default priority score (normal task, no urgency).
47    #[inline]
48    #[must_use]
49    pub const fn normal() -> Self {
50        Self {
51            score: 1.0,
52            deadline_urgency: 1.0,
53            novelty_boost: 0.0,
54            risk_penalty: 0.0,
55        }
56    }
57
58    /// Creates a high priority score.
59    #[inline]
60    #[must_use]
61    pub const fn high() -> Self {
62        Self {
63            score: 3.0,
64            deadline_urgency: 3.0,
65            novelty_boost: 0.0,
66            risk_penalty: 0.0,
67        }
68    }
69
70    /// Creates a critical priority score.
71    #[inline]
72    #[must_use]
73    pub const fn critical() -> Self {
74        Self {
75            score: 5.0,
76            deadline_urgency: 5.0,
77            novelty_boost: 0.0,
78            risk_penalty: 0.0,
79        }
80    }
81
82    /// Returns true if this score is higher than another.
83    #[inline]
84    #[must_use]
85    pub fn is_higher_than(&self, other: &Self) -> bool {
86        self.score > other.score
87    }
88
89    /// Adds novelty boost to the score.
90    #[inline]
91    #[must_use]
92    pub fn with_novelty(mut self, boost: f32) -> Self {
93        let clamped = if boost < 0.0 {
94            0.0
95        } else if boost > 1.0 {
96            1.0
97        } else {
98            boost
99        };
100        self.novelty_boost = clamped;
101        self.score = self.deadline_urgency + self.novelty_boost - self.risk_penalty;
102        self
103    }
104
105    /// Adds risk penalty to the score.
106    #[inline]
107    #[must_use]
108    pub fn with_risk(mut self, penalty: f32) -> Self {
109        let clamped = if penalty < 0.0 {
110            0.0
111        } else if penalty > 2.0 {
112            2.0
113        } else {
114            penalty
115        };
116        self.risk_penalty = clamped;
117        self.score = self.deadline_urgency + self.novelty_boost - self.risk_penalty;
118        self
119    }
120}
121
122impl Default for SchedulerScore {
123    fn default() -> Self {
124        Self::normal()
125    }
126}
127
128impl PartialOrd for SchedulerScore {
129    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
130        self.score.partial_cmp(&other.score)
131    }
132}
133
134/// Task scheduling partition.
135///
136/// Tasks are grouped by their RVF mount origin. Each partition gets
137/// a guaranteed time slice, preventing a misbehaving component from
138/// starving others.
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
140#[repr(C)]
141pub struct SchedulerPartition {
142    /// Partition ID (typically matches RVF mount ID).
143    pub partition_id: u32,
144
145    /// Time slice in microseconds per scheduling epoch.
146    pub time_slice_us: u32,
147
148    /// Current tasks in this partition.
149    pub task_count: u32,
150
151    /// Remaining time in current epoch.
152    pub remaining_us: u32,
153}
154
155impl SchedulerPartition {
156    /// Creates a new scheduler partition.
157    #[inline]
158    #[must_use]
159    pub const fn new(partition_id: u32, time_slice_us: u32) -> Self {
160        Self {
161            partition_id,
162            time_slice_us,
163            task_count: 0,
164            remaining_us: time_slice_us,
165        }
166    }
167
168    /// Returns true if the partition has exhausted its time slice.
169    #[inline]
170    #[must_use]
171    pub const fn is_exhausted(&self) -> bool {
172        self.remaining_us == 0
173    }
174
175    /// Resets the partition for a new scheduling epoch.
176    #[inline]
177    pub fn reset(&mut self) {
178        self.remaining_us = self.time_slice_us;
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_scheduler_score_ordering() {
188        let low = SchedulerScore::normal();
189        let high = SchedulerScore::high();
190        let critical = SchedulerScore::critical();
191
192        assert!(high.is_higher_than(&low));
193        assert!(critical.is_higher_than(&high));
194    }
195
196    #[test]
197    fn test_scheduler_score_with_novelty() {
198        let base = SchedulerScore::normal();
199        let boosted = base.with_novelty(0.5);
200
201        assert!(boosted.is_higher_than(&base));
202        assert!((boosted.novelty_boost - 0.5).abs() < 0.001);
203    }
204
205    #[test]
206    fn test_scheduler_score_with_risk() {
207        let base = SchedulerScore::normal();
208        let penalized = base.with_risk(1.0);
209
210        assert!(!penalized.is_higher_than(&base));
211        assert!((penalized.risk_penalty - 1.0).abs() < 0.001);
212    }
213
214    #[test]
215    fn test_scheduler_partition() {
216        let mut partition = SchedulerPartition::new(1, 10000);
217        assert!(!partition.is_exhausted());
218
219        partition.remaining_us = 0;
220        assert!(partition.is_exhausted());
221
222        partition.reset();
223        assert!(!partition.is_exhausted());
224    }
225}