diff --git a/Cargo.lock b/Cargo.lock index 80d66e5..7cda8be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,6 +221,7 @@ dependencies = [ "bufstream 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index 9b6f62a..782efcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ authors = ["Tim Visée "] bufstream = "0.1" clap = "2.29" image = "0.18" +num_cpus = "1.8" regex = "0.2" diff --git a/README.md b/README.md index 7bad421..a65948c 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,14 @@ Pixelflut an animated image: pixelpwnr 127.0.0.1:8080 -i *.png --fps 5 -c 4 -w 400 -h 400 -x 100 -y 100 ``` +Use the `--help` flag, or see the [help](#help) section for all available +options. + ## Installation For installation, Git and Rust cargo are required. Install the latest version of Rust with [rustup][rustup]. -Then, clone and install pixelpwnr with: +Then, clone and install `pixelpwnr` with: ```bash # Clone the project @@ -60,7 +63,7 @@ pixelpwnr --help cargo run --release -- --help ``` -Or just build it and invoke the binary directly: +Or just build it and invoke the binary directly (Linux/macOS): ```bash # Clone the project @@ -74,6 +77,28 @@ cargo build --release ./target/release/pixelpwnr --help ``` +## Performance & speed optimization +There are many things that affect how quickly pixels can be painted on a +pixelflut server. +Some of them are: +- Size of the image that is drawn. +- Amount of connections used to push pixels. +- Performance of the machine `pixelpwnr` is running on. +- Network interface performance of the client. +- Network interface performance of the server. +- Performance of the pixelflut server. + +Things that improve painting performance: +- Use a wired connection. +- Use a LAN connection, closely linked to the pixelflut server. The lower + latency the better, due to the connection being over TCP. +- Use as many threads (`-c` flag) as the server, your connection and your + machine allows. +- Paint a smaller image (`-w`, `-h` flags). +- Paint in an area on the screen, where the least other things are pained. +- Use multiple machines (servers) with multiple `pixelpwnr` instances to push + pixels to the screen. + ## Help ```text pixelpwnr --help @@ -93,9 +118,9 @@ OPTIONS: -i, --image ... Image paths -w, --width Draw width (def: screen width) -h, --height Draw height (def: screen height) - -x, --x Draw X offset (def: 0) - -y, --y Draw Y offset (def: 0) - -c, --count Number of concurrent threads (def: 4) + -x Draw X offset (def: 0) + -y Draw Y offset (def: 0) + -c, --count Number of concurrent threads (def: CPUs) -r, --fps Frames per second with multiple images (def: 1) ARGS: @@ -110,5 +135,5 @@ Check out the [LICENSE](LICENSE) file for more information. [34C3]: https://events.ccc.de/congress/2017/wiki/index.php/Main_Page [pixelflut]: https://cccgoe.de/wiki/Pixelflut [pixelflut-video]: https://vimeo.com/92827556/ -[rust]: https://rust-lang.org/ +[rust]: https://www.rust-lang.org/ [rustup]: https://rustup.rs/ diff --git a/TODO.md b/TODO.md index 030f8ae..7b4d5af 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,19 @@ # TODO -- Don't draw pixels outside screen. +- Resolve relative paths, or paths with a `~` correctly. - Add alpha support. -- Do not draw invisible (alpha) pixels. -- Read size from screen. - Instantly update images in painter threads, not just when the stopped drawing. -- Process all images at start. -- Combine many pixel messages to improve performance. - Create a small listening server, to benchmark throughput. + +# Further optimizations +- Process and slice all images before starting, don't process them each frame + again. +- Create a pixel map at start, instead of continuously getting pixels from the + image. +- Convert whole image blocks to a single large command string, to push in one + piece to the pixelflut server. Instead of pushing each pixel command + separately. +- Do not draw transparant (alpha) pixels. +- Do not draw pixels outside the screen size. +- Further control buffering in drawing pipes. +- Allow UDP mode (for pixelflut servers that support it). diff --git a/src/app.rs b/src/app.rs index 414f1ea..0b6ba5a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,8 +4,5 @@ pub const APP_VERSION: &'static str = "0.1"; pub const APP_AUTHOR: &'static str = "Tim Visee "; pub const APP_ABOUT: &'static str = "A quick pixelflut client, that pwns pixelflut panels."; -// The default thread count -pub const DEFAULT_THREAD_COUNT: usize = 4; - // The default frames per second rate pub const DEFAULT_IMAGE_FPS: u32 = 1; diff --git a/src/arg_handler.rs b/src/arg_handler.rs index 7a5fee9..5ad3780 100644 --- a/src/arg_handler.rs +++ b/src/arg_handler.rs @@ -1,4 +1,5 @@ extern crate clap; +extern crate num_cpus; use clap::{Arg, ArgMatches, App}; @@ -50,14 +51,12 @@ impl<'a: 'b, 'b> ArgHandler<'a> { .takes_value(true)) .arg(Arg::with_name("x") .short("x") - .long("x") .value_name("PIXELS") .help("Draw X offset (def: 0)") .display_order(4) .takes_value(true)) .arg(Arg::with_name("y") .short("y") - .long("y") .value_name("PIXELS") .help("Draw Y offset (def: 0)") .display_order(5) @@ -68,7 +67,7 @@ impl<'a: 'b, 'b> ArgHandler<'a> { .alias("thread") .alias("threads") .value_name("COUNT") - .help("Number of concurrent threads (def: 4)") + .help("Number of concurrent threads (def: CPUs)") .display_order(6) .takes_value(true)) .arg(Arg::with_name("fps") @@ -95,9 +94,10 @@ impl<'a: 'b, 'b> ArgHandler<'a> { /// Get the thread count. pub fn count(&self) -> usize { self.matches.value_of("count") - .unwrap_or(&format!("{}", DEFAULT_THREAD_COUNT)) - .parse::() - .expect("Invalid count specified") + .map(|count| count.parse::() + .expect("Invalid count specified") + ) + .unwrap_or(num_cpus::get()) } /// Get the image paths. @@ -112,17 +112,19 @@ impl<'a: 'b, 'b> ArgHandler<'a> { pub fn size(&self, def: Option<(u32, u32)>) -> (u32, u32) { ( self.matches.value_of("width") - .unwrap_or( - &format!("{}", def.expect("No screen width set or known").0) + .map(|width| width.parse::() + .expect("Invalid image width") ) - .parse::() - .expect("Invalid image width"), + .unwrap_or( + def.expect("No screen width set or known").0 + ), self.matches.value_of("height") - .unwrap_or( - &format!("{}", def.expect("No screen height set or known").1) + .map(|height| height.parse::() + .expect("Invalid image height") ) - .parse::() - .expect("Invalid image height"), + .unwrap_or( + def.expect("No screen height set or known").1 + ), ) } @@ -130,21 +132,24 @@ impl<'a: 'b, 'b> ArgHandler<'a> { pub fn offset(&self) -> (u32, u32) { ( self.matches.value_of("x") - .unwrap_or("0") - .parse::() - .expect("Invalid X offset"), + .map(|x| x.parse::() + .expect("Invalid X offset") + ) + .unwrap_or(0), self.matches.value_of("y") - .unwrap_or("0") - .parse::() - .expect("Invalid Y offset"), + .map(|y| y.parse::() + .expect("Invalid Y offset") + ) + .unwrap_or(0), ) } /// Get the FPS. pub fn fps(&self) -> u32 { self.matches.value_of("fps") - .unwrap_or(&format!("{}", DEFAULT_IMAGE_FPS)) - .parse::() - .expect("Invalid frames per second rate") + .map(|fps| fps.parse::() + .expect("Invalid frames per second rate") + ) + .unwrap_or(DEFAULT_IMAGE_FPS) } } diff --git a/src/painter/mod.rs b/src/painter/mod.rs index ad0c615..72c957a 100644 --- a/src/painter/mod.rs +++ b/src/painter/mod.rs @@ -1,3 +1,3 @@ -// Re-export modules +// Reexport modules pub mod painter; pub mod handle; diff --git a/src/pix/mod.rs b/src/pix/mod.rs index 14a715f..e0c13c7 100644 --- a/src/pix/mod.rs +++ b/src/pix/mod.rs @@ -1,3 +1,3 @@ -// Re-export modules +// Reexport modules pub mod canvas; pub mod client;