Skip to content

Commit

Permalink
Add multithreaded example
Browse files Browse the repository at this point in the history
  • Loading branch information
sourcefrog committed Mar 7, 2022
1 parent 2f6dc7e commit 7a3c61b
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 9 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ yansi = "0.5"

[dev-dependencies]
pretty_assertions = "1.1"
rand = "0.8"
80 changes: 80 additions & 0 deletions examples/multithreaded.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2022 Martin Pool.

//! Demonstrate multiple threads writing to a single view.
//!
//! A single View is shared in an Arc across all threads. (A scoped thread
//! would also work.)
//!
//! Each thread periodically updates the model, which will make it repaint
//! subject to the update rate limit.
use std::fmt::Write;
use std::io;
use std::sync::Arc;
use std::thread::{self, sleep};
use std::time::Duration;

use rand::Rng;

const THREAD_WORK_MAX: usize = 20;

/// Per-thread progress.
struct JobState {
x: usize,
complete: bool,
}

/// Overall task progress.
struct Model {
job_state: Vec<JobState>,
}

impl nutmeg::Model for Model {
fn render(&mut self, _width: usize) -> String {
let mut s = String::new();
let n_jobs = self.job_state.len();
let n_complete = self.job_state.iter().filter(|j| j.complete).count();
writeln!(s, "{}/{} complete", n_complete, n_jobs).unwrap();
for (i, js) in self.job_state.iter().enumerate() {
let remains = THREAD_WORK_MAX - js.x;
writeln!(s, "{:3}: {}{}", i, "#".repeat(js.x), "_".repeat(remains)).unwrap();
}
s
}
}

fn work<T: io::Write>(i_thread: usize, arc_view: Arc<nutmeg::View<Model, T>>) {
let mut rng = rand::thread_rng();
for j in 0..=THREAD_WORK_MAX {
arc_view.update(|model| model.job_state[i_thread].x = j);
sleep(Duration::from_millis(rng.gen_range(100..600)));
}
arc_view.update(|model| model.job_state[i_thread].complete = true);
}

fn main() {
let model = Model {
job_state: Vec::new(),
};
let view = nutmeg::View::new(model, nutmeg::Options::default());
view.update(|_m| ());
let arc_view = Arc::new(view);
let mut join_handles = Vec::new();
for i_thread in 0..20 {
arc_view.update(|model| {
model.job_state.push(JobState {
x: 0,
complete: false,
})
});
sleep(Duration::from_millis(100));
let give_arc_view = arc_view.clone();
join_handles.push(thread::spawn(move || work(i_thread, give_arc_view)));
}
for join_handle in join_handles {
arc_view.update(|_m| ());
join_handle.join().unwrap();
}
arc_view.update(|_| ());
sleep(Duration::from_millis(500));
}
19 changes: 11 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,32 +307,35 @@ where
}

/// Print a message to the view.
///
/// The progress bar, if present, is removed to print the message
///
/// The progress bar, if present, is removed to print the message
/// and then remains off for a time controlled by [Options::print_holdoff].
///
///
/// The message may contain ANSI control codes for styling.
///
///
/// The message may contain multiple lines.
///
///
/// If the last character of the message is *not* '\n' then the incomplete
/// line remains on the terminal, and the progress bar will not be painted
/// until it is completed by a message finishing in `\n`.
///
///
/// This is equivalent to `write!(view, ...)` except:
/// * [std::io::Write::write] requires a `&mut View`, while `message`
/// can be called on a `&View`.
/// * `message` panics on an error writing to the terminal; `write!` requires
/// the caller to handle a `Result`.
/// * `write!` integrates string formatting; `message` does not.
///
///
/// ```
/// let view = nutmeg::View::new(0, nutmeg::Options::default());
/// // ...
/// view.message(&format!("{} splines reticulated\n", 42));
/// ```
pub fn message(&self, message: &str) {
self.inner.lock().write(message.as_bytes()).expect("writing message");
self.inner
.lock()
.write(message.as_bytes())
.expect("writing message");
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/to_print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//!
//! This module implements a bit of a hack to get default progress output
//! captured, by redirecting a `Write` into `print!`.
//!
//!
//! For context on this weird workaround see
//! <https://github.com/rust-lang/rust/issues/31343>.
Expand Down

0 comments on commit 7a3c61b

Please sign in to comment.