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() {
78            EventKind::Quit
79        } else if event.is_key() {
80            EventKind::Key
81        } else if event.is_mouse() {
82            EventKind::Mouse
83        } else if event.is_user() {
84            EventKind::User
85        } else if event.is_resize() {
86            EventKind::Resize
87        } else if event.is_tick() {
88            EventKind::Tick
89        } else if event.is_paste() {
90            EventKind::Paste
91        } else if event.is_focus() {
92            EventKind::Focus
93        } else if event.is_blur() {
94            EventKind::Blur
95        } else if event.is_suspend() {
96            EventKind::Suspend
97        } else if event.is_resume() {
98            EventKind::Resume
99        } else {
100            EventKind::ExecProcess
101        }
102    }
103}
104
105/// A priority mapper that doesn't require unsafe code
106pub trait SafePriorityMapper<M: Message>: Send + Sync {
107    /// Map an event to its priority
108    fn map_priority(&self, event: &Event<M>) -> Priority;
109}
110
111/// Default priority mapper implementation
112pub struct DefaultPriorityMapper;
113
114impl<M: Message> SafePriorityMapper<M> for DefaultPriorityMapper {
115    fn map_priority(&self, event: &Event<M>) -> Priority {
116        detect_priority(event)
117    }
118}
119
120/// Function type for mapping events to priorities
121type PriorityMapperFn<M> = Box<dyn Fn(&Event<M>) -> Option<Priority> + Send + Sync>;
122
123/// Custom priority mapper that can override specific event priorities
124pub struct CustomPriorityMapper<M: Message> {
125    mapper: PriorityMapperFn<M>,
126}
127
128impl<M: Message> CustomPriorityMapper<M> {
129    /// Create a new custom priority mapper
130    pub fn new<F>(mapper: F) -> Self
131    where
132        F: Fn(&Event<M>) -> Option<Priority> + Send + Sync + 'static,
133    {
134        Self {
135            mapper: Box::new(mapper),
136        }
137    }
138}
139
140impl<M: Message> SafePriorityMapper<M> for CustomPriorityMapper<M> {
141    fn map_priority(&self, event: &Event<M>) -> Priority {
142        // Try custom mapper first, fall back to default
143        (self.mapper)(event).unwrap_or_else(|| detect_priority(event))
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use hojicha_core::event::{Event, Key, KeyEvent, KeyModifiers};
151
152    #[test]
153    fn test_priority_detection() {
154        // High priority events
155        assert_eq!(detect_priority::<()>(&Event::Quit), Priority::High);
156        assert_eq!(
157            detect_priority::<()>(&Event::Key(KeyEvent::new(
158                Key::Char('a'),
159                KeyModifiers::empty()
160            ))),
161            Priority::High
162        );
163
164        // Normal priority events
165        assert_eq!(
166            detect_priority::<String>(&Event::User("test".to_string())),
167            Priority::Normal
168        );
169
170        // Low priority events
171        assert_eq!(detect_priority::<()>(&Event::Tick), Priority::Low);
172        assert_eq!(
173            detect_priority::<()>(&Event::Resize {
174                width: 80,
175                height: 24
176            }),
177            Priority::Low
178        );
179    }
180
181    #[test]
182    fn test_event_kind() {
183        let quit_event: Event<()> = Event::Quit;
184        assert_eq!(
185            EventKind::from_event(&quit_event).priority(),
186            Priority::High
187        );
188
189        let tick_event: Event<()> = Event::Tick;
190        assert_eq!(EventKind::from_event(&tick_event).priority(), Priority::Low);
191    }
192
193    #[test]
194    fn test_custom_mapper() {
195        // Create a custom mapper that makes all User events high priority
196        let mapper = CustomPriorityMapper::new(|event| {
197            if event.is_user() {
198                Some(Priority::High)
199            } else {
200                None
201            }
202        });
203
204        let user_event = Event::User("important".to_string());
205        assert_eq!(mapper.map_priority(&user_event), Priority::High);
206
207        let tick_event: Event<String> = Event::Tick;
208        assert_eq!(mapper.map_priority(&tick_event), Priority::Low);
209    }
210}