use crate::event::{KeyEvent, MouseEvent};
use smallvec::SmallVec;
use std::mem;
const INLINE_SIZE: usize = 24;
#[derive(Debug, Clone)]
pub enum OptimizedEvent<M> {
Key(KeyEvent),
Mouse(MouseEvent),
Resize {
width: u16,
height: u16,
},
Tick,
User(InlineMessage<M>),
Quit,
Focus,
Blur,
Suspend,
Resume,
Paste(std::borrow::Cow<'static, str>),
#[doc(hidden)]
ExecProcess,
}
#[derive(Debug, Clone)]
pub enum InlineMessage<M> {
Inline(M),
Boxed(Box<M>),
}
impl<M> InlineMessage<M> {
#[inline(always)]
pub fn new(msg: M) -> Self {
if mem::size_of::<M>() <= INLINE_SIZE {
Self::Inline(msg)
} else {
Self::Boxed(Box::new(msg))
}
}
#[inline(always)]
pub fn get(&self) -> &M {
match self {
Self::Inline(msg) => msg,
Self::Boxed(msg) => msg.as_ref(),
}
}
#[inline(always)]
pub fn into_inner(self) -> M {
match self {
Self::Inline(msg) => msg,
Self::Boxed(msg) => *msg,
}
}
#[inline(always)]
pub fn is_inline(&self) -> bool {
matches!(self, Self::Inline(_))
}
}
impl<M> OptimizedEvent<M> {
#[inline(always)]
pub fn user(msg: M) -> Self {
Self::User(InlineMessage::new(msg))
}
#[inline(always)]
pub const fn is_key(&self) -> bool {
matches!(self, Self::Key(_))
}
#[inline(always)]
pub fn is_key_press(&self, key: crate::event::Key) -> bool {
matches!(self, Self::Key(k) if k.key == key)
}
#[inline(always)]
pub const fn as_key(&self) -> Option<&KeyEvent> {
match self {
Self::Key(k) => Some(k),
_ => None,
}
}
#[inline(always)]
pub const fn is_mouse(&self) -> bool {
matches!(self, Self::Mouse(_))
}
#[inline(always)]
pub const fn as_mouse(&self) -> Option<&MouseEvent> {
match self {
Self::Mouse(m) => Some(m),
_ => None,
}
}
#[inline(always)]
pub const fn is_user(&self) -> bool {
matches!(self, Self::User(_))
}
#[inline(always)]
pub fn as_user(&self) -> Option<&M> {
match self {
Self::User(msg) => Some(msg.get()),
_ => None,
}
}
#[inline(always)]
pub fn into_user(self) -> Option<M> {
match self {
Self::User(msg) => Some(msg.into_inner()),
_ => None,
}
}
#[inline(always)]
pub const fn is_quit(&self) -> bool {
matches!(self, Self::Quit)
}
#[inline(always)]
pub const fn is_tick(&self) -> bool {
matches!(self, Self::Tick)
}
#[inline(always)]
pub const fn is_resize(&self) -> bool {
matches!(self, Self::Resize { .. })
}
#[inline(always)]
pub const fn as_resize(&self) -> Option<(u16, u16)> {
match self {
Self::Resize { width, height } => Some((*width, *height)),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct EventBatch<M> {
events: SmallVec<[OptimizedEvent<M>; 8]>,
}
impl<M> EventBatch<M> {
#[inline]
pub fn new() -> Self {
Self {
events: SmallVec::new(),
}
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self {
events: SmallVec::with_capacity(capacity),
}
}
#[inline]
pub fn push(&mut self, event: OptimizedEvent<M>) {
self.events.push(event);
}
#[inline]
pub fn len(&self) -> usize {
self.events.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
#[inline]
pub fn iter(&self) -> std::slice::Iter<'_, OptimizedEvent<M>> {
self.events.iter()
}
#[inline]
pub fn drain(&mut self) -> smallvec::Drain<'_, [OptimizedEvent<M>; 8]> {
self.events.drain(..)
}
#[inline]
pub fn clear(&mut self) {
self.events.clear();
}
#[inline]
pub fn is_inline(&self) -> bool {
!self.events.spilled()
}
}
impl<M> Default for EventBatch<M> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<M> IntoIterator for EventBatch<M> {
type Item = OptimizedEvent<M>;
type IntoIter = smallvec::IntoIter<[OptimizedEvent<M>; 8]>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.events.into_iter()
}
}
pub struct EventCoalescer<M> {
buffer: SmallVec<[OptimizedEvent<M>; 16]>,
last_resize: Option<(u16, u16)>,
}
impl<M> EventCoalescer<M> {
#[inline]
pub fn new() -> Self {
Self {
buffer: SmallVec::new(),
last_resize: None,
}
}
pub fn push(&mut self, event: OptimizedEvent<M>) {
match event {
OptimizedEvent::Resize { width, height } => {
self.last_resize = Some((width, height));
}
OptimizedEvent::Tick => {
if !self.buffer.iter().any(|e| e.is_tick()) {
self.buffer.push(event);
}
}
_ => {
self.buffer.push(event);
}
}
}
pub fn finish(&mut self) -> EventBatch<M> {
if let Some((width, height)) = self.last_resize.take() {
self.buffer.push(OptimizedEvent::Resize { width, height });
}
let mut batch = EventBatch::with_capacity(self.buffer.len());
batch.events.extend(self.buffer.drain(..));
batch
}
#[inline]
pub fn clear(&mut self) {
self.buffer.clear();
self.last_resize = None;
}
}
impl<M> Default for EventCoalescer<M> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<M> From<crate::event::Event<M>> for OptimizedEvent<M> {
#[inline]
fn from(event: crate::event::Event<M>) -> Self {
match event {
crate::event::Event::Key(k) => Self::Key(k),
crate::event::Event::Mouse(m) => Self::Mouse(m),
crate::event::Event::Resize { width, height } => Self::Resize { width, height },
crate::event::Event::Tick => Self::Tick,
crate::event::Event::User(m) => Self::user(m),
crate::event::Event::Quit => Self::Quit,
crate::event::Event::Focus => Self::Focus,
crate::event::Event::Blur => Self::Blur,
crate::event::Event::Suspend => Self::Suspend,
crate::event::Event::Resume => Self::Resume,
crate::event::Event::Paste(text) => Self::Paste(text.into()),
crate::event::Event::ExecProcess => Self::ExecProcess,
}
}
}
impl<M> From<OptimizedEvent<M>> for crate::event::Event<M> {
#[inline]
fn from(event: OptimizedEvent<M>) -> Self {
match event {
OptimizedEvent::Key(k) => Self::Key(k),
OptimizedEvent::Mouse(m) => Self::Mouse(m),
OptimizedEvent::Resize { width, height } => Self::Resize { width, height },
OptimizedEvent::Tick => Self::Tick,
OptimizedEvent::User(m) => Self::User(m.into_inner()),
OptimizedEvent::Quit => Self::Quit,
OptimizedEvent::Focus => Self::Focus,
OptimizedEvent::Blur => Self::Blur,
OptimizedEvent::Suspend => Self::Suspend,
OptimizedEvent::Resume => Self::Resume,
OptimizedEvent::Paste(text) => Self::Paste(text.into_owned()),
OptimizedEvent::ExecProcess => Self::ExecProcess,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_inline_message_small() {
let msg = 42u32;
let inline_msg = InlineMessage::new(msg);
assert!(inline_msg.is_inline());
assert_eq!(*inline_msg.get(), 42);
}
#[test]
fn test_inline_message_large() {
#[derive(Debug, PartialEq, Clone)]
struct LargeStruct([u8; 32]);
let msg = LargeStruct([0u8; 32]);
let inline_msg = InlineMessage::new(msg.clone());
assert!(!inline_msg.is_inline());
assert_eq!(*inline_msg.get(), msg);
}
#[test]
fn test_event_coalescing() {
let mut coalescer: EventCoalescer<()> = EventCoalescer::new();
coalescer.push(OptimizedEvent::Resize {
width: 80,
height: 24,
});
coalescer.push(OptimizedEvent::Resize {
width: 100,
height: 30,
});
coalescer.push(OptimizedEvent::Resize {
width: 120,
height: 40,
});
coalescer.push(OptimizedEvent::Tick);
coalescer.push(OptimizedEvent::Tick);
let batch = coalescer.finish();
assert_eq!(batch.len(), 2);
let events: Vec<_> = batch.into_iter().collect();
assert!(events.iter().any(|e| matches!(e, OptimizedEvent::Tick)));
assert!(events.iter().any(|e| matches!(
e,
OptimizedEvent::Resize {
width: 120,
height: 40
}
)));
}
#[test]
fn test_event_batch_inline_storage() {
let mut batch = EventBatch::new();
batch.push(OptimizedEvent::Tick);
batch.push(OptimizedEvent::user(42u32));
batch.push(OptimizedEvent::Quit);
assert!(batch.is_inline()); assert_eq!(batch.len(), 3);
}
#[test]
fn test_zero_cost_event_checks() {
let event = OptimizedEvent::user(String::from("test"));
assert!(event.is_user());
assert!(!event.is_key());
assert!(!event.is_mouse());
assert_eq!(event.as_user().unwrap(), "test");
}
}