hojicha_core/
optimized_event.rs

1//! Zero-cost Event optimizations with inline storage
2//!
3//! This module provides performance-optimized event types that minimize allocations
4//! and provide zero-cost abstractions for common event patterns.
5
6use crate::event::{KeyEvent, MouseEvent};
7use smallvec::SmallVec;
8use std::mem;
9
10/// Size threshold for inline message storage (in bytes)
11const INLINE_SIZE: usize = 24;
12
13/// Zero-cost event wrapper with inline storage for small messages
14#[derive(Debug, Clone)]
15pub enum OptimizedEvent<M> {
16    /// A keyboard event - stored inline, no allocation
17    Key(KeyEvent),
18    /// A mouse event - stored inline, no allocation
19    Mouse(MouseEvent),
20    /// Terminal was resized - stored inline, no allocation
21    Resize {
22        /// Terminal width in columns
23        width: u16,
24        /// Terminal height in rows
25        height: u16,
26    },
27    /// A tick event - stored inline, no allocation
28    Tick,
29    /// User-defined message with inline storage optimization
30    User(InlineMessage<M>),
31    /// Request to quit the program - stored inline, no allocation
32    Quit,
33    /// Terminal gained focus - stored inline, no allocation
34    Focus,
35    /// Terminal lost focus - stored inline, no allocation
36    Blur,
37    /// Program suspend request (Ctrl+Z) - stored inline, no allocation
38    Suspend,
39    /// Program resumed from suspend - stored inline, no allocation
40    Resume,
41    /// Bracketed paste event with COW string for efficiency
42    Paste(std::borrow::Cow<'static, str>),
43    /// Internal event to trigger external process execution - stored inline, no allocation
44    #[doc(hidden)]
45    ExecProcess,
46}
47
48/// Message storage that inlines small messages to avoid allocations
49#[derive(Debug, Clone)]
50pub enum InlineMessage<M> {
51    /// Small message stored inline (no heap allocation)
52    Inline(M),
53    /// Large message stored on heap (only when necessary)
54    Boxed(Box<M>),
55}
56
57impl<M> InlineMessage<M> {
58    /// Create a new inline message, automatically choosing storage type
59    #[inline(always)]
60    pub fn new(msg: M) -> Self {
61        if mem::size_of::<M>() <= INLINE_SIZE {
62            Self::Inline(msg)
63        } else {
64            Self::Boxed(Box::new(msg))
65        }
66    }
67
68    /// Get reference to the message
69    #[inline(always)]
70    pub fn get(&self) -> &M {
71        match self {
72            Self::Inline(msg) => msg,
73            Self::Boxed(msg) => msg.as_ref(),
74        }
75    }
76
77    /// Take the message out of storage
78    #[inline(always)]
79    pub fn into_inner(self) -> M {
80        match self {
81            Self::Inline(msg) => msg,
82            Self::Boxed(msg) => *msg,
83        }
84    }
85
86    /// Check if message is stored inline (zero allocation)
87    #[inline(always)]
88    pub fn is_inline(&self) -> bool {
89        matches!(self, Self::Inline(_))
90    }
91}
92
93impl<M> OptimizedEvent<M> {
94    /// Create a new user event with automatic storage optimization
95    #[inline(always)]
96    pub fn user(msg: M) -> Self {
97        Self::User(InlineMessage::new(msg))
98    }
99
100    /// Check if this is a key event
101    #[inline(always)]
102    pub const fn is_key(&self) -> bool {
103        matches!(self, Self::Key(_))
104    }
105
106    /// Check if this is a specific key press
107    #[inline(always)]
108    pub fn is_key_press(&self, key: crate::event::Key) -> bool {
109        matches!(self, Self::Key(k) if k.key == key)
110    }
111
112    /// Get the key event if this is a key event
113    #[inline(always)]
114    pub const fn as_key(&self) -> Option<&KeyEvent> {
115        match self {
116            Self::Key(k) => Some(k),
117            _ => None,
118        }
119    }
120
121    /// Check if this is a mouse event
122    #[inline(always)]
123    pub const fn is_mouse(&self) -> bool {
124        matches!(self, Self::Mouse(_))
125    }
126
127    /// Get the mouse event if this is a mouse event
128    #[inline(always)]
129    pub const fn as_mouse(&self) -> Option<&MouseEvent> {
130        match self {
131            Self::Mouse(m) => Some(m),
132            _ => None,
133        }
134    }
135
136    /// Check if this is a user message
137    #[inline(always)]
138    pub const fn is_user(&self) -> bool {
139        matches!(self, Self::User(_))
140    }
141
142    /// Get the user message if this is a user event
143    #[inline(always)]
144    pub fn as_user(&self) -> Option<&M> {
145        match self {
146            Self::User(msg) => Some(msg.get()),
147            _ => None,
148        }
149    }
150
151    /// Take the user message if this is a user event
152    #[inline(always)]
153    pub fn into_user(self) -> Option<M> {
154        match self {
155            Self::User(msg) => Some(msg.into_inner()),
156            _ => None,
157        }
158    }
159
160    /// Check if this is a quit event
161    #[inline(always)]
162    pub const fn is_quit(&self) -> bool {
163        matches!(self, Self::Quit)
164    }
165
166    /// Check if this is a tick event
167    #[inline(always)]
168    pub const fn is_tick(&self) -> bool {
169        matches!(self, Self::Tick)
170    }
171
172    /// Check if this is a resize event
173    #[inline(always)]
174    pub const fn is_resize(&self) -> bool {
175        matches!(self, Self::Resize { .. })
176    }
177
178    /// Get resize dimensions if this is a resize event
179    #[inline(always)]
180    pub const fn as_resize(&self) -> Option<(u16, u16)> {
181        match self {
182            Self::Resize { width, height } => Some((*width, *height)),
183            _ => None,
184        }
185    }
186}
187
188/// Event batch for efficient processing of multiple events
189#[derive(Debug, Clone)]
190pub struct EventBatch<M> {
191    /// Events stored in a small vector to avoid allocations for small batches
192    events: SmallVec<[OptimizedEvent<M>; 8]>,
193}
194
195impl<M> EventBatch<M> {
196    /// Create a new empty event batch
197    #[inline]
198    pub fn new() -> Self {
199        Self {
200            events: SmallVec::new(),
201        }
202    }
203
204    /// Create a new event batch with capacity
205    #[inline]
206    pub fn with_capacity(capacity: usize) -> Self {
207        Self {
208            events: SmallVec::with_capacity(capacity),
209        }
210    }
211
212    /// Add an event to the batch
213    #[inline]
214    pub fn push(&mut self, event: OptimizedEvent<M>) {
215        self.events.push(event);
216    }
217
218    /// Get the number of events in the batch
219    #[inline]
220    pub fn len(&self) -> usize {
221        self.events.len()
222    }
223
224    /// Check if the batch is empty
225    #[inline]
226    pub fn is_empty(&self) -> bool {
227        self.events.is_empty()
228    }
229
230    /// Iterate over events in the batch
231    #[inline]
232    pub fn iter(&self) -> std::slice::Iter<'_, OptimizedEvent<M>> {
233        self.events.iter()
234    }
235
236    /// Drain all events from the batch
237    #[inline]
238    pub fn drain(&mut self) -> smallvec::Drain<'_, [OptimizedEvent<M>; 8]> {
239        self.events.drain(..)
240    }
241
242    /// Clear the batch
243    #[inline]
244    pub fn clear(&mut self) {
245        self.events.clear();
246    }
247
248    /// Check if batch is using inline storage (no heap allocations)
249    #[inline]
250    pub fn is_inline(&self) -> bool {
251        !self.events.spilled()
252    }
253}
254
255impl<M> Default for EventBatch<M> {
256    #[inline]
257    fn default() -> Self {
258        Self::new()
259    }
260}
261
262impl<M> IntoIterator for EventBatch<M> {
263    type Item = OptimizedEvent<M>;
264    type IntoIter = smallvec::IntoIter<[OptimizedEvent<M>; 8]>;
265
266    #[inline]
267    fn into_iter(self) -> Self::IntoIter {
268        self.events.into_iter()
269    }
270}
271
272/// Event coalescing for reducing redundant events
273pub struct EventCoalescer<M> {
274    /// Buffer for coalescing events
275    buffer: SmallVec<[OptimizedEvent<M>; 16]>,
276    /// Track last resize event to coalesce multiple resize events
277    last_resize: Option<(u16, u16)>,
278}
279
280impl<M> EventCoalescer<M> {
281    /// Create a new event coalescer
282    #[inline]
283    pub fn new() -> Self {
284        Self {
285            buffer: SmallVec::new(),
286            last_resize: None,
287        }
288    }
289
290    /// Add an event, coalescing with existing events where possible
291    pub fn push(&mut self, event: OptimizedEvent<M>) {
292        match event {
293            // Coalesce resize events - only keep the latest
294            OptimizedEvent::Resize { width, height } => {
295                self.last_resize = Some((width, height));
296            }
297            // Coalesce tick events - only keep one tick per batch
298            OptimizedEvent::Tick => {
299                if !self.buffer.iter().any(|e| e.is_tick()) {
300                    self.buffer.push(event);
301                }
302            }
303            // Don't coalesce other events
304            _ => {
305                self.buffer.push(event);
306            }
307        }
308    }
309
310    /// Finish coalescing and return the batch
311    pub fn finish(&mut self) -> EventBatch<M> {
312        // Add the final resize event if any
313        if let Some((width, height)) = self.last_resize.take() {
314            self.buffer.push(OptimizedEvent::Resize { width, height });
315        }
316
317        let mut batch = EventBatch::with_capacity(self.buffer.len());
318        batch.events.extend(self.buffer.drain(..));
319        batch
320    }
321
322    /// Clear the coalescer
323    #[inline]
324    pub fn clear(&mut self) {
325        self.buffer.clear();
326        self.last_resize = None;
327    }
328}
329
330impl<M> Default for EventCoalescer<M> {
331    #[inline]
332    fn default() -> Self {
333        Self::new()
334    }
335}
336
337/// Convert from the standard Event to OptimizedEvent
338impl<M> From<crate::event::Event<M>> for OptimizedEvent<M> {
339    #[inline]
340    fn from(event: crate::event::Event<M>) -> Self {
341        match event {
342            crate::event::Event::Key(k) => Self::Key(k),
343            crate::event::Event::Mouse(m) => Self::Mouse(m),
344            crate::event::Event::Resize { width, height } => Self::Resize { width, height },
345            crate::event::Event::Tick => Self::Tick,
346            crate::event::Event::User(m) => Self::user(m),
347            crate::event::Event::Quit => Self::Quit,
348            crate::event::Event::Focus => Self::Focus,
349            crate::event::Event::Blur => Self::Blur,
350            crate::event::Event::Suspend => Self::Suspend,
351            crate::event::Event::Resume => Self::Resume,
352            crate::event::Event::Paste(text) => Self::Paste(text.into()),
353            crate::event::Event::ExecProcess => Self::ExecProcess,
354        }
355    }
356}
357
358/// Convert from OptimizedEvent back to standard Event
359impl<M> From<OptimizedEvent<M>> for crate::event::Event<M> {
360    #[inline]
361    fn from(event: OptimizedEvent<M>) -> Self {
362        match event {
363            OptimizedEvent::Key(k) => Self::Key(k),
364            OptimizedEvent::Mouse(m) => Self::Mouse(m),
365            OptimizedEvent::Resize { width, height } => Self::Resize { width, height },
366            OptimizedEvent::Tick => Self::Tick,
367            OptimizedEvent::User(m) => Self::User(m.into_inner()),
368            OptimizedEvent::Quit => Self::Quit,
369            OptimizedEvent::Focus => Self::Focus,
370            OptimizedEvent::Blur => Self::Blur,
371            OptimizedEvent::Suspend => Self::Suspend,
372            OptimizedEvent::Resume => Self::Resume,
373            OptimizedEvent::Paste(text) => Self::Paste(text.into_owned()),
374            OptimizedEvent::ExecProcess => Self::ExecProcess,
375        }
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_inline_message_small() {
385        let msg = 42u32;
386        let inline_msg = InlineMessage::new(msg);
387        assert!(inline_msg.is_inline());
388        assert_eq!(*inline_msg.get(), 42);
389    }
390
391    #[test]
392    fn test_inline_message_large() {
393        // Create a large struct that exceeds INLINE_SIZE
394        #[derive(Debug, PartialEq, Clone)]
395        struct LargeStruct([u8; 32]); // This exceeds INLINE_SIZE of 24
396
397        let msg = LargeStruct([0u8; 32]);
398        let inline_msg = InlineMessage::new(msg.clone());
399        // Large messages should be boxed
400        assert!(!inline_msg.is_inline());
401        assert_eq!(*inline_msg.get(), msg);
402    }
403
404    #[test]
405    fn test_event_coalescing() {
406        let mut coalescer: EventCoalescer<()> = EventCoalescer::new();
407
408        // Add multiple resize events
409        coalescer.push(OptimizedEvent::Resize {
410            width: 80,
411            height: 24,
412        });
413        coalescer.push(OptimizedEvent::Resize {
414            width: 100,
415            height: 30,
416        });
417        coalescer.push(OptimizedEvent::Resize {
418            width: 120,
419            height: 40,
420        });
421
422        // Add multiple ticks
423        coalescer.push(OptimizedEvent::Tick);
424        coalescer.push(OptimizedEvent::Tick);
425
426        let batch = coalescer.finish();
427
428        // Should have only one resize (the last one) and one tick
429        assert_eq!(batch.len(), 2);
430
431        let events: Vec<_> = batch.into_iter().collect();
432        assert!(events.iter().any(|e| matches!(e, OptimizedEvent::Tick)));
433        assert!(events.iter().any(|e| matches!(
434            e,
435            OptimizedEvent::Resize {
436                width: 120,
437                height: 40
438            }
439        )));
440    }
441
442    #[test]
443    fn test_event_batch_inline_storage() {
444        let mut batch = EventBatch::new();
445
446        // Add a few events - should stay inline
447        batch.push(OptimizedEvent::Tick);
448        batch.push(OptimizedEvent::user(42u32));
449        batch.push(OptimizedEvent::Quit);
450
451        assert!(batch.is_inline()); // No heap allocation
452        assert_eq!(batch.len(), 3);
453    }
454
455    #[test]
456    fn test_zero_cost_event_checks() {
457        let event = OptimizedEvent::user(String::from("test"));
458
459        // These should all be constant time, no allocations
460        assert!(event.is_user());
461        assert!(!event.is_key());
462        assert!(!event.is_mouse());
463        assert_eq!(event.as_user().unwrap(), "test");
464    }
465}