refactor: extract mod exit, task; Thread -> Task

This commit is contained in:
same mf who else 2026-03-28 14:11:23 +02:00
parent bea88ac58d
commit dae4d5a140
8 changed files with 114 additions and 74 deletions

View file

@ -2,6 +2,12 @@ use crate::{*, lang::*, color::*, space::*};
/// Drawable that supports dynamic dispatch. /// Drawable that supports dynamic dispatch.
/// ///
/// Drawables are composable, e.g. the [when] and [either] conditionals
/// or the layout constraints.
///
/// Drawables are consumable, i.e. the [Draw::draw] method receives an
/// owned `self` and does not return it, consuming the drawable.
///
/// ``` /// ```
/// use tengri::draw::*; /// use tengri::draw::*;
/// struct TestScreen(bool); /// struct TestScreen(bool);
@ -20,25 +26,29 @@ use crate::{*, lang::*, color::*, space::*};
pub trait Draw<T: Screen> { pub trait Draw<T: Screen> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>>; fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>>;
} }
impl<T: Screen> Draw<T> for () { impl<T: Screen> Draw<T> for () {
fn draw (self, __: &mut T) -> Usually<XYWH<T::Unit>> { fn draw (self, __: &mut T) -> Usually<XYWH<T::Unit>> {
Ok(Default::default()) Ok(Default::default())
} }
} }
impl<T: Screen, D: Draw<T>> Draw<T> for Option<D> { impl<T: Screen, D: Draw<T>> Draw<T> for Option<D> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> { fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
Ok(self.map(|draw|draw.draw(to)).transpose()?.unwrap_or_default()) Ok(self.map(|draw|draw.draw(to)).transpose()?.unwrap_or_default())
} }
} }
pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> (draw: F) -> Thunk<T, F> {
Thunk(draw, std::marker::PhantomData)
}
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. /// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>>( pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>>(
pub F, pub F,
std::marker::PhantomData<T> std::marker::PhantomData<T>
); );
pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> (draw: F) -> Thunk<T, F> {
Thunk(draw, std::marker::PhantomData)
}
impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> { impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> { fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
(self.0)(to) (self.0)(to)
@ -76,8 +86,8 @@ pub const fn either <T: Screen> (condition: bool, a: impl Draw<T>, b: impl Draw<
/// ///
/// impl Screen for TestOut { /// impl Screen for TestOut {
/// type Unit = u16; /// type Unit = u16;
/// fn place <T: Draw<Self> + ?Sized> (&mut self, area: impl TwoD<u16>, _: &T) { /// fn place_at <T: Draw<Self> + ?Sized> (&mut self, area: impl TwoD<u16>, _: &T) {
/// println!("place: {area:?}"); /// println!("placed: {area:?}");
/// () /// ()
/// } /// }
/// } /// }
@ -101,3 +111,14 @@ pub trait Screen: Space<Self::Unit> + Send + Sync + Sized {
unimplemented!() unimplemented!()
} }
} }
/// Emit a [Draw]able.
pub trait View<T: Screen> {
fn view (&self) -> impl Draw<T>;
}
impl<T: Screen, V: View<T>> Draw<T> for &V {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
self.view().draw(to)
}
}

10
src/exit.rs Normal file
View file

@ -0,0 +1,10 @@
use std::sync::{Arc, atomic::AtomicBool};
use crate::Usually;
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
impl Exit {
pub fn run <T> (run: impl Fn(Self)->Usually<T>) -> Usually<T> {
run(Self(Arc::new(AtomicBool::new(false))))
}
}

View file

