Skip to main content

oxihuman_core/
command_queue.rs

1//! Command queue for deferred execution with priority levels.
2
3/// Priority levels for queued commands.
4#[allow(dead_code)]
5#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub enum CommandPriority {
7    /// Must execute immediately / first.
8    Critical,
9    /// High importance.
10    High,
11    /// Default importance.
12    Normal,
13    /// Background / best-effort.
14    Low,
15}
16
17/// A single command waiting in the queue.
18#[allow(dead_code)]
19#[derive(Clone, Debug)]
20pub struct QueuedCommand {
21    /// Unique id assigned at enqueue time.
22    pub id: u64,
23    /// Human-readable label.
24    pub label: String,
25    /// Priority tier.
26    pub priority: CommandPriority,
27    /// Monotonic sequence number for FIFO within same priority.
28    pub sequence: u64,
29}
30
31/// A priority-aware command queue.
32#[allow(dead_code)]
33#[derive(Clone, Debug)]
34pub struct CommandQueue {
35    /// Queued commands, kept sorted by (priority, sequence).
36    commands: Vec<QueuedCommand>,
37    /// Running counter for unique ids.
38    next_id: u64,
39    /// Running counter for insertion order.
40    next_seq: u64,
41    /// Total number of commands ever enqueued.
42    total_enqueued: u64,
43    /// Maximum number of commands the queue has ever held.
44    max_depth: usize,
45}
46
47// ---------------------------------------------------------------------------
48// Helpers
49// ---------------------------------------------------------------------------
50
51/// Map a `CommandPriority` to a sort key (lower = higher priority).
52#[allow(dead_code)]
53fn priority_rank(p: &CommandPriority) -> u8 {
54    match p {
55        CommandPriority::Critical => 0,
56        CommandPriority::High => 1,
57        CommandPriority::Normal => 2,
58        CommandPriority::Low => 3,
59    }
60}
61
62/// Sort the internal list by (priority_rank, sequence).
63#[allow(dead_code)]
64fn sort_commands(cmds: &mut [QueuedCommand]) {
65    cmds.sort_by(|a, b| {
66        let pa = priority_rank(&a.priority);
67        let pb = priority_rank(&b.priority);
68        pa.cmp(&pb).then(a.sequence.cmp(&b.sequence))
69    });
70}
71
72// ---------------------------------------------------------------------------
73// Construction
74// ---------------------------------------------------------------------------
75
76/// Create a new, empty `CommandQueue`.
77#[allow(dead_code)]
78pub fn new_command_queue() -> CommandQueue {
79    CommandQueue {
80        commands: Vec::new(),
81        next_id: 1,
82        next_seq: 0,
83        total_enqueued: 0,
84        max_depth: 0,
85    }
86}
87
88// ---------------------------------------------------------------------------
89// Enqueue / Dequeue
90// ---------------------------------------------------------------------------
91
92/// Enqueue a single command with the given label and priority.
93/// Returns the assigned command id.
94#[allow(dead_code)]
95pub fn enqueue(queue: &mut CommandQueue, label: &str, priority: CommandPriority) -> u64 {
96    let id = queue.next_id;
97    queue.next_id += 1;
98    let seq = queue.next_seq;
99    queue.next_seq += 1;
100    queue.total_enqueued += 1;
101    queue.commands.push(QueuedCommand {
102        id,
103        label: label.to_string(),
104        priority,
105        sequence: seq,
106    });
107    sort_commands(&mut queue.commands);
108    if queue.commands.len() > queue.max_depth {
109        queue.max_depth = queue.commands.len();
110    }
111    id
112}
113
114/// Remove and return the highest-priority (lowest rank) command.
115/// Returns `None` if the queue is empty.
116#[allow(dead_code)]
117pub fn dequeue(queue: &mut CommandQueue) -> Option<QueuedCommand> {
118    if queue.commands.is_empty() {
119        None
120    } else {
121        Some(queue.commands.remove(0))
122    }
123}
124
125/// Peek at the next command without removing it.
126#[allow(dead_code)]
127pub fn peek_next(queue: &CommandQueue) -> Option<&QueuedCommand> {
128    queue.commands.first()
129}
130
131// ---------------------------------------------------------------------------
132// Queries
133// ---------------------------------------------------------------------------
134
135/// Return the number of commands currently in the queue.
136#[allow(dead_code)]
137pub fn command_count(queue: &CommandQueue) -> usize {
138    queue.commands.len()
139}
140
141/// Return `true` if the queue has no commands.
142#[allow(dead_code)]
143pub fn is_queue_empty(queue: &CommandQueue) -> bool {
144    queue.commands.is_empty()
145}
146
147/// Return `true` if there is at least one command of the given priority.
148#[allow(dead_code)]
149pub fn has_priority(queue: &CommandQueue, priority: &CommandPriority) -> bool {
150    queue.commands.iter().any(|c| &c.priority == priority)
151}
152
153/// Return the total number of commands ever enqueued.
154#[allow(dead_code)]
155pub fn total_enqueued(queue: &CommandQueue) -> u64 {
156    queue.total_enqueued
157}
158
159/// Return the historical maximum queue depth.
160#[allow(dead_code)]
161pub fn max_queue_depth(queue: &CommandQueue) -> usize {
162    queue.max_depth
163}
164
165// ---------------------------------------------------------------------------
166// Batch / bulk operations
167// ---------------------------------------------------------------------------
168
169/// Remove all commands from the queue.
170#[allow(dead_code)]
171pub fn clear_queue(queue: &mut CommandQueue) {
172    queue.commands.clear();
173}
174
175/// Remove and return all commands, ordered by priority then sequence.
176#[allow(dead_code)]
177pub fn drain_all(queue: &mut CommandQueue) -> Vec<QueuedCommand> {
178    let mut out = std::mem::take(&mut queue.commands);
179    sort_commands(&mut out);
180    out
181}
182
183/// Enqueue a batch of `(label, priority)` pairs. Returns the assigned ids.
184#[allow(dead_code)]
185pub fn enqueue_batch(queue: &mut CommandQueue, items: &[(&str, CommandPriority)]) -> Vec<u64> {
186    let mut ids = Vec::with_capacity(items.len());
187    for (label, pri) in items {
188        ids.push(enqueue(queue, label, pri.clone()));
189    }
190    ids
191}
192
193/// Collect references to all commands of a given priority.
194#[allow(dead_code)]
195pub fn commands_by_priority<'a>(
196    queue: &'a CommandQueue,
197    priority: &CommandPriority,
198) -> Vec<&'a QueuedCommand> {
199    queue
200        .commands
201        .iter()
202        .filter(|c| &c.priority == priority)
203        .collect()
204}
205
206// ---------------------------------------------------------------------------
207// Serialization
208// ---------------------------------------------------------------------------
209
210/// Produce a minimal JSON representation of the queue.
211#[allow(dead_code)]
212pub fn command_queue_to_json(queue: &CommandQueue) -> String {
213    let mut s = String::from("{\"commands\":[");
214    for (i, c) in queue.commands.iter().enumerate() {
215        if i > 0 {
216            s.push(',');
217        }
218        s.push_str(&format!(
219            "{{\"id\":{},\"label\":\"{}\",\"priority\":\"{:?}\"}}",
220            c.id, c.label, c.priority
221        ));
222    }
223    s.push_str(&format!(
224        "],\"total_enqueued\":{},\"max_depth\":{}}}",
225        queue.total_enqueued, queue.max_depth
226    ));
227    s
228}
229
230// ---------------------------------------------------------------------------
231// Tests
232// ---------------------------------------------------------------------------
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    #[test]
239    fn test_new_command_queue() {
240        let q = new_command_queue();
241        assert!(is_queue_empty(&q));
242        assert_eq!(command_count(&q), 0);
243    }
244
245    #[test]
246    fn test_enqueue_single() {
247        let mut q = new_command_queue();
248        let id = enqueue(&mut q, "cmd1", CommandPriority::Normal);
249        assert!(id > 0);
250        assert_eq!(command_count(&q), 1);
251    }
252
253    #[test]
254    fn test_dequeue_fifo() {
255        let mut q = new_command_queue();
256        enqueue(&mut q, "first", CommandPriority::Normal);
257        enqueue(&mut q, "second", CommandPriority::Normal);
258        let c = dequeue(&mut q).expect("should succeed");
259        assert_eq!(c.label, "first");
260    }
261
262    #[test]
263    fn test_dequeue_empty() {
264        let mut q = new_command_queue();
265        assert!(dequeue(&mut q).is_none());
266    }
267
268    #[test]
269    fn test_priority_ordering() {
270        let mut q = new_command_queue();
271        enqueue(&mut q, "low", CommandPriority::Low);
272        enqueue(&mut q, "critical", CommandPriority::Critical);
273        enqueue(&mut q, "normal", CommandPriority::Normal);
274        let c = dequeue(&mut q).expect("should succeed");
275        assert_eq!(c.label, "critical");
276    }
277
278    #[test]
279    fn test_peek_next() {
280        let mut q = new_command_queue();
281        assert!(peek_next(&q).is_none());
282        enqueue(&mut q, "peek_me", CommandPriority::High);
283        let p = peek_next(&q).expect("should succeed");
284        assert_eq!(p.label, "peek_me");
285        assert_eq!(command_count(&q), 1); // not removed
286    }
287
288    #[test]
289    fn test_clear_queue() {
290        let mut q = new_command_queue();
291        enqueue(&mut q, "a", CommandPriority::Normal);
292        enqueue(&mut q, "b", CommandPriority::High);
293        clear_queue(&mut q);
294        assert!(is_queue_empty(&q));
295    }
296
297    #[test]
298    fn test_drain_all() {
299        let mut q = new_command_queue();
300        enqueue(&mut q, "a", CommandPriority::Low);
301        enqueue(&mut q, "b", CommandPriority::Critical);
302        let drained = drain_all(&mut q);
303        assert_eq!(drained.len(), 2);
304        assert_eq!(drained[0].label, "b"); // critical first
305        assert!(is_queue_empty(&q));
306    }
307
308    #[test]
309    fn test_enqueue_batch() {
310        let mut q = new_command_queue();
311        let ids = enqueue_batch(
312            &mut q,
313            &[
314                ("x", CommandPriority::Normal),
315                ("y", CommandPriority::High),
316                ("z", CommandPriority::Low),
317            ],
318        );
319        assert_eq!(ids.len(), 3);
320        assert_eq!(command_count(&q), 3);
321    }
322
323    #[test]
324    fn test_commands_by_priority() {
325        let mut q = new_command_queue();
326        enqueue(&mut q, "a", CommandPriority::Normal);
327        enqueue(&mut q, "b", CommandPriority::High);
328        enqueue(&mut q, "c", CommandPriority::Normal);
329        let normals = commands_by_priority(&q, &CommandPriority::Normal);
330        assert_eq!(normals.len(), 2);
331    }
332
333    #[test]
334    fn test_total_enqueued() {
335        let mut q = new_command_queue();
336        enqueue(&mut q, "a", CommandPriority::Normal);
337        enqueue(&mut q, "b", CommandPriority::Normal);
338        dequeue(&mut q);
339        assert_eq!(total_enqueued(&q), 2);
340    }
341
342    #[test]
343    fn test_is_queue_empty() {
344        let mut q = new_command_queue();
345        assert!(is_queue_empty(&q));
346        enqueue(&mut q, "x", CommandPriority::Low);
347        assert!(!is_queue_empty(&q));
348    }
349
350    #[test]
351    fn test_has_priority() {
352        let mut q = new_command_queue();
353        enqueue(&mut q, "a", CommandPriority::High);
354        assert!(has_priority(&q, &CommandPriority::High));
355        assert!(!has_priority(&q, &CommandPriority::Low));
356    }
357
358    #[test]
359    fn test_max_queue_depth() {
360        let mut q = new_command_queue();
361        enqueue(&mut q, "a", CommandPriority::Normal);
362        enqueue(&mut q, "b", CommandPriority::Normal);
363        enqueue(&mut q, "c", CommandPriority::Normal);
364        dequeue(&mut q);
365        assert_eq!(max_queue_depth(&q), 3);
366    }
367
368    #[test]
369    fn test_command_queue_to_json() {
370        let mut q = new_command_queue();
371        enqueue(&mut q, "test", CommandPriority::Normal);
372        let json = command_queue_to_json(&q);
373        assert!(json.contains("\"commands\""));
374        assert!(json.contains("\"test\""));
375        assert!(json.contains("\"total_enqueued\":1"));
376    }
377
378    #[test]
379    fn test_priority_stable_fifo() {
380        let mut q = new_command_queue();
381        enqueue(&mut q, "high1", CommandPriority::High);
382        enqueue(&mut q, "high2", CommandPriority::High);
383        enqueue(&mut q, "high3", CommandPriority::High);
384        let c1 = dequeue(&mut q).expect("should succeed");
385        let c2 = dequeue(&mut q).expect("should succeed");
386        assert_eq!(c1.label, "high1");
387        assert_eq!(c2.label, "high2");
388    }
389}