Skip to main content

oxihuman_core/
command_list.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Ordered list of named commands with priority and enabled flag.
6
7/// Priority level for a command.
8#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
10pub enum CmdPriority {
11    Low,
12    Normal,
13    High,
14}
15
16/// A single entry in the command list.
17#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct CmdEntry {
20    pub name: String,
21    pub priority: CmdPriority,
22    pub enabled: bool,
23    pub tag: Option<String>,
24}
25
26/// Ordered, prioritised list of named commands.
27#[allow(dead_code)]
28#[derive(Debug, Clone, Default)]
29pub struct CommandList {
30    entries: Vec<CmdEntry>,
31}
32
33#[allow(dead_code)]
34impl CommandList {
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    pub fn push(&mut self, name: &str, priority: CmdPriority) {
40        self.entries.push(CmdEntry {
41            name: name.to_string(),
42            priority,
43            enabled: true,
44            tag: None,
45        });
46    }
47
48    pub fn push_tagged(&mut self, name: &str, priority: CmdPriority, tag: &str) {
49        self.entries.push(CmdEntry {
50            name: name.to_string(),
51            priority,
52            enabled: true,
53            tag: Some(tag.to_string()),
54        });
55    }
56
57    pub fn len(&self) -> usize {
58        self.entries.len()
59    }
60
61    pub fn is_empty(&self) -> bool {
62        self.entries.is_empty()
63    }
64
65    pub fn get(&self, index: usize) -> Option<&CmdEntry> {
66        self.entries.get(index)
67    }
68
69    pub fn enable(&mut self, name: &str) {
70        for e in &mut self.entries {
71            if e.name == name {
72                e.enabled = true;
73            }
74        }
75    }
76
77    pub fn disable(&mut self, name: &str) {
78        for e in &mut self.entries {
79            if e.name == name {
80                e.enabled = false;
81            }
82        }
83    }
84
85    pub fn enabled_names(&self) -> Vec<&str> {
86        self.entries
87            .iter()
88            .filter(|e| e.enabled)
89            .map(|e| e.name.as_str())
90            .collect()
91    }
92
93    pub fn sorted_by_priority(&self) -> Vec<&CmdEntry> {
94        let mut v: Vec<&CmdEntry> = self.entries.iter().collect();
95        v.sort_by(|a, b| b.priority.cmp(&a.priority));
96        v
97    }
98
99    pub fn by_tag(&self, tag: &str) -> Vec<&CmdEntry> {
100        self.entries
101            .iter()
102            .filter(|e| e.tag.as_deref() == Some(tag))
103            .collect()
104    }
105
106    pub fn remove(&mut self, name: &str) -> bool {
107        let before = self.entries.len();
108        self.entries.retain(|e| e.name != name);
109        self.entries.len() < before
110    }
111
112    pub fn clear(&mut self) {
113        self.entries.clear();
114    }
115
116    pub fn contains(&self, name: &str) -> bool {
117        self.entries.iter().any(|e| e.name == name)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn new_is_empty() {
127        let cl = CommandList::new();
128        assert!(cl.is_empty());
129        assert_eq!(cl.len(), 0);
130    }
131
132    #[test]
133    fn push_and_get() {
134        let mut cl = CommandList::new();
135        cl.push("draw", CmdPriority::Normal);
136        assert_eq!(cl.len(), 1);
137        assert_eq!(cl.get(0).expect("should succeed").name, "draw");
138        assert!(cl.get(0).expect("should succeed").enabled);
139    }
140
141    #[test]
142    fn contains_works() {
143        let mut cl = CommandList::new();
144        cl.push("update", CmdPriority::High);
145        assert!(cl.contains("update"));
146        assert!(!cl.contains("missing"));
147    }
148
149    #[test]
150    fn enable_disable() {
151        let mut cl = CommandList::new();
152        cl.push("tick", CmdPriority::Normal);
153        cl.disable("tick");
154        assert!(!cl.get(0).expect("should succeed").enabled);
155        cl.enable("tick");
156        assert!(cl.get(0).expect("should succeed").enabled);
157    }
158
159    #[test]
160    fn enabled_names_filters() {
161        let mut cl = CommandList::new();
162        cl.push("a", CmdPriority::Normal);
163        cl.push("b", CmdPriority::Low);
164        cl.disable("b");
165        let names = cl.enabled_names();
166        assert_eq!(names, vec!["a"]);
167    }
168
169    #[test]
170    fn sorted_by_priority_descending() {
171        let mut cl = CommandList::new();
172        cl.push("lo", CmdPriority::Low);
173        cl.push("hi", CmdPriority::High);
174        cl.push("nm", CmdPriority::Normal);
175        let sorted = cl.sorted_by_priority();
176        assert_eq!(sorted[0].priority, CmdPriority::High);
177        assert_eq!(sorted[2].priority, CmdPriority::Low);
178    }
179
180    #[test]
181    fn push_tagged_and_by_tag() {
182        let mut cl = CommandList::new();
183        cl.push_tagged("render_a", CmdPriority::Normal, "render");
184        cl.push_tagged("render_b", CmdPriority::Normal, "render");
185        cl.push("logic", CmdPriority::Normal);
186        let tagged = cl.by_tag("render");
187        assert_eq!(tagged.len(), 2);
188    }
189
190    #[test]
191    fn remove_entry() {
192        let mut cl = CommandList::new();
193        cl.push("x", CmdPriority::Normal);
194        assert!(cl.remove("x"));
195        assert!(cl.is_empty());
196        assert!(!cl.remove("x"));
197    }
198
199    #[test]
200    fn clear_empties_list() {
201        let mut cl = CommandList::new();
202        cl.push("a", CmdPriority::Normal);
203        cl.push("b", CmdPriority::High);
204        cl.clear();
205        assert!(cl.is_empty());
206    }
207
208    #[test]
209    fn multiple_same_name_priority() {
210        let mut cl = CommandList::new();
211        cl.push("dup", CmdPriority::Normal);
212        cl.push("dup", CmdPriority::High);
213        assert_eq!(cl.len(), 2);
214        cl.disable("dup");
215        assert!(cl.enabled_names().is_empty());
216    }
217}