spec_ai/spec_ai_tui/event/
event_loop.rs1use super::Event;
4use crossterm::event::EventStream;
5use futures::StreamExt;
6use std::time::Duration;
7use tokio::sync::mpsc;
8
9pub struct EventLoop {
11 tick_rate: Duration,
13 custom_rx: mpsc::UnboundedReceiver<Event>,
15 custom_tx: mpsc::UnboundedSender<Event>,
17}
18
19impl EventLoop {
20 pub fn new(tick_rate: Duration) -> Self {
22 let (custom_tx, custom_rx) = mpsc::unbounded_channel();
23 Self {
24 tick_rate,
25 custom_rx,
26 custom_tx,
27 }
28 }
29
30 pub fn default_rate() -> Self {
32 Self::new(Duration::from_millis(100))
33 }
34
35 pub fn sender(&self) -> mpsc::UnboundedSender<Event> {
40 self.custom_tx.clone()
41 }
42
43 pub async fn next(&mut self) -> Option<Event> {
52 let tick_delay = tokio::time::sleep(self.tick_rate);
53 tokio::pin!(tick_delay);
54
55 let mut event_stream = EventStream::new();
56
57 tokio::select! {
58 maybe_event = event_stream.next() => {
60 match maybe_event {
61 Some(Ok(event)) => Some(event.into()),
62 Some(Err(_)) => None,
63 None => None,
64 }
65 }
66 Some(event) = self.custom_rx.recv() => {
68 Some(event)
69 }
70 _ = &mut tick_delay => {
72 Some(Event::Tick)
73 }
74 }
75 }
76
77 pub async fn run<F>(&mut self, mut handler: F)
81 where
82 F: FnMut(Event) -> bool,
83 {
84 #[allow(clippy::while_let_loop)]
85 loop {
86 if let Some(event) = self.next().await {
87 if !handler(event) {
88 break;
89 }
90 } else {
91 break;
92 }
93 }
94 }
95}
96
97#[allow(dead_code)]
99pub struct EventLoopBuilder {
100 tick_rate: Duration,
101}
102
103#[allow(dead_code)]
104impl EventLoopBuilder {
105 pub fn new() -> Self {
107 Self {
108 tick_rate: Duration::from_millis(100),
109 }
110 }
111
112 pub fn tick_rate(mut self, rate: Duration) -> Self {
114 self.tick_rate = rate;
115 self
116 }
117
118 pub fn build(self) -> EventLoop {
120 EventLoop::new(self.tick_rate)
121 }
122}
123
124impl Default for EventLoopBuilder {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[tokio::test]
135 #[ignore = "requires terminal"]
136 async fn test_custom_event() {
137 let mut event_loop = EventLoop::new(Duration::from_secs(10)); let sender = event_loop.sender();
139
140 sender.send(Event::Tick).unwrap();
142
143 let event = tokio::time::timeout(Duration::from_millis(100), event_loop.next())
145 .await
146 .unwrap();
147
148 assert!(matches!(event, Some(Event::Tick)));
149 }
150}