From dae4d5a140660847d61ca7e4692213fe1463918c Mon Sep 17 00:00:00 2001 From: same mf who else Date: Sat, 28 Mar 2026 14:11:23 +0200 Subject: [PATCH] refactor: extract mod exit, task; Thread -> Task --- src/draw.rs | 31 ++++++++++++++++++++----- src/exit.rs | 10 +++++++++ src/keys.rs | 6 ++--- src/lib.rs | 4 ++++ src/play.rs | 62 +------------------------------------------------- src/task.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/term.rs | 6 ++--- src/text.rs | 4 ++-- 8 files changed, 114 insertions(+), 74 deletions(-) create mode 100644 src/exit.rs create mode 100644 src/task.rs diff --git a/src/draw.rs b/src/draw.rs index b33a155..9b8ebdb 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,6 +1,12 @@ use crate::{*, lang::*, color::*, space::*}; /// 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::*; @@ -20,25 +26,29 @@ use crate::{*, lang::*, color::*, space::*}; pub trait Draw { fn draw (self, to: &mut T) -> Usually>; } + impl Draw for () { fn draw (self, __: &mut T) -> Usually> { Ok(Default::default()) } } + impl> Draw for Option { fn draw (self, to: &mut T) -> Usually> { Ok(self.map(|draw|draw.draw(to)).transpose()?.unwrap_or_default()) } } +pub const fn thunk Usually>> (draw: F) -> Thunk { + Thunk(draw, std::marker::PhantomData) +} + /// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. pub struct ThunkUsually>>( pub F, std::marker::PhantomData ); -pub const fn thunk Usually>> (draw: F) -> Thunk { - Thunk(draw, std::marker::PhantomData) -} + implUsually>> Draw for Thunk { fn draw (self, to: &mut T) -> Usually> { (self.0)(to) @@ -76,8 +86,8 @@ pub const fn either (condition: bool, a: impl Draw, b: impl Draw< /// /// impl Screen for TestOut { /// type Unit = u16; -/// fn place + ?Sized> (&mut self, area: impl TwoD, _: &T) { -/// println!("place: {area:?}"); +/// fn place_at + ?Sized> (&mut self, area: impl TwoD, _: &T) { +/// println!("placed: {area:?}"); /// () /// } /// } @@ -101,3 +111,14 @@ pub trait Screen: Space + Send + Sync + Sized { unimplemented!() } } + +/// Emit a [Draw]able. +pub trait View { + fn view (&self) -> impl Draw; +} + +impl> Draw for &V { + fn draw (self, to: &mut T) -> Usually> { + self.view().draw(to) + } +} diff --git a/src/exit.rs b/src/exit.rs new file mode 100644 index 0000000..2d07816 --- /dev/null +++ b/src/exit.rs @@ -0,0 +1,10 @@ +use std::sync::{Arc, atomic::AtomicBool}; +use crate::Usually; + +#[derive(Clone)] pub struct Exit(Arc); + +impl Exit { + pub fn run (run: impl Fn(Self)->Usually) -> Usually { + run(Self(Arc::new(AtomicBool::new(false)))) + } +} diff --git a/src/keys.rs b/src/keys.rs index 54217c4..2d2037c 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,4 +1,4 @@ -use crate::play::Thread; +use crate::task::Task; use ::std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; use ::std::time::Duration; @@ -10,10 +10,10 @@ use ::crossterm::event::{ /// Spawn the TUI input thread which reads keys from the terminal. pub fn tui_input > + Send + Sync + 'static> ( exited: &Arc, state: &Arc>, poll: Duration -) -> Result { +) -> Result { let exited = exited.clone(); let state = state.clone(); - Thread::new_poll(exited.clone(), poll, move |_| { + Task::new_poll(exited.clone(), poll, move |_| { let event = read().unwrap(); match event { diff --git a/src/lib.rs b/src/lib.rs index 7897606..349ca17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,11 @@ pub(crate) use ::{ #[cfg(feature = "lang")] pub use ::dizzle::{self, Usually, Perhaps, impl_default}; #[cfg(feature = "time")] pub mod time; + #[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 mod sing; #[cfg(feature = "sing")] pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; diff --git a/src/play.rs b/src/play.rs index f07a4f7..96c83c4 100644 --- a/src/play.rs +++ b/src/play.rs @@ -1,65 +1,5 @@ use crate::{*, time::*, lang::*}; use ::std::{thread::JoinHandle, time::Duration}; -#[cfg(feature = "term")] use ::crossterm::event::poll; - -#[derive(Clone)] pub struct Exit(Arc); - -impl Exit { - pub fn run (run: impl Fn(Self)->Usually) -> Usually { - run(Self(Arc::new(AtomicBool::new(false)))) - } -} - -#[derive(Debug)] pub struct Thread { - /// Exit flag. - pub exit: Arc, - /// Performance counter. - pub perf: Arc, - /// 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 (exit: Arc, mut call: F) -> Result - 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 ( - exit: Arc, time: Duration, mut call: F - ) -> Result - 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 ( - exit: Arc, time: Duration, mut call: F - ) -> Result - 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> { - self.join.join() - } -} /// Define an enum containing commands, and implement [Command] trait for over given `State`. #[macro_export] macro_rules! def_command ( @@ -82,7 +22,7 @@ impl Thread { }); /// 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) => { impl ::tengri::Handle for $State { fn handle (&mut $self, $input: &E) -> Perhaps { diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 0000000..dc37898 --- /dev/null +++ b/src/task.rs @@ -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, + /// Performance counter. + pub perf: Arc, + /// 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 (exit: Arc, mut call: F) -> Result + 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 ( + exit: Arc, time: Duration, mut call: F + ) -> Result + 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 ( + exit: Arc, time: Duration, mut call: F + ) -> Result + 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> { + self.join.join() + } +} diff --git a/src/term.rs b/src/term.rs index b59edbd..c038785 100644 --- a/src/term.rs +++ b/src/term.rs @@ -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 rand::distributions::uniform::UniformSampler; use ::{ @@ -509,14 +509,14 @@ pub fn tui_output + Send + Sync exited: &Arc, state: &Arc>, sleep: Duration -) -> Usually { +) -> Usually { let state = state.clone(); tui_setup()?; let mut backend = CrosstermBackend::new(output); 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_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"); if let Ok(state) = state.try_read() { tui_resize(&mut backend, &mut buffer_a, Rect { x: 0, y: 0, width, height }); diff --git a/src/text.rs b/src/text.rs index a853845..9fe1dd4 100644 --- a/src/text.rs +++ b/src/text.rs @@ -17,12 +17,12 @@ pub(crate) use ::unicode_width::*; self.as_str().draw(to) } } - impl Draw for &std::sync::Arc { + impl Draw for std::sync::Arc { fn draw (self, to: &mut Tui) -> Usually> { self.as_ref().draw(to) } } - impl Draw for std::sync::Arc { + impl Draw for &std::sync::Arc { fn draw (self, to: &mut Tui) -> Usually> { self.as_ref().draw(to) }