Skip to main content

rush_sync_server/commands/
registry.rs

1use super::command::Command;
2use crate::core::prelude::*;
3use std::collections::HashMap;
4
5pub struct CommandRegistry {
6    commands: Vec<Box<dyn Command>>,
7    name_map: HashMap<String, usize>,
8    available_cache: std::sync::RwLock<Vec<usize>>,
9    cache_dirty: std::sync::atomic::AtomicBool,
10}
11
12impl CommandRegistry {
13    pub fn new() -> Self {
14        Self {
15            commands: Vec::new(),
16            name_map: HashMap::new(),
17            available_cache: std::sync::RwLock::new(Vec::new()),
18            cache_dirty: std::sync::atomic::AtomicBool::new(true),
19        }
20    }
21
22    pub fn register<T>(&mut self, command: T) -> &mut Self
23    where
24        T: Into<Box<dyn Command>>,
25    {
26        let boxed = command.into();
27        let name = boxed.name().to_lowercase();
28        let index = self.commands.len();
29
30        self.commands.push(boxed);
31        self.name_map.insert(name, index);
32
33        // Invalidate cache
34        self.cache_dirty
35            .store(true, std::sync::atomic::Ordering::Release);
36
37        self
38    }
39
40    pub fn find_command(&self, input: &str) -> Option<&dyn Command> {
41        let input = input.trim().to_lowercase();
42
43        // Exact match (fast path)
44        if let Some(&index) = self.name_map.get(&input) {
45            if let Some(cmd) = self.commands.get(index) {
46                if cmd.is_available() {
47                    return Some(cmd.as_ref());
48                }
49            }
50        }
51
52        // Cache-based pattern matching
53        self.update_available_cache_if_needed();
54
55        if let Ok(cache) = self.available_cache.read() {
56            for &index in cache.iter() {
57                if let Some(cmd) = self.commands.get(index) {
58                    if cmd.matches(&input) {
59                        return Some(cmd.as_ref());
60                    }
61                }
62            }
63        }
64
65        None
66    }
67
68    fn update_available_cache_if_needed(&self) {
69        if !self.cache_dirty.load(std::sync::atomic::Ordering::Acquire) {
70            return;
71        }
72
73        if let Ok(mut cache) = self.available_cache.write() {
74            cache.clear();
75            for (index, cmd) in self.commands.iter().enumerate() {
76                if cmd.is_available() {
77                    cache.push(index);
78                }
79            }
80            self.cache_dirty
81                .store(false, std::sync::atomic::Ordering::Release);
82        }
83    }
84
85    pub fn execute_sync(&self, command: &str, args: &[&str]) -> Option<Result<String>> {
86        self.find_command(command).map(|cmd| cmd.execute_sync(args))
87    }
88
89    pub async fn execute_async(&self, command: &str, args: &[&str]) -> Option<Result<String>> {
90        match self.find_command(command) {
91            Some(cmd) => Some(cmd.execute(args).await),
92            None => None,
93        }
94    }
95
96    pub fn list_commands(&self) -> Vec<(&str, &str)> {
97        self.update_available_cache_if_needed();
98
99        if let Ok(cache) = self.available_cache.read() {
100            cache
101                .iter()
102                .filter_map(|&index| {
103                    self.commands
104                        .get(index)
105                        .map(|cmd| (cmd.name(), cmd.description()))
106                })
107                .collect()
108        } else {
109            // Fallback on lock failure
110            self.commands
111                .iter()
112                .filter(|cmd| cmd.is_available())
113                .map(|cmd| (cmd.name(), cmd.description()))
114                .collect()
115        }
116    }
117
118    pub fn debug_info(&self) -> String {
119        format!(
120            "CommandRegistry: {} commands registered",
121            self.commands.len()
122        )
123    }
124
125    pub fn len(&self) -> usize {
126        self.commands.len()
127    }
128
129    pub fn is_empty(&self) -> bool {
130        self.commands.is_empty()
131    }
132}
133
134impl Default for CommandRegistry {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140// Enables both register(MyCommand::new()) and register(Box::new(MyCommand::new()))
141impl<T: Command> From<T> for Box<dyn Command> {
142    fn from(cmd: T) -> Self {
143        Box::new(cmd)
144    }
145}