diff --git a/src/args.rs b/src/args.rs index 1fdaee0..1e08039 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,5 +1,7 @@ use clap::Parser; +use crate::pix::client::FlushMode; + #[derive(Parser)] #[command(author, version, about, disable_help_flag = true)] pub struct Arguments { @@ -49,9 +51,13 @@ pub struct Arguments { #[arg(short, long, alias = "bin")] binary: bool, - /// Flush socket after each pixel [default: true] - #[arg(short, long, action = clap::ArgAction::Set, value_name = "ENABLED", default_value_t = true)] - flush: bool, + /// Mode used for flushing the buffer [default: commands] + #[arg(long, value_name = "MODE", default_value_t = FlushMode::Commands)] + flushmode: FlushMode, + + /// Size after which the buffer should be flushed [default: true] + #[arg(long, value_name = "PIXELS", default_value_t = 1)] + flushsize: u16, } /// CLI argument handler. @@ -110,7 +116,12 @@ impl ArgHandler { } /// Whether to flush after each pixel. - pub fn flush(&self) -> bool { - self.data.flush + pub fn flush_mode(&self) -> FlushMode { + self.data.flushmode.clone() + } + + /// Whether to flush after each pixel. + pub fn flush_size(&self) -> u16 { + self.data.flushsize } } diff --git a/src/main.rs b/src/main.rs index 436af97..23152f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,8 @@ use image_manager::ImageManager; use pix::canvas::Canvas; use pix::client::Client; +use crate::pix::client::FlushMode; + /// Main application entrypoint. fn main() { // Parse CLI arguments @@ -40,7 +42,8 @@ fn start(arg_handler: &ArgHandler) { size, arg_handler.offset(), arg_handler.binary(), - arg_handler.flush(), + arg_handler.flush_mode(), + arg_handler.flush_size(), ); // Load the image manager @@ -53,7 +56,13 @@ fn start(arg_handler: &ArgHandler) { /// Gather important facts about the host. fn gather_host_facts(arg_handler: &ArgHandler) -> Result<(u16, u16), Error> { // Set up a client, and get the screen size - let size = Client::connect(arg_handler.host().to_string(), false, false)?.read_screen_size()?; + let size = Client::connect( + arg_handler.host().to_string(), + false, + FlushMode::Commands, + 1, + )? + .read_screen_size()?; // Print status println!("Gathered screen size: {}x{}", size.0, size.1); diff --git a/src/painter/painter.rs b/src/painter/painter.rs index a83f490..f8b0ed5 100644 --- a/src/painter/painter.rs +++ b/src/painter/painter.rs @@ -85,6 +85,9 @@ impl Painter { } } + if let Some(client) = &mut self.client { + client.flush()?; + } // Everything seems to be ok Ok(()) } diff --git a/src/pix/canvas.rs b/src/pix/canvas.rs index d15d497..7d9c472 100644 --- a/src/pix/canvas.rs +++ b/src/pix/canvas.rs @@ -10,6 +10,8 @@ use crate::painter::painter::Painter; use crate::pix::client::Client; use crate::rect::Rect; +use super::client::FlushMode; + /// A pixflut instance pub struct Canvas { host: String, @@ -27,7 +29,8 @@ impl Canvas { size: (u16, u16), offset: (u16, u16), binary: bool, - flush: bool, + flush_mode: FlushMode, + flush_size: u16, ) -> Canvas { // Initialize the object let mut canvas = Canvas { @@ -42,14 +45,14 @@ impl Canvas { println!("Starting painter threads..."); // Spawn some painters - canvas.spawn_painters(binary, flush); + canvas.spawn_painters(binary, flush_mode, flush_size); // Return the canvas canvas } /// Spawn the painters for this canvas - fn spawn_painters(&mut self, binary: bool, flush: bool) { + fn spawn_painters(&mut self, binary: bool, flush_mode: FlushMode, flush_size: u16) { // Spawn some painters for i in 0..self.painter_count { // Determine the slice width @@ -59,12 +62,12 @@ impl Canvas { let painter_area = Rect::from((i as u16) * width, 0, width, self.size.1); // Spawn the painter - self.spawn_painter(painter_area, binary, flush); + self.spawn_painter(painter_area, binary, flush_mode.clone(), flush_size); } } /// Spawn a single painter in a thread. - fn spawn_painter(&mut self, area: Rect, binary: bool, flush: bool) { + fn spawn_painter(&mut self, area: Rect, binary: bool, flush_mode: FlushMode, flush_size: u16) { // Get the host that will be used let host = self.host.to_string(); @@ -81,7 +84,7 @@ impl Canvas { loop { // Connect - match Client::connect(host.clone(), binary, flush) { + match Client::connect(host.clone(), binary, flush_mode.clone(), flush_size) { Ok(client) => { painter.set_client(Some(client)); diff --git a/src/pix/client.rs b/src/pix/client.rs index ba53ab2..fbc3194 100644 --- a/src/pix/client.rs +++ b/src/pix/client.rs @@ -1,8 +1,11 @@ +use std::fmt::Display; use std::io::prelude::*; use std::io::{Error, ErrorKind}; use std::net::TcpStream; +use std::str::FromStr; use bufstream::BufStream; +use clap::ValueEnum; use regex::Regex; use crate::color::Color; @@ -15,6 +18,36 @@ const CMD_READ_BUFFER_SIZE: usize = 32; // The response format of the screen size from a pixelflut server. const PIX_SERVER_SIZE_REGEX: &str = r"^(?i)\s*SIZE\s+([[:digit:]]+)\s+([[:digit:]]+)\s*$"; +#[derive(Clone, ValueEnum)] +pub enum FlushMode { + Manual, + Bytes, + Commands, +} + +impl FromStr for FlushMode { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "manual" => Ok(FlushMode::Manual), + "bytes" => Ok(FlushMode::Bytes), + "commands" => Ok(FlushMode::Commands), + _ => Err(()), + } + } +} + +impl Display for FlushMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FlushMode::Manual => write!(f, "manual"), + FlushMode::Bytes => write!(f, "bytes"), + FlushMode::Commands => write!(f, "commands"), + } + } +} + /// A pixelflut client. /// /// This client uses a stream to talk to a pixelflut panel. @@ -28,24 +61,38 @@ pub struct Client { /// Whether to use binary mode (PB) instead of (PX). binary: bool, - /// Whether to flush the stream after each pixel. - flush: bool, + /// When the stream should be flushed. + flush_mode: FlushMode, + current_size: u16, + flush_size: u16, } impl Client { /// Create a new client instance. - pub fn new(stream: TcpStream, binary: bool, flush: bool) -> Client { + pub fn new(stream: TcpStream, binary: bool, flush_mode: FlushMode, flush_size: u16) -> Client { Client { stream: BufStream::new(stream), binary, - flush, + flush_mode, + flush_size, + current_size: 0, } } /// Create a new client instane from the given host, and connect to it. - pub fn connect(host: String, binary: bool, flush: bool) -> Result { + pub fn connect( + host: String, + binary: bool, + flush_mode: FlushMode, + flush_size: u16, + ) -> Result { // Create a new stream, and instantiate the client - Ok(Client::new(create_stream(host)?, binary, flush)) + Ok(Client::new( + create_stream(host)?, + binary, + flush_mode, + flush_size, + )) } /// Write a pixel to the given stream. @@ -99,22 +146,49 @@ impl Client { } } + /// Flush the write buffer. + pub fn flush(&mut self) -> Result<(), Error> { + self.stream.flush()?; + self.current_size = 0; + Ok(()) + } + /// Write the given command to the given stream. fn write_command(&mut self, cmd: &[u8], newline: bool) -> Result<(), Error> { // Write the pixels and a new line + + let new_size: u16 = match self.flush_mode { + FlushMode::Manual => { + self.stream.write_all(cmd)?; + if newline { + self.stream.write_all(b"\n")?; + } + return Ok(()); + } + FlushMode::Bytes => { + let mut new_size = cmd.len(); + if newline { + new_size += 1; + } + new_size as u16 + } + FlushMode::Commands => 1, + }; + + if self.current_size + new_size >= self.flush_size { + self.flush()?; + } + self.stream.write_all(cmd)?; if newline { self.stream.write_all(b"\n")?; } + self.current_size += new_size; - // Flush, make sure to clear the send buffer - // TODO: only flush each 100 pixels? - // TODO: make buffer size configurable? - if self.flush { - self.stream.flush()?; + if self.current_size == self.flush_size { + self.flush()?; } - // Everything seems to be ok Ok(()) }