dampen_core/handler/
mod.rs

1//! Handler system for event dispatch
2
3use std::any::Any;
4use std::collections::{HashMap, HashSet};
5use std::sync::{Arc, RwLock};
6
7/// Registry of event handlers
8#[derive(Clone, Debug)]
9pub struct HandlerRegistry {
10    handlers: Arc<RwLock<HashMap<String, HandlerEntry>>>,
11}
12
13/// Entry in the handler registry
14#[derive(Clone)]
15#[allow(clippy::type_complexity)]
16pub enum HandlerEntry {
17    /// Simple handler: `fn(&mut Model)`
18    Simple(Arc<dyn Fn(&mut dyn Any) + Send + Sync>),
19
20    /// Handler with value: `fn(&mut Model, T)`
21    WithValue(Arc<dyn Fn(&mut dyn Any, Box<dyn Any>) + Send + Sync>),
22
23    /// Handler returning command: `fn(&mut Model) -> Command<Message>`
24    WithCommand(Arc<dyn Fn(&mut dyn Any) -> Box<dyn Any> + Send + Sync>),
25}
26
27impl std::fmt::Debug for HandlerEntry {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            HandlerEntry::Simple(_) => f.write_str("Simple(handler)"),
31            HandlerEntry::WithValue(_) => f.write_str("WithValue(handler)"),
32            HandlerEntry::WithCommand(_) => f.write_str("WithCommand(handler)"),
33        }
34    }
35}
36
37impl HandlerRegistry {
38    /// Create a new empty handler registry
39    pub fn new() -> Self {
40        Self {
41            handlers: Arc::new(RwLock::new(HashMap::new())),
42        }
43    }
44
45    /// Register a simple handler
46    pub fn register_simple<F>(&self, name: &str, handler: F)
47    where
48        F: Fn(&mut dyn Any) + Send + Sync + 'static,
49    {
50        if let Ok(mut handlers) = self.handlers.write() {
51            handlers.insert(name.to_string(), HandlerEntry::Simple(Arc::new(handler)));
52        }
53    }
54
55    /// Register a handler with a value parameter
56    pub fn register_with_value<F>(&self, name: &str, handler: F)
57    where
58        F: Fn(&mut dyn Any, Box<dyn Any>) + Send + Sync + 'static,
59    {
60        if let Ok(mut handlers) = self.handlers.write() {
61            handlers.insert(name.to_string(), HandlerEntry::WithValue(Arc::new(handler)));
62        }
63    }
64
65    /// Register a handler that returns a command
66    pub fn register_with_command<F>(&self, name: &str, handler: F)
67    where
68        F: Fn(&mut dyn Any) -> Box<dyn Any> + Send + Sync + 'static,
69    {
70        if let Ok(mut handlers) = self.handlers.write() {
71            handlers.insert(
72                name.to_string(),
73                HandlerEntry::WithCommand(Arc::new(handler)),
74            );
75        }
76    }
77
78    /// Look up a handler by name
79    pub fn get(&self, name: &str) -> Option<HandlerEntry> {
80        self.handlers.read().ok()?.get(name).cloned()
81    }
82
83    /// Dispatches a handler by name, executing it with the provided model and optional value.
84    ///
85    /// This is a convenience method that combines `get()` and handler invocation.
86    ///
87    /// # Arguments
88    ///
89    /// * `handler_name` - Name of the handler to dispatch
90    /// * `model` - Mutable reference to the model (as `&mut dyn Any`)
91    /// * `value` - Optional string value passed to WithValue handlers
92    ///
93    /// # Example
94    ///
95    /// ```rust,ignore
96    /// use dampen_core::HandlerRegistry;
97    ///
98    /// let registry = HandlerRegistry::new();
99    /// registry.register_simple("greet", |model| {
100    ///     let model = model.downcast_mut::<MyModel>().unwrap();
101    ///     model.count += 1;
102    /// });
103    ///
104    /// let model = &mut MyModel { count: 0 } as &mut dyn std::any::Any;
105    /// registry.dispatch("greet", model, None);
106    /// ```
107    pub fn dispatch(&self, handler_name: &str, model: &mut dyn Any, value: Option<String>) {
108        if let Some(entry) = self.get(handler_name) {
109            match entry {
110                HandlerEntry::Simple(h) => h(model),
111                HandlerEntry::WithValue(h) => {
112                    let val = value.unwrap_or_default();
113                    h(model, Box::new(val));
114                }
115                HandlerEntry::WithCommand(h) => {
116                    h(model);
117                }
118            }
119        }
120    }
121
122    /// Dispatches a handler by name and returns any command/task it produces.
123    ///
124    /// This method is similar to `dispatch()` but returns the command/task from
125    /// `WithCommand` handlers instead of discarding it. This is essential for
126    /// integrating with the Elm/MVU pattern where handlers can return tasks.
127    ///
128    /// # Arguments
129    ///
130    /// * `handler_name` - Name of the handler to dispatch
131    /// * `model` - Mutable reference to the model (as `&mut dyn Any`)
132    /// * `value` - Optional string value passed to WithValue handlers
133    ///
134    /// # Returns
135    ///
136    /// * `Some(Box<dyn Any>)` - The command/task from a `WithCommand` handler
137    /// * `None` - For `Simple` and `WithValue` handlers, or if handler not found
138    ///
139    /// # Example
140    ///
141    /// ```rust,ignore
142    /// use dampen_core::HandlerRegistry;
143    /// use iced::Task;
144    ///
145    /// let registry = HandlerRegistry::new();
146    /// registry.register_with_command("navigate", |model| {
147    ///     let model = model.downcast_mut::<MyModel>().unwrap();
148    ///     Box::new(Task::done(Message::SwitchView))
149    /// });
150    ///
151    /// let model = &mut MyModel::default() as &mut dyn std::any::Any;
152    /// if let Some(boxed_task) = registry.dispatch_with_command("navigate", model, None) {
153    ///     if let Ok(task) = boxed_task.downcast::<Task<Message>>() {
154    ///         return *task;
155    ///     }
156    /// }
157    /// ```
158    pub fn dispatch_with_command(
159        &self,
160        handler_name: &str,
161        model: &mut dyn Any,
162        value: Option<String>,
163    ) -> Option<Box<dyn Any>> {
164        if let Some(entry) = self.get(handler_name) {
165            match entry {
166                HandlerEntry::Simple(h) => {
167                    h(model);
168                    None
169                }
170                HandlerEntry::WithValue(h) => {
171                    let val = value.unwrap_or_default();
172                    h(model, Box::new(val));
173                    None
174                }
175                HandlerEntry::WithCommand(h) => Some(h(model)),
176            }
177        } else {
178            None
179        }
180    }
181
182    /// Check if a handler exists
183    pub fn contains(&self, name: &str) -> bool {
184        if let Ok(handlers) = self.handlers.read() {
185            handlers.contains_key(name)
186        } else {
187            false
188        }
189    }
190}
191
192impl Default for HandlerRegistry {
193    fn default() -> Self {
194        Self::new()
195    }
196}
197
198/// Handler metadata for compile-time validation
199#[derive(Debug, Clone, PartialEq)]
200pub struct HandlerSignature {
201    /// Handler name
202    pub name: String,
203
204    /// Parameter type if applicable
205    pub param_type: Option<String>,
206
207    /// Whether handler returns Command
208    pub returns_command: bool,
209}
210
211/// Build-time analysis structure for circular dependency detection
212#[derive(Debug, Clone)]
213pub struct HandlerCallGraph {
214    /// Map of handler name to its dependencies
215    dependencies: HashMap<String, Vec<String>>,
216}
217
218impl HandlerCallGraph {
219    /// Create a new empty call graph
220    pub fn new() -> Self {
221        Self {
222            dependencies: HashMap::new(),
223        }
224    }
225
226    /// Add a dependency edge (from depends on to)
227    pub fn add_dependency(&mut self, from: &str, to: &str) {
228        self.dependencies
229            .entry(from.to_string())
230            .or_default()
231            .push(to.to_string());
232    }
233
234    /// Detect if adding edge would create a cycle
235    pub fn would_create_cycle(&self, from: &str, to: &str) -> bool {
236        // Check if 'to' can reach 'from' (which would create a cycle)
237        let mut visited = HashSet::new();
238        self.can_reach(to, from, &mut visited)
239    }
240
241    /// Check if 'from' can reach 'to' via dependencies
242    fn can_reach(&self, from: &str, to: &str, visited: &mut HashSet<String>) -> bool {
243        if from == to {
244            return true;
245        }
246
247        if visited.contains(from) {
248            return false;
249        }
250
251        visited.insert(from.to_string());
252
253        if let Some(deps) = self.dependencies.get(from) {
254            for dep in deps {
255                if self.can_reach(dep, to, visited) {
256                    return true;
257                }
258            }
259        }
260
261        false
262    }
263
264    /// Get all handlers that depend on the given handler
265    pub fn dependents_of(&self, handler: &str) -> Vec<String> {
266        self.dependencies
267            .iter()
268            .filter_map(|(k, v)| {
269                if v.contains(&handler.to_string()) {
270                    Some(k.clone())
271                } else {
272                    None
273                }
274            })
275            .collect()
276    }
277
278    /// Detect cycles in the call graph using DFS
279    pub fn detect_cycles(&self) -> Option<Vec<String>> {
280        let mut visited = HashSet::new();
281        let mut recursion_stack = HashSet::new();
282        let mut path = Vec::new();
283
284        for handler in self.dependencies.keys() {
285            if !visited.contains(handler) {
286                if let Some(cycle) =
287                    self.dfs_detect_cycle(handler, &mut visited, &mut recursion_stack, &mut path)
288                {
289                    return Some(cycle);
290                }
291            }
292        }
293
294        None
295    }
296
297    fn dfs_detect_cycle(
298        &self,
299        handler: &str,
300        visited: &mut HashSet<String>,
301        recursion_stack: &mut HashSet<String>,
302        path: &mut Vec<String>,
303    ) -> Option<Vec<String>> {
304        visited.insert(handler.to_string());
305        recursion_stack.insert(handler.to_string());
306        path.push(handler.to_string());
307
308        if let Some(deps) = self.dependencies.get(handler) {
309            for dep in deps {
310                if !visited.contains(dep) {
311                    if let Some(cycle) = self.dfs_detect_cycle(dep, visited, recursion_stack, path)
312                    {
313                        return Some(cycle);
314                    }
315                } else if recursion_stack.contains(dep) {
316                    // Found a cycle - construct the cycle path
317                    if let Some(cycle_start) = path.iter().position(|h| h == dep) {
318                        let mut cycle = path[cycle_start..].to_vec();
319                        cycle.push(dep.to_string());
320                        return Some(cycle);
321                    }
322                }
323            }
324        }
325
326        path.pop();
327        recursion_stack.remove(handler);
328        None
329    }
330}
331
332impl Default for HandlerCallGraph {
333    fn default() -> Self {
334        Self::new()
335    }
336}