From 534ddba827e73b0e9cad63979fc1f4a8e5fb7b50 Mon Sep 17 00:00:00 2001 From: Juergen Stuber Date: Sun, 16 Apr 2023 19:13:11 +0200 Subject: [PATCH] Receive in a thread to avoid blocking the window event handling --- src/main.rs | 126 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 34 deletions(-) diff --git a/src/main.rs b/src/main.rs index d0053a2..6b75bfa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,14 @@ +use core::mem::swap; +use core::sync::atomic::AtomicBool; +use core::sync::atomic::Ordering; + use std::error; use std::io::Read; use std::path; use std::process; +use std::sync::Arc; +use std::sync::Mutex; +use std::thread; use clap::Parser; @@ -28,13 +35,29 @@ struct Cli { debug: u8, } +struct Buffer { + frame: Vec, + count: u64, +} +impl Buffer { + fn new(size: usize) -> Buffer { + Buffer { + frame: vec![0; size], + count: 0, + } + } + fn is_newer(&self, other: &Buffer) -> bool { + self.count > other.count + } +} + fn main() -> Result<(), Box> { let cli = Cli::parse(); if cli.debug >= 2 { eprintln!("CLI: {:?}", cli); } - let mut child = process::Command::new(&cli.executable) + let mut app = process::Command::new(&cli.executable) .arg(cli.cols.to_string()) .arg(cli.rows.to_string()) .args(cli.args) @@ -44,63 +67,98 @@ fn main() -> Result<(), Box> { eprintln!( "Running {} as a child process with pid {}", cli.executable.display(), - child.id() + app.id() ); } - let mut stdout = child + let mut stdout = app .stdout .take() - .ok_or("opening child process stdout failed")?; + .ok_or("opening app process stdout failed")?; // TODO support also RGBW let colors = 3; // TODO check for overflow and excessive size let buffer_size = cli.cols * cli.rows * colors; - let mut buffer = vec![0_u8; buffer_size]; let mut frame_count = 0; + let x_size = cli.cols; + let y_size = cli.rows; + let mut window: PistonWindow = WindowSettings::new( format!("pixelfoo-viewer {}", cli.executable.display()), - [1600, 800], + [20 * (x_size as u32), 20 * (y_size as u32)], ) .exit_on_esc(true) .vsync(true) .build()?; - let x_size = cli.cols; - let y_size = cli.rows; - while let Some(event) = window.next() { - window.draw_2d(&event, |context, graphics, _device| { - let [vsx, vsy] = context.get_view_size(); + // Shared buffer for one frame of data from the app. + let buffer0 = Arc::new(Mutex::new(Buffer::new(buffer_size))); + let buffer1 = buffer0.clone(); - // Compute cell size - let csx = (vsx as f64) / (x_size as f64); - let csy = (vsy as f64) / (y_size as f64); - let cs = csx.min(csy); + let receiver_stop = AtomicBool::new(false); + thread::scope(|scope| { + let receiver_thread = scope.spawn(|| { + let mut buffer = Buffer::new(buffer_size); + while !receiver_stop.load(Ordering::Relaxed) { + // Read a frame from app process. + stdout + .read_exact(&mut buffer.frame) + .expect("pipe read failed"); + if cli.debug >= 3 { + eprintln!("Received frame {}", frame_count); + } + frame_count += 1; + buffer.count = frame_count; - pw::clear([0.5, 0.5, 0.5, 1.0], graphics); - - stdout.read_exact(&mut buffer).expect("pipe read failed"); - if cli.debug >= 3 { - eprintln!("Received frame {}", frame_count); - } - - for y in 0..y_size { - for x in 0..x_size { - let i = colors * (y * x_size + x); - let r = f32::from(buffer[i + 0]) / 255.0; - let g = f32::from(buffer[i + 1]) / 255.0; - let b = f32::from(buffer[i + 2]) / 255.0; - let color = [r, g, b, 1.0]; - let rectangle = [(x as f64) * cs, (y as f64) * cs, cs, cs]; - pw::rectangle(color, rectangle, context.transform, graphics); + // Put the frame in the shared buffer. + { + let mut b = buffer0.lock().unwrap(); + swap(&mut *b, &mut buffer); } } }); - frame_count += 1; - } - child.kill()?; + + let mut buffer = Buffer::new(buffer_size); + while let Some(event) = window.next() { + window.draw_2d(&event, |context, graphics, _device| { + let [vsx, vsy] = context.get_view_size(); + + // Compute cell size + let csx = (vsx as f64) / (x_size as f64); + let csy = (vsy as f64) / (y_size as f64); + let cs = csx.min(csy); + + pw::clear([0.5, 0.5, 0.5, 1.0], graphics); + + // Swap the shared buffer with our own buffer if it is newer. + { + let mut b = buffer1.lock().unwrap(); + if b.is_newer(&buffer) { + swap(&mut *b, &mut buffer); + } + }; + + // Display the buffer. + for y in 0..y_size { + for x in 0..x_size { + let i = colors * (y * x_size + x); + let r = f32::from(buffer.frame[i + 0]) / 255.0; + let g = f32::from(buffer.frame[i + 1]) / 255.0; + let b = f32::from(buffer.frame[i + 2]) / 255.0; + let color = [r, g, b, 1.0]; + let rectangle = [(x as f64) * cs, (y as f64) * cs, cs, cs]; + pw::rectangle(color, rectangle, context.transform, graphics); + } + } + }); + } + receiver_stop.store(true, Ordering::Relaxed); + let _result = receiver_thread.join(); + }); + + app.kill()?; Ok(()) }