use crate::{
coord,
coord::Vec2,
error::{AlreadyRunning, Error, ErrorKind, ServicesOff},
event,
event::{Event, Reactor},
screen::{renderer, Screen, ScreenData},
};
use std::{
future::Future,
sync::{
atomic::{AtomicBool, Ordering::*},
Arc,
},
time::Duration,
};
use tokio::{
sync::{Barrier, RwLock, RwLockReadGuard, RwLockWriteGuard},
task,
time,
};
static RUN_GUARD_STATE: AtomicBool = AtomicBool::new(false);
#[derive(Debug)]
struct RunGuard;
impl RunGuard {
fn acquire() -> Result<Self, AlreadyRunning> {
if RUN_GUARD_STATE.swap(true, Acquire) {
Err(AlreadyRunning)
} else {
Ok(Self)
}
}
}
impl Drop for RunGuard {
fn drop(&mut self) {
RUN_GUARD_STATE.store(false, Release)
}
}
#[derive(Debug, Clone)]
pub struct Builder {
min_screen: Vec2,
frame_time: Duration,
event_interval: Duration,
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
impl Builder {
pub fn new() -> Self {
Self {
min_screen: Vec2 { x: 80, y: 25 },
frame_time: Duration::from_millis(20),
event_interval: Duration::from_millis(20),
}
}
pub fn min_screen(self, min_screen: Vec2) -> Self {
Self { min_screen, ..self }
}
pub fn frame_time(self, frame_time: Duration) -> Self {
Self { frame_time, ..self }
}
pub fn event_interval(self, event_interval: Duration) -> Self {
Self { event_interval, ..self }
}
pub async fn run<F, A, T>(self, start: F) -> Result<T, Error>
where
F: FnOnce(Terminal) -> A + Send + 'static,
A: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let _guard = RunGuard::acquire()?;
let initial_size = self.initial_size()?;
let terminal = self.finish(initial_size).await;
let shared = terminal.shared.clone();
shared.screen().setup().await?;
let barrier = Arc::new(Barrier::new(3));
let events_fut = {
let interval = self.event_interval;
let barrier = barrier.clone();
let shared = shared.clone();
tokio::spawn(events_task(barrier, interval, shared, initial_size))
};
let renderer_fut = {
let barrier = barrier.clone();
let shared = shared.clone();
tokio::spawn(renderer_task(barrier, shared))
};
let main_fut = {
let barrier = barrier.clone();
tokio::spawn(main_task(barrier, terminal, start))
};
let (main_ret, events_ret, renderer_ret) =
tokio::join!(main_fut, events_fut, renderer_fut);
let _ = shared.screen().cleanup().await;
if let Err(error) = events_ret? {
match error.kind() {
ErrorKind::ServicesOff(_) => (),
_ => Err(error)?,
}
}
if let Err(error) = renderer_ret? {
match error.kind() {
ErrorKind::ServicesOff(_) => (),
_ => Err(error)?,
}
}
Ok(main_ret?)
}
fn initial_size(&self) -> Result<Vec2, Error> {
let size_res = task::block_in_place(|| {
crossterm::terminal::enable_raw_mode()?;
crossterm::terminal::size()
});
let (width, height) = size_res.map_err(Error::from_crossterm)?;
Ok(Vec2 {
y: coord::from_crossterm(height),
x: coord::from_crossterm(width),
})
}
async fn finish(&self, screen_size: Vec2) -> Terminal {
let shared = Arc::new(Shared::new(
screen_size,
self.min_screen,
self.frame_time,
));
Terminal { shared, curr_epoch: 0 }
}
}
async fn main_task<F, A, T>(
barrier: Arc<Barrier>,
terminal: Terminal,
start: F,
) -> T
where
F: FnOnce(Terminal) -> A + Send + 'static,
A: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let cloned = terminal.clone();
let _guard = cloned.shared.conn_guard();
barrier.wait().await;
start(terminal).await
}
async fn events_task(
barrier: Arc<Barrier>,
interval: Duration,
shared: Arc<Shared>,
initial_size: Vec2,
) -> Result<(), Error> {
let mut reactor = Reactor::new(&shared);
reactor.pre_loop(initial_size).await?;
barrier.wait().await;
reactor.react_loop(interval).await
}
async fn renderer_task(
barrier: Arc<Barrier>,
shared: Arc<Shared>,
) -> Result<(), Error> {
let _guard = shared.conn_guard();
barrier.wait().await;
renderer(&shared).await
}
#[derive(Debug, Clone)]
pub struct Terminal {
shared: Arc<Shared>,
curr_epoch: event::Epoch,
}
impl Terminal {
pub async fn run<F, A, T>(start: F) -> Result<T, Error>
where
F: FnOnce(Terminal) -> A + Send + 'static,
A: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
Builder::default().run(start).await
}
pub async fn lock_now<'terminal>(
&'terminal mut self,
) -> Result<TerminalGuard<'terminal>, ServicesOff> {
let guard = self.shared.app_guard().await?;
let event = self.shared.events().read(self.curr_epoch);
let screen = self.shared.screen().lock().await;
Ok(TerminalGuard {
screen,
guard,
event,
curr_epoch: &mut self.curr_epoch,
})
}
pub async fn listen<'terminal>(
&'terminal mut self,
) -> Result<TerminalGuard<'terminal>, ServicesOff> {
self.shared.events.subscribe().await;
self.lock_now().await
}
pub fn clear_event(&mut self) {
self.curr_epoch = self.shared.events().epoch();
}
pub async fn wait_user<'terminal>(
&'terminal mut self,
delay: Duration,
) -> Result<TerminalGuard<'terminal>, ServicesOff> {
time::sleep(delay).await;
self.clear_event();
self.listen().await
}
}
#[derive(Debug)]
pub struct TerminalGuard<'terminal> {
guard: AppSyncGuard<'terminal>,
event: Option<(event::Epoch, Event)>,
curr_epoch: &'terminal mut event::Epoch,
screen: Screen<'terminal>,
}
impl<'terminal> TerminalGuard<'terminal> {
pub fn event(&mut self) -> Option<Event> {
self.event.map(|(new_epoch, event)| {
*self.curr_epoch = new_epoch;
event
})
}
pub fn screen(&mut self) -> &mut Screen<'terminal> {
&mut self.screen
}
}
#[derive(Debug)]
pub(crate) struct Shared {
sync: RwLock<()>,
connected: AtomicBool,
screen: ScreenData,
events: event::Channel,
}
impl Shared {
pub fn new(
screen_size: Vec2,
min_screen: Vec2,
frame_time: Duration,
) -> Self {
Self {
sync: RwLock::new(()),
connected: AtomicBool::new(true),
screen: ScreenData::new(screen_size, min_screen, frame_time),
events: event::Channel::default(),
}
}
pub fn is_connected(&self) -> bool {
self.connected.load(Acquire)
}
pub fn disconnect(&self) {
self.connected.store(false, Release);
self.events.notify();
self.screen.notify();
}
pub async fn service_guard<'this>(
&'this self,
) -> Result<ServiceSyncGuard<'this>, ServicesOff> {
let guard = ServiceSyncGuard { inner: self.sync.read().await };
if self.is_connected() {
Ok(guard)
} else {
Err(ServicesOff)
}
}
pub async fn app_guard<'this>(
&'this self,
) -> Result<AppSyncGuard<'this>, ServicesOff> {
let guard = AppSyncGuard { inner: self.sync.write().await };
if self.is_connected() {
Ok(guard)
} else {
Err(ServicesOff)
}
}
pub fn events(&self) -> &event::Channel {
&self.events
}
pub fn screen(&self) -> &ScreenData {
&self.screen
}
pub fn conn_guard(&self) -> ConnGuard {
ConnGuard { shared: self }
}
}
#[derive(Debug)]
pub(crate) struct ServiceSyncGuard<'shared> {
inner: RwLockReadGuard<'shared, ()>,
}
#[derive(Debug)]
pub(crate) struct AppSyncGuard<'shared> {
inner: RwLockWriteGuard<'shared, ()>,
}
#[derive(Debug)]
pub(crate) struct ConnGuard<'shared> {
shared: &'shared Shared,
}
impl<'shared> Drop for ConnGuard<'shared> {
fn drop(&mut self) {
self.shared.disconnect()
}
}