hojicha_runtime/
safe_priority.rs

1//! Safe priority detection without unsafe transmutes
2//!
3//! This module provides a safe way to detect event priority without
4//! using unsafe memory transmutation.
5
6use crate::priority_queue::Priority;
7use hojicha_core::core::Message;
8use hojicha_core::event::Event;
9
10/// Detect the priority of an event without unsafe operations
11pub fn detect_priority<M: Message>(event: &Event<M>) -> Priority {
12    // Use helper methods for cleaner priority detection
13    if event.is_quit() || event.is_key() || event.is_suspend() || event.is_resume() {
14        Priority::High
15    } else if event.is_resize() || event.is_tick() {
16        Priority::Low  
17    } else {
18        Priority::Normal
19    }
20}
21
22/// Type-erased event for priority detection
23///
24/// This allows us to work with events without knowing their message type,
25/// avoiding the need for unsafe transmutes.
26#[derive(Debug, Clone)]
27pub enum EventKind {
28    /// Quit event - terminates the program
29    Quit,
30    /// Keyboard input event
31    Key,
32    /// Mouse input event
33    Mouse,
34    /// User-defined message event
35    User,
36    /// Terminal resize event
37    Resize,
38    /// Timer tick event
39    Tick,
40    /// Text paste event
41    Paste,
42    /// Terminal focus gained event
43    Focus,
44    /// Terminal focus lost event
45    Blur,
46    /// Process suspension event
47    Suspend,
48    /// Process resumption event
49    Resume,
50    /// Process execution event
51    ExecProcess,
52}
53
54impl EventKind {
55    /// Get the priority for this event kind
56    pub fn priority(&self) -> Priority {
57        match self {
58            EventKind::Quit => Priority::High,
59            EventKind::Key => Priority::High,
60            EventKind::Suspend => Priority::High,
61            EventKind::Resume => Priority::High,
62
63            EventKind::Mouse => Priority::Normal,
64            EventKind::User => Priority::Normal,
65            EventKind::Paste => Priority::Normal,
66            EventKind::Focus => Priority::Normal,
67            EventKind::Blur => Priority::Normal,
68            EventKind::ExecProcess => Priority::Normal,
69
70            EventKind::Resize => Priority::Low,
71            EventKind::Tick => Priority::Low,
72        }
73    }
74
75    /// Create from an event
76    pub fn from_event<M: Message>(event: &Event<M>) -> Self {
77        if event.is_quit() { EventKind::Quit }
78        else if event.is_key() { EventKind::Key }
79        else if event.is_mouse() { EventKind::Mouse }
80        else if event.is_user() { EventKind::User }
81        else if event.is_resize() { EventKind::Resize }
82        else if event.is_tick() { EventKind::Tick }
83        else if event.is_paste() { EventKind::Paste }
84        else if event.is_focus() { EventKind::Focus }
85        else if event.is_blur() { EventKind::Blur }
86        else if event.is_suspend() { EventKind::Suspend }
87        else if event.is_resume() { EventKind::Resume }
88        else { EventKind::ExecProcess }
89    }
90}
91
92/// A priority mapper that doesn't require unsafe code
93pub trait SafePriorityMapper<M: Message>: Send + Sync {
94    /// Map an event to its priority
95    fn map_priority(&self, event: &Event<M>) -> Priority;
96}
97
98/// Default priority mapper implementation
99pub struct DefaultPriorityMapper;
100
101impl<M: Message> SafePriorityMapper<M> for DefaultPriorityMapper {
102    fn map_priority(&self, event: &Event<M>) -> Priority {
103        detect_priority(event)
104    }
105}
106
107/// Custom priority mapper that can override specific event priorities
108pub struct CustomPriorityMapper<M: Message> {
109    mapper: Box<dyn Fn(&Event<M>) -> Option<Priority> + Send + Sync>,
110}
111
112impl<M: Message> CustomPriorityMapper<M> {
113    /// Create a new custom priority mapper
114    pub fn new<F>(mapper: F) -> Self
115    where
116        F: Fn(&Event<M>) -> Option<Priority> + Send + Sync + 'static,
117    {
118        Self {
119            mapper: Box::new(mapper),
120        }
121    }
122}
123
124impl<M: Message> SafePriorityMapper<M> for CustomPriorityMapper<M> {
125    fn map_priority(&self, event: &Event<M>) -> Priority {
126        // Try custom mapper first, fall back to default
127        (self.mapper)(event).unwrap_or_else(|| detect_priority(event))
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use hojicha_core::event::{Event, Key, KeyEvent, KeyModifiers};
135
136    #[test]
137    fn test_priority_detection() {
138        // High priority events
139        assert_eq!(detect_priority::<()>(&Event::Quit), Priority::High);
140        assert_eq!(
141            detect_priority::<()>(&Event::Key(KeyEvent::new(
142                Key::Char('a'),
143                KeyModifiers::empty()
144            ))),
145            Priority::High
146        );
147
148        // Normal priority events
149        assert_eq!(
150            detect_priority::<String>(&Event::User("test".to_string())),
151            Priority::Normal
152        );
153
154        // Low priority events
155        assert_eq!(detect_priority::<()>(&Event::Tick), Priority::Low);
156        assert_eq!(
157            detect_priority::<()>(&Event::Resize {
158                width: 80,
159                height: 24
160            }),
161            Priority::Low
162        );
163    }
164
165    #[test]
166    fn test_event_kind() {
167        let quit_event: Event<()> = Event::Quit;
168        assert_eq!(
169            EventKind::from_event(&quit_event).priority(),
170            Priority::High
171        );
172
173        let tick_event: Event<()> = Event::Tick;
174        assert_eq!(EventKind::from_event(&tick_event).priority(), Priority::Low);
175    }
176
177    #[test]
178    fn test_custom_mapper() {
179        // Create a custom mapper that makes all User events high priority
180        let mapper = CustomPriorityMapper::new(|event| {
181            if event.is_user() {
182                Some(Priority::High)
183            } else {
184                None
185            }
186        });
187
188        let user_event = Event::User("important".to_string());
189        assert_eq!(mapper.map_priority(&user_event), Priority::High);
190
191        let tick_event: Event<String> = Event::Tick;
192        assert_eq!(mapper.map_priority(&tick_event), Priority::Low);
193    }
194}