diff --git a/examples/units.rs b/examples/units.rs index 842f842..0033c43 100644 --- a/examples/units.rs +++ b/examples/units.rs @@ -26,7 +26,7 @@ fn main() -> Result { } fn work_for_a_long_time_blocking(root: Arc) { - let mut bytes = root.add_child("download unknown"); + let mut bytes = root.add_child_with_id("download unknown", *b"DLUK"); bytes.init( None, Some(unit::dynamic_and_mode( @@ -34,7 +34,7 @@ fn work_for_a_long_time_blocking(root: Arc) { unit::display::Mode::with_throughput(), )), ); - let mut bytes_max = root.add_child("download"); + let mut bytes_max = root.add_child_with_id("download", *b"DLKN"); bytes_max.init( Some(100_000_000), Some(unit::dynamic_and_mode( @@ -43,9 +43,9 @@ fn work_for_a_long_time_blocking(root: Arc) { )), ); - let mut duration = root.add_child("duration unknown"); + let mut duration = root.add_child_with_id("duration unknown", *b"DRUK"); duration.init(None, Some(unit::dynamic(unit::Duration))); - let mut duration_max = root.add_child("duration"); + let mut duration_max = root.add_child_with_id("duration", *b"DRKN"); duration_max.init( Some(60 * 60 * 24), Some(unit::dynamic_and_mode( @@ -59,7 +59,7 @@ fn work_for_a_long_time_blocking(root: Arc) { f.with_decimals(decimals); f } - let mut human_count = root.add_child("item count unknown"); + let mut human_count = root.add_child_with_id("item count unknown", *b"ITUK"); human_count.init( None, Some(unit::dynamic_and_mode( @@ -67,7 +67,7 @@ fn work_for_a_long_time_blocking(root: Arc) { unit::display::Mode::with_throughput(), )), ); - let mut human_count_max = root.add_child("item count"); + let mut human_count_max = root.add_child_with_id("item count", *b"ITKN"); human_count_max.init( Some(7_542_241), Some(unit::dynamic_and_mode( @@ -76,7 +76,7 @@ fn work_for_a_long_time_blocking(root: Arc) { )), ); - let mut steps = root.add_child("steps to take unknown"); + let mut steps = root.add_child_with_id("steps to take unknown", *b"STUK"); steps.init( None, Some(unit::dynamic_and_mode( @@ -84,7 +84,7 @@ fn work_for_a_long_time_blocking(root: Arc) { unit::display::Mode::with_throughput(), )), ); - let mut steps_max = root.add_child("steps to take"); + let mut steps_max = root.add_child_with_id("steps to take", *b"STKN"); steps_max.init( Some(100), Some(unit::dynamic_and_mode( diff --git a/src/progress/log.rs b/src/progress/log.rs index e86a66a..47395c4 100644 --- a/src/progress/log.rs +++ b/src/progress/log.rs @@ -1,4 +1,4 @@ -use crate::progress::{Step, StepShared}; +use crate::progress::{Id, Step, StepShared}; use crate::{messages::MessageLevel, Progress, Unit}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -10,6 +10,7 @@ use std::time::Duration; /// to see if progress information should actually be emitted. pub struct Log { name: String, + id: Id, max: Option, unit: Option, step: usize, @@ -37,6 +38,7 @@ impl Log { }); Log { name: name.into(), + id: crate::progress::UNKNOWN, current_level: 0, max_level: max_level.unwrap_or(usize::MAX), max: None, @@ -51,8 +53,13 @@ impl Progress for Log { type SubProgress = Log; fn add_child(&mut self, name: impl Into) -> Self::SubProgress { + self.add_child_with_id(name, crate::progress::UNKNOWN) + } + + fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Self::SubProgress { Log { name: format!("{}{}{}", self.name, SEP, Into::::into(name)), + id, current_level: self.current_level + 1, max_level: self.max_level, step: 0, @@ -117,6 +124,10 @@ impl Progress for Log { self.name.split(SEP).nth(1).map(ToOwned::to_owned) } + fn id(&self) -> Id { + self.id + } + fn message(&mut self, level: MessageLevel, message: impl Into) { let message: String = message.into(); match level { diff --git a/src/progress/mod.rs b/src/progress/mod.rs index 360b501..3139d20 100644 --- a/src/progress/mod.rs +++ b/src/progress/mod.rs @@ -17,6 +17,18 @@ pub use self::log::Log; pub use utils::{Discard, DoOrDiscard, Either, ThroughputOnDrop}; +/// Four bytes of function-local unique and stable identifier for each item added as progress, +/// like b"TREE" or b"FILE". +/// +/// Note that uniqueness only relates to one particular method call where those interested in its progress +/// may assume certain stable ids to look for when selecting specific bits of progress to process. +pub type Id = [u8; 4]; + +/// The default Id to use if there is no need for an id. +/// +/// This is the default unless applications wish to make themselves more introspectable. +pub const UNKNOWN: Id = *b"\0\0\0\0"; + /// The amount of steps a progress can make pub type Step = usize; @@ -72,6 +84,9 @@ impl Value { pub struct Task { /// The name of the `Item` or task. pub name: String, + /// The stable identifier of this task. + /// Useful for selecting specific tasks out of a set of them. + pub id: Id, /// The progress itself, unless this value belongs to an `Item` serving as organizational unit. pub progress: Option, } diff --git a/src/progress/utils.rs b/src/progress/utils.rs index a13a524..e444e32 100644 --- a/src/progress/utils.rs +++ b/src/progress/utils.rs @@ -1,4 +1,4 @@ -use crate::{messages::MessageLevel, Progress, Unit}; +use crate::{messages::MessageLevel, progress::Id, Progress, Unit}; /// An implementation of [`Progress`] which discards all calls. pub struct Discard; @@ -10,6 +10,10 @@ impl Progress for Discard { Discard } + fn add_child_with_id(&mut self, _name: impl Into, _id: Id) -> Self::SubProgress { + Discard + } + fn init(&mut self, _max: Option, _unit: Option) {} fn set(&mut self, _step: usize) {} @@ -30,6 +34,10 @@ impl Progress for Discard { None } + fn id(&self) -> Id { + crate::progress::UNKNOWN + } + fn message(&mut self, _level: MessageLevel, _message: impl Into) {} fn counter(&self) -> Option { @@ -62,6 +70,13 @@ where } } + fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Self::SubProgress { + match self { + Either::Left(l) => Either::Left(l.add_child_with_id(name, id)), + Either::Right(r) => Either::Right(r.add_child_with_id(name, id)), + } + } + fn init(&mut self, max: Option, unit: Option) { match self { Either::Left(l) => l.init(max, unit), @@ -125,6 +140,10 @@ where } } + fn id(&self) -> Id { + todo!() + } + fn message(&mut self, level: MessageLevel, message: impl Into) { match self { Either::Left(l) => l.message(level, message), @@ -184,6 +203,10 @@ where DoOrDiscard(self.0.add_child(name)) } + fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Self::SubProgress { + DoOrDiscard(self.0.add_child_with_id(name, id)) + } + fn init(&mut self, max: Option, unit: Option) { self.0.init(max, unit) } @@ -220,6 +243,10 @@ where self.0.name() } + fn id(&self) -> Id { + self.0.id() + } + fn message(&mut self, level: MessageLevel, message: impl Into) { self.0.message(level, message) } @@ -249,6 +276,10 @@ impl Progress for ThroughputOnDrop { self.0.add_child(name) } + fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Self::SubProgress { + self.0.add_child_with_id(name, id) + } + fn init(&mut self, max: Option, unit: Option) { self.0.init(max, unit) } @@ -285,6 +316,10 @@ impl Progress for ThroughputOnDrop { self.0.name() } + fn id(&self) -> Id { + self.0.id() + } + fn message(&mut self, level: MessageLevel, message: impl Into) { self.0.message(level, message) } diff --git a/src/render/tui/draw/progress.rs b/src/render/tui/draw/progress.rs index 7d64567..ffd934b 100644 --- a/src/render/tui/draw/progress.rs +++ b/src/render/tui/draw/progress.rs @@ -196,7 +196,20 @@ pub fn draw_progress( None => state, }); - for (line, (entry_index, (key, Task { progress, name: title }))) in entries + for ( + line, + ( + entry_index, + ( + key, + Task { + progress, + name: title, + id: _, + }, + ), + ), + ) in entries .iter() .enumerate() .skip(offset as usize) diff --git a/src/render/tui/mod.rs b/src/render/tui/mod.rs index 66582b9..5660a8a 100644 --- a/src/render/tui/mod.rs +++ b/src/render/tui/mod.rs @@ -9,15 +9,17 @@ * ```should_panic * # fn main() -> Result<(), Box> { * use futures::task::{LocalSpawnExt, SpawnExt}; -* use prodash::tui::ticker; +* use prodash::render::tui::ticker; +* use prodash::Root; * // obtain a progress tree * let root = prodash::Tree::new(); * // Configure the gui, provide it with a handle to the ever-changing tree -* let render_fut = prodash::tui::render( -* root.clone(), -* prodash::tui::Options { +* let render_fut = prodash::render::tui::render( +* std::io::stdout(), +* root.downgrade(), +* prodash::render::tui::Options { * title: "minimal example".into(), -* ..prodash::tui::Options::default() +* ..prodash::render::tui::Options::default() * } * )?; * // As it runs forever, we want a way to stop it. @@ -30,7 +32,7 @@ * use futures::StreamExt; * let mut progress = root.add_child("task"); * async move { -* progress.init(None, Some("items")); +* progress.init(None, None); * let mut count = 0; * let mut ticks = ticker(std::time::Duration::from_millis(100)); * while let Some(_) = ticks.next().await { diff --git a/src/traits.rs b/src/traits.rs index 782ee77..b279b13 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,4 @@ -use crate::{messages::MessageLevel, progress, Unit}; +use crate::{messages::MessageLevel, progress, progress::Id, Unit}; use std::time::Instant; /// A trait for describing hierarchical process. @@ -6,11 +6,19 @@ pub trait Progress: Send { /// The type of progress returned by [`add_child()`][Progress::add_child()]. type SubProgress: Progress; - /// Adds a new child, whose parent is this instance, with the given name. + /// Adds a new child, whose parent is this instance, with the given `name`. /// /// This will make the child progress to appear contained in the parent progress. + /// Note that such progress does not have a stable identifier, which can be added + /// with [`add_child_with_id()`][Progress::add_child_with_id()] if desired. fn add_child(&mut self, name: impl Into) -> Self::SubProgress; + /// Adds a new child, whose parent is this instance, with the given `name` and `id`. + /// + /// This will make the child progress to appear contained in the parent progress, and it can be identified + /// using `id`. + fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Self::SubProgress; + /// Initialize the Item for receiving progress information. /// /// If `max` is `Some(…)`, it will be treated as upper bound. When progress is [set(…)](./struct.Item.html#method.set) @@ -70,6 +78,10 @@ pub trait Progress: Send { /// The progress is allowed to not be named, thus there is no guarantee that a previously set names 'sticks'. fn name(&self) -> Option; + /// Get a stable identifier for the progress instance. + /// Note that it could be [unknown][crate::progress::UNKNOWN]. + fn id(&self) -> Id; + /// Create a `message` of the given `level` and store it with the progress tree. /// /// Use this to provide additional,human-readable information about the progress @@ -186,7 +198,7 @@ pub trait Root { mod impls { use crate::messages::MessageLevel; - use crate::progress::{Step, StepShared}; + use crate::progress::{Id, Step, StepShared}; use crate::{Progress, Unit}; use std::ops::{Deref, DerefMut}; use std::time::Instant; @@ -201,6 +213,10 @@ mod impls { self.deref_mut().add_child(name) } + fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Self::SubProgress { + self.deref_mut().add_child_with_id(name, id) + } + fn init(&mut self, max: Option, unit: Option) { self.deref_mut().init(max, unit) } @@ -241,6 +257,10 @@ mod impls { self.deref().name() } + fn id(&self) -> Id { + todo!() + } + fn message(&mut self, level: MessageLevel, message: impl Into) { self.deref_mut().message(level, message) } diff --git a/src/tree/item.rs b/src/tree/item.rs index 96ba1d2..086642c 100644 --- a/src/tree/item.rs +++ b/src/tree/item.rs @@ -1,7 +1,7 @@ use crate::progress::StepShared; use crate::{ messages::{MessageLevel, MessageRingBuffer}, - progress::{key, Key, State, Step, Task, Value}, + progress::{key, Id, Key, State, Step, Task, Value}, unit::Unit, }; use dashmap::DashMap; @@ -21,7 +21,7 @@ use std::{ops::Deref, sync::Arc, time::SystemTime}; /// progress.set(p); /// } /// progress.done("great success"); -/// let mut sub_progress = progress.add_child("sub-task 1"); +/// let mut sub_progress = progress.add_child_with_id("sub-task 1", *b"TSK2"); /// sub_progress.init(None, None); /// sub_progress.set(5); /// sub_progress.fail("couldn't finish"); @@ -90,6 +90,14 @@ impl Item { self.tree.get(&self.key).map(|r| r.value().name.to_owned()) } + /// Get the stable identifier of this instance. + pub fn id(&self) -> Id { + self.tree + .get(&self.key) + .map(|r| r.value().id) + .unwrap_or(crate::progress::UNKNOWN) + } + /// Returns the current step, as controlled by `inc*(…)` calls pub fn step(&self) -> Option { self.value.load(Ordering::Relaxed).into() @@ -180,11 +188,21 @@ impl Item { /// Exceeding the level will be ignored, and new tasks will be added to this instance's /// level instead. pub fn add_child(&mut self, name: impl Into) -> Item { + self.add_child_with_id(name, crate::progress::UNKNOWN) + } + + /// Adds a new child `Tree`, whose parent is this instance, with the given `name` and `id`. + /// + /// **Important**: The depth of the hierarchy is limited to [`tree::Key::max_level`](./struct.Key.html#method.max_level). + /// Exceeding the level will be ignored, and new tasks will be added to this instance's + /// level instead. + pub fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Item { let child_key = self.key.add_child(self.highest_child_id); self.tree.insert( child_key, Task { name: name.into(), + id, progress: None, }, ); @@ -254,6 +272,10 @@ impl crate::Progress for Item { Item::add_child(self, name) } + fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Self::SubProgress { + Item::add_child_with_id(self, name, id) + } + fn init(&mut self, max: Option, unit: Option) { Item::init(self, max, unit) } @@ -290,6 +312,10 @@ impl crate::Progress for Item { Item::name(self) } + fn id(&self) -> Id { + Item::id(self) + } + fn message(&mut self, level: MessageLevel, message: impl Into) { Item::message(self, level, message) } diff --git a/src/tree/root.rs b/src/tree/root.rs index 9311aa2..947385d 100644 --- a/src/tree/root.rs +++ b/src/tree/root.rs @@ -1,6 +1,6 @@ use crate::{ messages::{Message, MessageCopyState, MessageRingBuffer}, - progress::{Key, Task}, + progress::{Id, Key, Task}, tree::Item, }; use dashmap::DashMap; @@ -43,6 +43,14 @@ impl Root { self.inner.lock().add_child(name) } + /// Adds a new child `tree::Item`, whose parent is this instance, with the given `name` and `id`. + /// + /// This builds a hierarchy of `tree::Item`s, each having their own progress. + /// Use this method to [track progress](./struct.Item.html) of your first tasks. + pub fn add_child_with_id(&self, name: impl Into, id: Id) -> Item { + self.inner.lock().add_child_with_id(name, id) + } + /// Copy the entire progress tree into the given `out` vector, so that /// it can be traversed from beginning to end in order of hierarchy. pub fn sorted_snapshot(&self, out: &mut Vec<(Key, Task)>) {