swactor/crates/runtime-dashboard/src/tui/event.rs
2026-02-12 16:10:28 +07:00

90 lines
2.6 KiB
Rust

use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use crossterm::event::{self, Event, KeyEvent, KeyEventKind};
use swactor::stats::RuntimeStats;
use super::types::RuntimeEndpoint;
pub enum AppEvent {
Key(KeyEvent),
Tick,
StatsUpdate {
source: RuntimeEndpoint,
stats: Box<RuntimeStats>,
},
#[cfg(feature = "distribution")]
DistributionUpdate {
snapshot: Box<distribution::snapshot::DistributionNodeSnapshot>,
},
}
pub struct EventLoop {
rx: mpsc::Receiver<AppEvent>,
}
impl EventLoop {
pub fn new(tick_ms: u64) -> Self {
let (tx, rx) = mpsc::channel();
// Input thread
let tx_input = tx.clone();
thread::spawn(move || loop {
// Poll with a timeout so the thread can eventually notice if the
// channel is dropped (tx_input.send will fail).
if event::poll(Duration::from_millis(100)).unwrap_or(false) {
if let Ok(Event::Key(key)) = event::read() {
// Only handle key press events, not release/repeat
if key.kind == KeyEventKind::Press {
if tx_input.send(AppEvent::Key(key)).is_err() {
return;
}
}
}
}
});
// Tick thread
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(tick_ms));
if tx.send(AppEvent::Tick).is_err() {
return;
}
});
Self { rx }
}
/// Create an event loop and return a sender for external producers (e.g. SSE client).
pub fn new_with_sender(tick_ms: u64) -> (Self, mpsc::Sender<AppEvent>) {
let (tx, rx) = mpsc::channel();
let tx_input = tx.clone();
thread::spawn(move || loop {
if event::poll(Duration::from_millis(100)).unwrap_or(false) {
if let Ok(Event::Key(key)) = event::read() {
if key.kind == KeyEventKind::Press {
if tx_input.send(AppEvent::Key(key)).is_err() {
return;
}
}
}
}
});
let tx_tick = tx.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(tick_ms));
if tx_tick.send(AppEvent::Tick).is_err() {
return;
}
});
(Self { rx }, tx)
}
pub fn next(&self) -> Result<AppEvent, mpsc::RecvError> {
self.rx.recv()
}
}