@ -1,4 +1,4 @@
use crate::play::Thread; use crate::task::Task;
use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
use ::std::time::Duration; use ::std::time::Duration;
@ -10,10 +10,10 @@ use ::crossterm::event::{
/// Spawn the TUI input thread which reads keys from the terminal. /// Spawn the TUI input thread which reads keys from the terminal.
pub fn tui_input <T: Do<TuiEvent, Usually<T>> + Send + Sync + 'static> ( pub fn tui_input <T: Do<TuiEvent, Usually<T>> + Send + Sync + 'static> (
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
) -> Result<Thread, std::io::Error> { ) -> Result<Task, std::io::Error> {
let exited = exited.clone(); let exited = exited.clone();
let state = state.clone(); let state = state.clone();
Thread::new_poll(exited.clone(), poll, move |_| { Task::new_poll(exited.clone(), poll, move |_| {
let event = read().unwrap(); let event = read().unwrap();
match event { match event {

View file

@ -27,7 +27,11 @@ pub(crate) use ::{
#[cfg(feature = "lang")] pub use ::dizzle::{self, Usually, Perhaps, impl_default}; #[cfg(feature = "lang")] pub use ::dizzle::{self, Usually, Perhaps, impl_default};
#[cfg(feature = "time")] pub mod time; #[cfg(feature = "time")] pub mod time;
#[cfg(feature = "play")] pub mod play; #[cfg(feature = "play")] pub mod play;
#[cfg(feature = "play")] pub mod exit;
#[cfg(feature = "play")] pub mod task;
#[cfg(feature = "sing")] pub extern crate jack; #[cfg(feature = "sing")] pub extern crate jack;
#[cfg(feature = "sing")] pub mod sing; #[cfg(feature = "sing")] pub mod sing;
#[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; #[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};

View file

@ -1,65 +1,5 @@
use crate::{*, time::*, lang::*}; use crate::{*, time::*, lang::*};
use ::std::{thread::JoinHandle, time::Duration}; use ::std::{thread::JoinHandle, time::Duration};
#[cfg(feature = "term")] use ::crossterm::event::poll;
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
impl Exit {
pub fn run <T> (run: impl Fn(Self)->Usually<T>) -> Usually<T> {
run(Self(Arc::new(AtomicBool::new(false))))
}
}
#[derive(Debug)] pub struct Thread {
/// Exit flag.
pub exit: Arc<AtomicBool>,
/// Performance counter.
pub perf: Arc<PerfModel>,
/// Use this to wait for the thread to finish.
pub join: JoinHandle<()>,
}
impl Thread {
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
pub fn new <F> (exit: Arc<AtomicBool>, mut call: F) -> Result<Self, std::io::Error>
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{
let perf = Arc::new(PerfModel::default());
Ok(Self {
exit: exit.clone(),
perf: perf.clone(),
join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || {
while !exit.fetch_and(true, Relaxed) {
let _ = perf.cycle(&mut call);
}
})?.into()
})
}
/// Spawn a thread that runs `call` least one, then repeats
/// until `exit`, sleeping for `time` msec after every iteration.
pub fn new_sleep <F> (
exit: Arc<AtomicBool>, time: Duration, mut call: F
) -> Result<Self, std::io::Error>
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{
Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); })
}
/// Spawn a thread that uses [crossterm::event::poll]
/// to run `call` every `time` msec.
#[cfg(feature = "term")] pub fn new_poll <F> (
exit: Arc<AtomicBool>, time: Duration, mut call: F
) -> Result<Self, std::io::Error>
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{
Self::new(exit, move |perf| { if poll(time).is_ok() { let _ = call(perf); } })
}
pub fn join (self) -> Result<(), Box<dyn std::any::Any + Send>> {
self.join.join()
}
}
/// Define an enum containing commands, and implement [Command] trait for over given `State`. /// Define an enum containing commands, and implement [Command] trait for over given `State`.
#[macro_export] macro_rules! def_command ( #[macro_export] macro_rules! def_command (
@ -82,7 +22,7 @@ impl Thread {
}); });
/// Implement [Handle] for given `State` and `handler`. /// Implement [Handle] for given `State` and `handler`.
#[macro_export] macro_rules! handle { #[macro_export] macro_rules! impl_handle {
(|$self:ident:$State:ty,$input:ident|$handler:expr) => { (|$self:ident:$State:ty,$input:ident|$handler:expr) => {
impl<E: Engine> ::tengri::Handle<E> for $State { impl<E: Engine> ::tengri::Handle<E> for $State {
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> { fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {

65
src/task.rs Normal file
View file

@ -0,0 +1,65 @@
use std::{
time::Duration,
sync::{Arc, atomic::{AtomicBool, Ordering::*}},
thread::{Builder, JoinHandle, sleep},
};
#[cfg(feature = "term")] use ::crossterm::event::poll;
use crate::time::PerfModel;
#[derive(Debug)] pub struct Task {
/// Exit flag.
pub exit: Arc<AtomicBool>,
/// Performance counter.
pub perf: Arc<PerfModel>,
/// Use this to wait for the thread to finish.
pub join: JoinHandle<()>,
}
impl Task {
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
pub fn new <F> (exit: Arc<AtomicBool>, mut call: F) -> Result<Self, std::io::Error>
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{
let perf = Arc::new(PerfModel::default());
Ok(Self {
exit: exit.clone(),
perf: perf.clone(),
join: Builder::new().name("tengri tui output".into()).spawn(move || {
while !exit.fetch_and(true, Relaxed) {
let _ = perf.cycle(&mut call);
}
})?.into()
})
}
/// Spawn a thread that runs `call` least one, then repeats
/// until `exit`, sleeping for `time` msec after every iteration.
pub fn new_sleep <F> (
exit: Arc<AtomicBool>, time: Duration, mut call: F
) -> Result<Self, std::io::Error>
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{
Self::new(exit, move |perf| {
let _ = call(perf);
sleep(time);
})
}
/// Spawn a thread that uses [crossterm::event::poll]
/// to run `call` every `time` msec.
#[cfg(feature = "term")] pub fn new_poll <F> (
exit: Arc<AtomicBool>, time: Duration, mut call: F
) -> Result<Self, std::io::Error>
where F: FnMut(&PerfModel)->() + Send + Sync + 'static
{
Self::new(exit, move |perf| {
if poll(time).is_ok() {
let _ = call(perf);
}
})
}
pub fn join (self) -> Result<(), Box<dyn std::any::Any + Send>> {
self.join.join()
}
}

View file

@ -1,4 +1,4 @@
use crate::{*, lang::*, play::*, draw::*, space::{*, Split::*}, color::*, text::*}; use crate::{*, lang::*, play::*, draw::*, space::{*, Split::*}, color::*, text::*, task::*};
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
use rand::distributions::uniform::UniformSampler; use rand::distributions::uniform::UniformSampler;
use ::{ use ::{
@ -509,14 +509,14 @@ pub fn tui_output <W: Write + Send + Sync + 'static, T: Draw<Tui> + Send + Sync
exited: &Arc<AtomicBool>, exited: &Arc<AtomicBool>,
state: &Arc<RwLock<T>>, state: &Arc<RwLock<T>>,
sleep: Duration sleep: Duration
) -> Usually<Thread> { ) -> Usually<Task> {
let state = state.clone(); let state = state.clone();
tui_setup()?; tui_setup()?;
let mut backend = CrosstermBackend::new(output); let mut backend = CrosstermBackend::new(output);
let Size { width, height } = backend.size().expect("get size failed"); let Size { width, height } = backend.size().expect("get size failed");
let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height }); let mut buffer_a = Buffer::empty(Rect { x: 0, y: 0, width, height });
let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height }); let mut buffer_b = Buffer::empty(Rect { x: 0, y: 0, width, height });
Ok(Thread::new_sleep(exited.clone(), sleep, move |perf| { Ok(Task::new_sleep(exited.clone(), sleep, move |perf| {
let Size { width, height } = backend.size().expect("get size failed"); let Size { width, height } = backend.size().expect("get size failed");
if let Ok(state) = state.try_read() { if let Ok(state) = state.try_read() {
tui_resize(&mut backend, &mut buffer_a, Rect { x: 0, y: 0, width, height }); tui_resize(&mut backend, &mut buffer_a, Rect { x: 0, y: 0, width, height });

View file

@ -17,12 +17,12 @@ pub(crate) use ::unicode_width::*;
self.as_str().draw(to) self.as_str().draw(to)
} }
} }
impl Draw<Tui> for &std::sync::Arc<str> { impl Draw<Tui> for std::sync::Arc<str> {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> { fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_ref().draw(to) self.as_ref().draw(to)
} }
} }
impl Draw<Tui> for std::sync::Arc<str> { impl Draw<Tui> for &std::sync::Arc<str> {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> { fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_ref().draw(to) self.as_ref().draw(to)
} }