improve tracing and locks
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
facile pop culture reference 2026-08-29 18:46:35 +03:00
parent 92f992e2e6
commit 8e7286e409
6 changed files with 123 additions and 47 deletions

View file

@ -19,6 +19,7 @@ pub struct Area<S: Screen, T>(
impl<'a, S: Screen, T: Draw<S>> Draw<S> for Area<S, T> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("area_draw");
to.draw(self.0, &self.1)
}
}

View file

@ -297,6 +297,7 @@ where
impl <'a, S: Screen, I: Draw<S>> Draw<S> for Full<'a, S, I> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("full_draw");
let XYWH(x0, y0, w0, h0) = to.area();
let item = match self {
Self::W(i) => i, Self::H(i) => i, Self::WH(i) => i, _ => unreachable!()
@ -315,6 +316,7 @@ impl <'a, S: Screen, I: Draw<S>> Draw<S> for Full<'a, S, I> {
impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Exact<'a, S, I, X> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("exact_draw");
let XYWH(x0, y0, w0, h0) = to.area();
let (item, w1, h1) = match self {
Self::W(item, w1) => (item, (*w1).into(), None),
@ -337,6 +339,7 @@ impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Ex
impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Min<'a, S, I, X> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("min_draw");
let XYWH(x0, y0, w0, h0) = to.area();
let (item, w1, h1) = match self {
Self::W(item, w1) => (item, (*w1).into(), None),
@ -358,6 +361,7 @@ impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Mi
impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Max<'a, S, I, X> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("max_draw");
let XYWH(x, y, w0, h0) = to.area();
let (item, w, h) = match self {
Self::W(item, w) => (item, (*w).into().unwrap_or(w0), h0),
@ -371,6 +375,7 @@ impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Ma
impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Push<'a, S, I, X> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("push_draw");
match self {
Self::__(_) => unreachable!(),
Self::X(item, x1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
@ -389,6 +394,7 @@ impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Pu
impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Pull<'a, S, I, X> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("pull_draw");
match self {
Self::__(_) => unreachable!(),
Self::X(item, x1) if let Some(XYWH(x, y, w, h)) = to.size(None, item)? => {
@ -413,6 +419,7 @@ impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Pu
impl <'a, S: Screen, I: Draw<S>, X: Into<Option<S::Unit>> + Copy> Draw<S> for Pad<'a, S, I, X> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("pad_draw");
let XYWH(x, y, w0, h0) = to.area();
let (item, w, h) = match self {
Self::X(item, w) => (item, (*w).into().unwrap_or_default(), Default::default()),

View file

@ -116,16 +116,17 @@ pub trait CanAlign<'a, S: Screen>: Draw<S> + Sized {
}
pub struct Align<T>(
pub(crate) Option<Azimuth>,
pub(crate) T,
pub Option<Azimuth>,
pub T,
);
impl<'a, S: Screen, T: Draw<S>> Draw<S> for Align<T> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("align_draw");
let Self(azimuth, item) = self;
let area0 = to.area();
let size = to.size(area0, item)?;
let area1 = align::<S>(area0, size, *azimuth);
let area1 = aligned::<S>(area0, size, *azimuth);
//println!("\n\r{azimuth:?} {area0:?} {size:?}=>{area1:?}");
Ok(if let Some(area) = area1 {
to.draw(area, &item)?
@ -135,9 +136,10 @@ impl<'a, S: Screen, T: Draw<S>> Draw<S> for Align<T> {
}
}
fn align <S: Screen> (
fn aligned <S: Screen> (
area0: XYWH<S::Unit>, area: Option<XYWH<S::Unit>>, azimuth: Option<Azimuth>
) -> Option<XYWH<S::Unit>> {
#[cfg(feature = "prof")] profiling::scope!("align");
area.map(|XYWH(x, y, w, h)|{
let XYWH(x0, y0, w0, h0) = area0;
match azimuth {
@ -158,6 +160,7 @@ fn align <S: Screen> (
}
fn_kw_layout!(kw_split |state, output, expr| {
#[cfg(feature = "prof")] profiling::scope!("kw_split");
let thunk_a = draw(move|screen|ok_flat(expr.nth(1)?.map(|x|state.interpret(screen, x))));
let thunk_b = draw(move|screen|ok_flat(expr.nth(2)?.map(|x|state.interpret(screen, x))));
Ok(match expr.head()? {
@ -205,6 +208,7 @@ pub fn split <'a, S: Screen, A: Draw<S>, B: Draw<S>> (
impl<'a, S: Screen, A: Draw<S>, B: Draw<S>> Draw<S> for Pair<A, B> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("pair_draw");
let Self(split, a, b, ..) = self;
let (area_a, area_b) = stack_areas(split, to, a, b)?;
let (drawn_a, drawn_b) = draw_stacks(split, to, a, area_a, None, b, area_b, None)?;
@ -222,6 +226,7 @@ fn draw_stacks <'a, S: Screen> (
area_b: impl Into<Option<XYWH<S::Unit>>>,
origin_b: impl Into<Option<Azimuth>>,
) -> UsuallyRef<'a, (Option<XYWH<S::Unit>>, Option<XYWH<S::Unit>>)> {
#[cfg(feature = "prof")] profiling::scope!("draw_stacks");
let draw_a = |to: &mut S|Ok::<_, Box<dyn Error>>(match origin_a.into() {
Some(origin_a) => to.draw(area_a.into(), a.align(origin_a))?,
None => to.draw(area_a.into(), a)?
@ -303,6 +308,7 @@ fn stack_drawn <S: Coord> (
drawn_a: Option<XYWH<S>>,
drawn_b: Option<XYWH<S>>,
) -> Option<XYWH<S>> {
#[cfg(feature = "prof")] profiling::scope!("stack_drawn");
if let (Some(XYWH(xa, ya, wa, ha)), Some(XYWH(xb, yb, wb, hb))) = (drawn_a, drawn_b) {
match split {
Split::South => Some(XYWH(xa.min(xb), ya, wa.max(wb), ha + hb)),

View file

@ -1,11 +1,28 @@
use crate::*;
fn_kw_layout!(kw_when |state, output, expr| {
pub struct When<T>(pub bool, pub T);
impl<S: Screen, T: Draw<S>> Draw<S> for When<T> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
#[cfg(feature = "prof")] profiling::scope!("when");
if self.0 {
self.1.draw(to)
} else {
Ok(Default::default())
}
}
}
impl<T> When<T> {
fn_kw_layout!(interpret |state, output, expr| {
#[cfg(feature = "prof")] profiling::scope!("kw_when");
let thunk = draw(move|screen|ok_flat(expr.nth(2)?.map(|x|state.interpret(screen, x))));
ok_flat(matches!(expr.head()?, Some("when")).then(||{
when(state.namespace(&expr.nth(1)?)?.unwrap(), thunk).draw(output)
}))
});
});
}
/// Only render when condition is true.
///
@ -15,10 +32,11 @@ fn_kw_layout!(kw_when |state, output, expr| {
/// # }
/// ```
pub const fn when <'a, T: Screen> (condition: bool, item: impl Draw<T>) -> impl Draw<T> {
draw(move|to: &mut T|if condition { item.draw(to) } else { Ok(Default::default()) })
When(condition, item)
}
fn_kw_layout!(kw_either |state, output, expr| {
#[cfg(feature = "prof")] profiling::scope!("kw_layout");
let thunk_a = draw(move|screen|ok_flat(expr.nth(2)?.map(|x|state.interpret(screen, x))));
let thunk_b = draw(move|screen|ok_flat(expr.nth(3)?.map(|x|state.interpret(screen, x))));
ok_flat(matches!(expr.head()?, Some("either")).then(||{
@ -36,5 +54,8 @@ fn_kw_layout!(kw_either |state, output, expr| {
pub const fn either <'a, T: Screen> (
condition: bool, a: impl Draw<T>, b: impl Draw<T>
) -> impl Draw<T> {
draw(move|to: &mut T|if condition { a.draw(to) } else { b.draw(to) })
draw(move|to: &mut T|{
#[cfg(feature = "prof")] profiling::scope!("either");
if condition { a.draw(to) } else { b.draw(to) }
})
}

View file

@ -68,9 +68,32 @@ mod deps; pub use self::deps::*;
}
/// Implement [`Debug`] in bulk.
#[macro_export] macro_rules! impl_debug (($($S:ty|$self:ident,$w:ident|$body:block)*)=>{
$(impl std::fmt::Debug for $S { fn fmt (&$self, $w: &mut std::fmt::Formatter) -> std::fmt::Result $body })*
});
#[macro_export] macro_rules! impl_debug (
(<$($T:ident $(: $U:ident)?),+> $S:ty|$self:ident,$w:ident|$body:block)=>{
impl <$($T$(:$U)?),+> std::fmt::Debug for $S {
fn fmt (&$self, $w: &mut std::fmt::Formatter) -> std::fmt::Result $body
}
};
($S:ty|$self:ident,$w:ident|$body:block)=>{
impl std::fmt::Debug for $S {
fn fmt (&$self, $w: &mut std::fmt::Formatter) -> std::fmt::Result $body
}
};
);
/// Implement [`Display`] in bulk.
#[macro_export] macro_rules! impl_display (
(<$($T:ident $(: $U:ident)?),+> $S:ty|$self:ident,$w:ident|$body:block)=>{
impl <$($T$(:$U)?),+> std::fmt::Display for $S {
fn fmt (&$self, $w: &mut std::fmt::Formatter) -> std::fmt::Result $body
}
};
($S:ty|$self:ident,$w:ident|$body:block)=>{
impl std::fmt::Display for $S {
fn fmt (&$self, $w: &mut std::fmt::Formatter) -> std::fmt::Result $body
}
};
);
/// Implement [`From`] in bulk.
#[macro_export] macro_rules! impl_from (
@ -280,7 +303,7 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
}
/// Run something with the client.
pub fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
match &*self.0.read().unwrap() {
match &*self.0.try_read().unwrap() {
Inert => panic!("jack client not activated"),
Inactive(client) => op(client),
Activating => panic!("jack client has not finished activation"),
@ -293,14 +316,14 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
let client_state = self.0.clone();
let app: Arc<RwLock<T>> = Arc::new(RwLock::new(init(self)?));
let mut state = Activating;
std::mem::swap(&mut*client_state.write().unwrap(), &mut state);
std::mem::swap(&mut*client_state.try_write().unwrap(), &mut state);
if let Inactive(client) = state {
// This is the misc notifications handler. It's a struct that wraps a [Box]
// which performs type erasure on a callback that takes [JackEvent], which is
// one of the available misc notifications.
let notify = JackNotify(Box::new({
let app = app.clone();
move|event|(&mut*app.write().unwrap()).handle(event)
move|event|(&mut*app.try_write().unwrap()).handle(event)
}) as BoxedJackEventHandler);
// This is the main processing handler. It's a struct that wraps a [Box]
// which performs type erasure on a callback that takes [Client] and [ProcessScope]
@ -308,14 +331,14 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
// implements audio and MIDI input and output on a realtime basis.
let process = ::jack::contrib::ClosureProcessHandler::new(Box::new({
let app = app.clone();
move|c: &_, s: &_|if let Ok(mut app) = app.write() {
move|c: &_, s: &_|if let Some(mut app) = app.try_write() {
app.process(c, s)
} else {
Control::Quit
}
}) as BoxedAudioHandler);
// Launch a client with the two handlers.
*client_state.write().unwrap() = Active(
*client_state.try_write().unwrap() = Active(
client.activate_async(notify, process)?
);
} else {
@ -445,7 +468,7 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
if let Some(mut state) = state.try_write() {
state.process(client, scope)
} else {
Control::Quit
@ -612,10 +635,10 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
for connect in self.connections().iter() {
match &connect.name {
Some(Exact(name)) => {
*connect.status.write().unwrap() = self.connect_exact(name)?;
*connect.status.try_write().unwrap() = self.connect_exact(name)?;
},
Some(RegExp(re)) => {
*connect.status.write().unwrap() = self.connect_regexp(re, connect.scope)?;
*connect.status.try_write().unwrap() = self.connect_regexp(re, connect.scope)?;
},
_ => {},
};
@ -922,7 +945,7 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
self.output_buffer[sample].push(self.note_buffer.clone());
// Update the list of currently held notes.
if let LiveEvent::Midi { ref message, .. } = event {
update_keys(&mut*self.held.write().unwrap(), message);
update_keys(&mut*self.held.try_write().unwrap(), message);
}
}
/// Write a chunk of MIDI data from the output buffer to the output port.
@ -1079,7 +1102,7 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
pub fn info (&self) -> Arc<str> {
format!(" ({}) {} {}", {
let status = self.status.read().unwrap();
let status = self.status.try_read().unwrap();
let mut ok = 0;
for (_, _, state) in status.iter() {
if *state == Connected {
@ -1167,6 +1190,8 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
use crate::time::PerfModel;
#[derive(Debug)] pub struct Task {
/// Human-friendly name
pub name: Arc<str>,
/// Exit flag.
pub exit: Arc<AtomicBool>,
/// Performance counter.
@ -1177,30 +1202,37 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
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
{
pub fn new <F: FnMut(&PerfModel)->() + Send + Sync + 'static> (
name: Option<impl AsRef<str>>,
exit: Arc<AtomicBool>,
mut call: F
) -> Result<Self, std::io::Error> {
let perf = Arc::new(PerfModel::default());
let name: Arc<str> = name.map(|x|x.as_ref().into()).unwrap_or_else(||"tengri".into());
Ok(Self {
exit: exit.clone(),
perf: perf.clone(),
join: Builder::new().name("tengri task".into()).spawn(move || {
join: Builder::new().name(name.as_ref().into()).spawn(move || {
#[cfg(feature = "prof")] profiling::register_thread!();
while !exit.fetch_and(true, Relaxed) {
let _ = perf.cycle(&mut call);
}
})?.into()
})?.into(),
name
})
}
/// 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
name: Option<impl AsRef<str>>,
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| {
Self::new(name, exit, move |perf| {
let _ = call(perf);
sleep(time);
})
@ -1209,11 +1241,14 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
/// 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
name: Option<impl AsRef<str>>,
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| {
Self::new(name, exit, move |perf| {
if poll(time).is_ok() {
let _ = call(perf);
}
@ -1366,7 +1401,19 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
}
}
impl<'a, S: Screen, D: Draw<S>> Draw<S> for Option<D> {
impl<S: Screen, D: Draw<S>> Draw<S> for Arc<D> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
(**self).draw(to)
}
}
impl<S: Screen, D: Draw<S>> Draw<S> for Box<D> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
(**self).draw(to)
}
}
impl<S: Screen, D: Draw<S>> Draw<S> for Option<D> {
fn draw (&self, to: &mut S) -> Drawn<S::Unit> {
self.as_ref().map(|it|it.draw(to)).transpose().map(Option::unwrap_or_default)
}
@ -1378,12 +1425,6 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
//}
//}
//impl<T: Screen, D: Draw<T>> Draw<T> for Arc<D> {
//fn draw (&self, __: &mut T) -> Perhaps<XYWH<T::Unit>> {
//todo!()
//}
//}
impl<'a, T: Screen, V: Draw<T>> Draw<T> for &V {
fn draw (&self, to: &mut T) -> Drawn<T::Unit> {
(*self).draw(to)

View file

@ -162,11 +162,11 @@ impl Tui {
) -> Result<Task, std::io::Error> {
let exited = exited.clone();
let state = state.clone();
Task::new_poll(exited.clone(), poll, move |_| {
Task::new_poll(Some("tengri input"), exited.clone(), poll, move |_| {
let event = read().unwrap();
if Exit::is(&event) {
exited.store(true, Relaxed);
} else if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) {
} else if let Err(e) = state.write().apply(&TuiEvent(event)) {
panic!("{e}")
}
})
@ -268,15 +268,15 @@ impl Tui {
let Size { width, height } = backend.size().expect("get size failed");
let mut prev = Tui::new(width, height);
let mut next = Tui::new(width, height);
Ok(Task::new_sleep(exited.clone(), sleep, move |perf| {
Ok(Task::new_sleep(Some("tengri output"), exited.clone(), sleep, move |perf| {
let Size { width, height } = backend.size().expect("get size failed");
if let Ok(state) = state.try_read() {
if let Some(state) = state.try_read() {
prev.resize(&mut backend, width, height);
state.draw(&mut next).expect("draw failed"); // TODO draw error
prev.redraw(&mut backend, &mut next);
}
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
prev.blit(&timer, 0, 0, Some(Style::default()));
//let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
//prev.blit(&timer, 0, 0, Some(Style::default()));
})?)
}
/// Draw TUI content or its error message.