use std::{
sync::mpsc::{self, Receiver, SyncSender, TryRecvError, TrySendError},
thread,
};
use ratatui::{DefaultTerminal, buffer::Buffer, layout::Position};
use crate::{
error::Error,
keybindings::{self, PollResult},
model::Model,
view::view,
};
pub fn run_loop(mut terminal: DefaultTerminal, mut model: Model) -> Result<(), Error> {
terminal.draw(|frame| {
let cursor_position = view(&model, frame.buffer_mut());
frame.set_cursor_position(cursor_position);
})?;
let (buf_in_tx, buf_in_rx) = mpsc::sync_channel::<(Buffer, Position)>(1);
let (buf_out_tx, buf_out_rx) = mpsc::sync_channel::<Buffer>(1);
buf_out_tx
.send(Buffer::empty(model.screen_size.into()))
.expect("unreachable: channel has capacity 1");
let render_thread = thread::Builder::new()
.name("render".into())
.spawn(move || -> Result<(), Error> { render(terminal, buf_in_rx, buf_out_tx) })?;
let mut dropped = false;
loop {
let (had_events, _, had_reload) = model.process_events()?;
let (had_input, skip_render) = match keybindings::poll(had_events, &mut model)? {
PollResult::Quit => break,
PollResult::None => (false, false),
PollResult::HadInput => (true, false),
PollResult::SkipRender => (true, true),
};
let should_render = dropped || ((had_events || had_input) && !skip_render && !had_reload);
if should_render {
let mut buf = match buf_out_rx.try_recv() {
Err(err) => match err {
TryRecvError::Disconnected => {
log::warn!("no buffer: disconnected");
break;
}
TryRecvError::Empty => {
log::warn!("dropping frame");
dropped = true;
continue;
}
},
Ok(mut buf) => {
buf.resize(model.screen_size.into());
buf
}
};
let cursor_position = view(&model, &mut buf);
if let Err(err) = buf_in_tx.try_send((buf, cursor_position)) {
match err {
TrySendError::Full(_) => {
log::warn!("frame dropped!");
dropped = true;
}
TrySendError::Disconnected(_) => {
log::error!("render buffer channel disconnected");
break;
}
}
} else {
dropped = false;
}
}
}
drop(buf_in_tx); render_thread
.join()
.map_err(|err| Error::Thread(format!("{err:?}")))?
}
fn render(
mut terminal: DefaultTerminal,
buf_in: Receiver<(Buffer, Position)>,
buf_out: SyncSender<Buffer>,
) -> Result<(), Error> {
while let Ok((mut buf, cursor_position)) = buf_in.recv() {
terminal.draw(|frame| {
std::mem::swap(frame.buffer_mut(), &mut buf);
frame.set_cursor_position(cursor_position);
})?;
buf_out
.send(buf)
.map_err(|err| Error::Thread(format!("could not return buffer: {err}")))?;
}
Ok(terminal.set_cursor_position((0, terminal.size()?.height - 1))?)
}