mod app;
mod ui;
use std::sync::Arc;
use std::time::{Duration, Instant};
use corium_peer::Connection;
use tokio::sync::mpsc;
use app::{App, Event, StatusSample, TxEntry};
pub async fn run(connection: Arc<Connection>, refresh: Duration) -> Result<(), String> {
let (sender, mut events) = mpsc::channel::<Event>(256);
spawn_input_reader(sender.clone());
spawn_status_poller(Arc::clone(&connection), refresh, sender.clone());
spawn_tx_listener(&connection, sender);
let mut terminal = ratatui::init();
let result = event_loop(&mut terminal, &connection, &mut events).await;
ratatui::restore();
result
}
async fn event_loop(
terminal: &mut ratatui::DefaultTerminal,
connection: &Connection,
events: &mut mpsc::Receiver<Event>,
) -> Result<(), String> {
let mut app = App::new(connection.db_name().to_owned());
loop {
let db = connection.db();
terminal
.draw(|frame| ui::draw(frame, &mut app, &db))
.map_err(|error| format!("cannot draw terminal: {error}"))?;
let Some(event) = events.recv().await else {
return Ok(());
};
app.handle(event, &db);
while let Ok(event) = events.try_recv() {
app.handle(event, &connection.db());
}
if app.should_quit {
return Ok(());
}
}
}
fn spawn_input_reader(sender: mpsc::Sender<Event>) {
std::thread::spawn(move || {
loop {
match ratatui::crossterm::event::read() {
Ok(event) => {
if sender.blocking_send(Event::Term(event)).is_err() {
return;
}
}
Err(_) => return,
}
}
});
}
fn spawn_status_poller(
connection: Arc<Connection>,
refresh: Duration,
sender: mpsc::Sender<Event>,
) {
tokio::spawn(async move {
let mut ticker = tokio::time::interval(refresh);
ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
ticker.tick().await;
let started = Instant::now();
let event = match connection.status().await {
Ok(status) => Event::Status(Box::new(StatusSample {
at: Instant::now(),
rtt: started.elapsed(),
status,
})),
Err(error) => Event::StatusError(error.to_string()),
};
if sender.send(event).await.is_err() {
return;
}
}
});
}
fn spawn_tx_listener(connection: &Connection, sender: mpsc::Sender<Event>) {
let mut reports = connection.tx_reports();
tokio::spawn(async move {
loop {
let event = match reports.recv().await {
Ok(report) => Event::Tx(Box::new(TxEntry::from_report(&report))),
Err(tokio::sync::broadcast::error::RecvError::Lagged(count)) => {
Event::TxLagged(count)
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => return,
};
if sender.send(event).await.is_err() {
return;
}
}
});
}