From 9dc681086d858eb20d72a8a5c24c9c0a0c8dfbac Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Fri, 18 Oct 2024 01:56:23 +0200 Subject: [PATCH] refactor: split into more logical files --- Cargo.lock | 380 ++++++++++++++++++++++++- Cargo.toml | 6 + src/config.rs | 11 + src/flutclient.rs | 151 ++++++++++ src/grid.rs | 35 +-- src/lib.rs | 77 +++++ src/main.rs | 243 +--------------- src/protocols.rs | 29 ++ src/{ => protocols}/binary_protocol.rs | 4 +- src/{ => protocols}/text_protocol.rs | 6 +- src/utils.rs | 62 ++++ 11 files changed, 728 insertions(+), 276 deletions(-) create mode 100644 src/config.rs create mode 100644 src/flutclient.rs create mode 100644 src/lib.rs create mode 100644 src/protocols.rs rename src/{ => protocols}/binary_protocol.rs (98%) rename src/{ => protocols}/text_protocol.rs (98%) create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index efc0d44..58e5d8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "aligned-vec" version = "0.5.0" @@ -44,6 +53,18 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + [[package]] name = "anyhow" version = "1.0.89" @@ -216,6 +237,12 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.1.29" @@ -257,6 +284,58 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + [[package]] name = "color_quant" version = "1.1.0" @@ -278,6 +357,42 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -321,6 +436,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "exr" version = "1.72.0" @@ -337,6 +462,12 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + [[package]] name = "fdeflate" version = "0.3.5" @@ -373,7 +504,11 @@ dependencies = [ "atoi_radix10", "bytes", "chrono", + "criterion", "image", + "rand", + "tempfile", + "test-case", "tokio", "tokio-test", ] @@ -439,6 +574,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -522,6 +663,26 @@ dependencies = [ "syn", ] +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -531,6 +692,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "jobserver" version = "0.1.32" @@ -578,6 +745,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.12" @@ -649,10 +822,10 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "wasi", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -742,6 +915,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + [[package]] name = "parking_lot" version = "0.12.3" @@ -783,6 +962,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "png" version = "0.17.14" @@ -901,7 +1108,7 @@ dependencies = [ "built", "cfg-if", "interpolate_name", - "itertools", + "itertools 0.12.1", "libc", "libfuzzer-sys", "log", @@ -965,6 +1172,35 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rgb" version = "0.8.50" @@ -980,6 +1216,34 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1006,6 +1270,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -1058,7 +1334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1100,6 +1376,52 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "test-case-core", +] + [[package]] name = "thiserror" version = "1.0.64" @@ -1131,6 +1453,16 @@ dependencies = [ "weezl", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tokio" version = "1.40.0" @@ -1146,7 +1478,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1241,6 +1573,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1302,12 +1644,31 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "weezl" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -1326,6 +1687,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 4afa4a8..ea7aa2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,12 @@ image = "0.25.2" tokio = { version = "1.38", features = ["full"] } tokio-test = "*" +[dev-dependencies] +tempfile = "*" +test-case = "*" +criterion = "*" +rand = "*" + [profile.dev] opt-level = 1 diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..06185e4 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,11 @@ +pub const GRID_LENGTH: usize = 1; +pub const HOST: &str = "0.0.0.0:7791"; + +pub const HELP_TEXT: &[u8] = b"Flurry is a pixelflut implementation, this means you can use commands to get and set pixels in the canvas +SIZE returns the size of the canvas +PX {x} {y} returns the color of the pixel at {x}, {y} +If you include a color in hex format you set a pixel instead +PX {x} {y} {RGB} sets the color of the pixel at {x}, {y} to the rgb value +PX {x} {y} {RGBA} blends the pixel at {x}, {y} with the rgb value weighted by the a +PX {x} {y} {W} sets the color of the pixel at {x}, {y} to the grayscale value +"; diff --git a/src/flutclient.rs b/src/flutclient.rs new file mode 100644 index 0000000..202cc20 --- /dev/null +++ b/src/flutclient.rs @@ -0,0 +1,151 @@ +use std::{ + io::{self, Error, ErrorKind}, + sync::Arc, +}; + +use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}; + +use crate::{ + get_pixel, + grid::{self, Flut}, + increment_counter, + protocols::{BinaryParser, IOProtocol, Parser, Responder, TextParser}, + set_pixel_rgba, Canvas, Color, Command, Coordinate, Protocol, Response, +}; + +macro_rules! build_parser_type_enum { + ($($name:ident: $t:ty,)*) => { + + #[derive(Clone)] + enum ParserTypes { + $($name($t),)* + } + + macro_rules! match_parser { + ($pident:ident: $parser:expr => $f:expr) => ( + match &mut $parser { + $( + ParserTypes::$name($pident) => $f, + )* + } + ) + } + }; +} + +build_parser_type_enum! { + TextParser: TextParser, + BinaryParser: BinaryParser, +} + +pub struct FlutClient +where + R: AsyncReadExt + std::marker::Unpin, + W: AsyncWriteExt + std::marker::Unpin, +{ + reader: BufReader, + writer: BufWriter, + grids: Arc<[Flut]>, + parser: ParserTypes, + counter: u64, +} + +impl FlutClient +where + R: AsyncReadExt + std::marker::Unpin, + W: AsyncWriteExt + std::marker::Unpin, +{ + async fn help_command(&mut self) -> io::Result<()> { + println!("HELP wanted"); + match_parser!(parser: self.parser => parser.unparse(Response::Help, &mut self.writer).await?); + + self.writer.flush().await?; + Ok(()) + } + + async fn size_command(&mut self, canvas: Canvas) -> io::Result<()> { + let (x, y) = self.grids[canvas as usize].get_size(); + match_parser!(parser: self.parser => parser.unparse( + Response::Size(Coordinate::try_from(x).unwrap(), Coordinate::try_from(y).unwrap()), &mut self.writer).await?); + + self.writer.flush().await?; + Ok(()) + } + + async fn get_pixel_command( + &mut self, + canvas: Canvas, + x: Coordinate, + y: Coordinate, + ) -> io::Result<()> { + let color = match get_pixel(&self.grids, canvas, x, y) { + None => return Err(Error::from(ErrorKind::InvalidInput)), + Some(color) => color.to_be_bytes(), + }; + match_parser!(parser: self.parser => parser.unparse( + Response::GetPixel(x,y,[color[0], color[1], color[2]]), &mut self.writer).await? + ); + Ok(()) + } + + fn set_pixel_command(&mut self, canvas: Canvas, x: Coordinate, y: Coordinate, color: &Color) { + let c: u32 = match color { + Color::RGB24(red, green, blue) => u32::from_be_bytes([*red, *green, *blue, 0xff]), + Color::RGBA32(red, green, blue, alpha) => { + u32::from_be_bytes([*red, *green, *blue, *alpha]) + } + Color::W8(white) => u32::from_be_bytes([*white, *white, *white, 0xff]), + }; + set_pixel_rgba(self.grids.as_ref(), canvas, x, y, c); + self.counter += 1; + } + + fn change_canvas_command(&mut self, canvas: Canvas) -> io::Result<()> { + match_parser!(parser: self.parser => parser.change_canvas(canvas)) + } + + fn change_protocol(&mut self, protocol: &Protocol) { + match protocol { + Protocol::Text => self.parser = ParserTypes::TextParser(TextParser::new(0)), + Protocol::Binary => self.parser = ParserTypes::BinaryParser(BinaryParser::new()), + } + } + + pub fn new(reader: R, writer: W, grids: Arc<[grid::Flut]>) -> Self { + FlutClient { + reader: BufReader::new(reader), + writer: BufWriter::new(writer), + grids, + parser: ParserTypes::TextParser(TextParser::new(0)), + counter: 0, + } + } + + pub async fn process_socket(&mut self) -> io::Result<()> { + loop { + match_parser!(parser: &self.parser.clone() => 'outer: loop { + for _ in 0..1000 { + let parsed = parser.parse(&mut self.reader).await; + match parsed { + Ok(Command::Help) => self.help_command().await?, + Ok(Command::Size(canvas)) => self.size_command(canvas).await?, + Ok(Command::GetPixel(canvas, x, y)) => self.get_pixel_command(canvas, x, y).await?, + Ok(Command::SetPixel(canvas, x, y, color)) => self.set_pixel_command(canvas, x, y, &color), + Ok(Command::ChangeCanvas(canvas)) => { + self.change_canvas_command(canvas)?; + break 'outer; + } + Ok(Command::ChangeProtocol(protocol)) => { + self.change_protocol(&protocol); + break 'outer; + } + Err(err) if err.kind() == ErrorKind::UnexpectedEof => return Ok(()), + Err(e) => return Err(e), + } + } + increment_counter(self.counter); + self.counter = 0; + }); + } + } +} diff --git a/src/grid.rs b/src/grid.rs index 90fd4bf..da2019d 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -1,6 +1,6 @@ use std::cell::SyncUnsafeCell; -use image::{DynamicImage, GenericImage, GenericImageView, ImageBuffer, Pixel, Rgb, Rgba}; +use image::{GenericImageView, Rgb}; use crate::Coordinate; @@ -74,7 +74,7 @@ impl GenericImageView for Flut { } fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel { - let pixel = self.get_unchecked(x as u16, y as u16); + let pixel = self.get_unchecked(x as Coordinate, y as Coordinate); let [r, g, b, _a] = pixel.to_be_bytes(); Rgb::from([r, g, b]) } @@ -83,9 +83,8 @@ impl GenericImageView for Flut { #[cfg(test)] #[allow(clippy::needless_return)] mod tests { - use super::*; - use crate::grid::Flut; - use test::Bencher; + use super::Flut; + use super::Grid; #[tokio::test] async fn test_grid_init_values() { @@ -132,30 +131,4 @@ mod tests { assert_eq!(grid.get(3, 1), None); assert_eq!(grid.get(1, 2), Some(&0)); } - - #[bench] - fn bench_init(b: &mut Bencher) { - b.iter(|| Flut::init(800, 600, 0)); - } - - #[bench] - fn bench_set(b: &mut Bencher) { - let grid = Flut::init(800, 600, 0); - b.iter(|| { - let x = test::black_box(293); - let y = test::black_box(222); - let color = test::black_box(0x29_39_23); - grid.set(x, y, color); - }); - } - - #[bench] - fn bench_get(b: &mut Bencher) { - let grid = Flut::init(800, 600, 0); - b.iter(|| { - let x = test::black_box(293); - let y = test::black_box(222); - grid.get(x, y) - }); - } } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7194a00 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,77 @@ +#![feature(test)] +#![feature(sync_unsafe_cell)] +#![feature(if_let_guard)] + +use std::sync::atomic::AtomicU64; + +use grid::Grid; + +pub mod config; +pub mod flutclient; +pub mod grid; +pub mod protocols; +pub mod utils; + +pub type Canvas = u8; +pub type Coordinate = u16; + +pub static COUNTER: AtomicU64 = AtomicU64::new(0); + +fn set_pixel_rgba( + grids: &[grid::Flut], + canvas: Canvas, + x: Coordinate, + y: Coordinate, + rgb: u32, +) { + if let Some(grid) = grids.get(canvas as usize) { + grid.set(x, y, rgb); + } +} + +fn get_pixel( + grids: &[grid::Flut], + canvas: Canvas, + x: Coordinate, + y: Coordinate, +) -> Option<&u32> { + match grids.get(canvas as usize) { + Some(grid) => grid.get(x, y), + None => None, + } +} + +#[inline] +fn increment_counter(amount: u64) { + COUNTER.fetch_add(amount, std::sync::atomic::Ordering::Relaxed); +} + +#[derive(Debug, PartialEq)] +pub enum Color { + RGB24(u8, u8, u8), + RGBA32(u8, u8, u8, u8), + W8(u8), +} + +#[derive(Debug, PartialEq)] +pub enum Protocol { + Text, + Binary, +} + +#[derive(Debug, PartialEq)] +pub enum Command { + Help, + Size(Canvas), + GetPixel(Canvas, Coordinate, Coordinate), + SetPixel(Canvas, Coordinate, Coordinate, Color), + ChangeCanvas(Canvas), + ChangeProtocol(Protocol), +} + +#[derive(Debug, PartialEq)] +pub enum Response { + Help, + Size(Coordinate, Coordinate), + GetPixel(Coordinate, Coordinate, [u8; 3]), +} diff --git a/src/main.rs b/src/main.rs index f876661..9c8ba37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,6 @@ #![feature(sync_unsafe_cell)] #![feature(if_let_guard)] -mod binary_protocol; -mod grid; -mod text_protocol; - use std::{ fs::{create_dir_all, File}, io::{self, Error, ErrorKind}, @@ -14,11 +10,14 @@ use std::{ time::Duration, }; -use binary_protocol::BinaryParser; use chrono::Local; -use grid::{Flut, Grid}; +use flurry::{ + config::{GRID_LENGTH, HOST}, + flutclient::FlutClient, + grid::{self, Flut}, + COUNTER, +}; use image::{codecs::jpeg::JpegEncoder, GenericImageView, SubImage}; -use text_protocol::TextParser; use tokio::{ io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, net::TcpListener, @@ -26,99 +25,6 @@ use tokio::{ }; extern crate test; -const GRID_LENGTH: usize = 1; -const HOST: &str = "0.0.0.0:7791"; - -const HELP_TEXT: &[u8] = b"Flurry is a pixelflut implementation, this means you can use commands to get and set pixels in the canvas -SIZE returns the size of the canvas -PX {x} {y} returns the color of the pixel at {x}, {y} -If you include a color in hex format you set a pixel instead -PX {x} {y} {RGB} sets the color of the pixel at {x}, {y} to the rgb value -PX {x} {y} {RGBA} blends the pixel at {x}, {y} with the rgb value weighted by the a -PX {x} {y} {W} sets the color of the pixel at {x}, {y} to the grayscale value -"; - -static COUNTER: AtomicU64 = AtomicU64::new(0); - -type Canvas = u8; -type Coordinate = u16; - -fn set_pixel_rgba( - grids: &[grid::Flut], - canvas: Canvas, - x: Coordinate, - y: Coordinate, - rgb: u32, -) { - if let Some(grid) = grids.get(canvas as usize) { - grid.set(x, y, rgb); - } -} - -fn get_pixel( - grids: &[grid::Flut], - canvas: Canvas, - x: Coordinate, - y: Coordinate, -) -> Option<&u32> { - match grids.get(canvas as usize) { - Some(grid) => grid.get(x, y), - None => None, - } -} - -#[inline] -fn increment_counter(amount: u64) { - COUNTER.fetch_add(amount, std::sync::atomic::Ordering::Relaxed); -} - -#[derive(Debug, PartialEq)] -enum Color { - RGB24(u8, u8, u8), - RGBA32(u8, u8, u8, u8), - W8(u8), -} - -#[derive(Debug, PartialEq)] -enum Protocol { - Text, - Binary, -} - -#[derive(Debug, PartialEq)] -enum Command { - Help, - Size(Canvas), - GetPixel(Canvas, Coordinate, Coordinate), - SetPixel(Canvas, Coordinate, Coordinate, Color), - ChangeCanvas(Canvas), - ChangeProtocol(Protocol), -} - -#[derive(Debug, PartialEq)] -enum Response { - Help, - Size(Coordinate, Coordinate), - GetPixel(Coordinate, Coordinate, [u8; 3]), -} - -trait Parser -where - R: std::marker::Unpin + tokio::io::AsyncBufRead, -{ - async fn parse(&self, reader: &mut R) -> io::Result; -} - -trait IOProtocol { - fn change_canvas(&mut self, canvas: Canvas) -> io::Result<()>; -} - -trait Responder -where - W: AsyncWriteExt + std::marker::Unpin, -{ - async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()>; -} async fn listen_handle() -> io::Result<()> { let mut interval = tokio::time::interval(Duration::from_millis(1000)); @@ -129,143 +35,6 @@ async fn listen_handle() -> io::Result<()> { } } -macro_rules! build_parser_type_enum { - ($($name:ident: $t:ty,)*) => { - - #[derive(Clone)] - enum ParserTypes { - $($name($t),)* - } - - macro_rules! match_parser { - ($pident:ident: $parser:expr => $f:expr) => ( - match &mut $parser { - $( - ParserTypes::$name($pident) => $f, - )* - } - ) - } - }; -} - -build_parser_type_enum! { - TextParser: TextParser, - BinaryParser: BinaryParser, -} - -struct FlutClient -where - R: AsyncReadExt + std::marker::Unpin, - W: AsyncWriteExt + std::marker::Unpin, -{ - reader: BufReader, - writer: BufWriter, - grids: Arc<[Flut]>, - parser: ParserTypes, - counter: u64, -} - -impl FlutClient -where - R: AsyncReadExt + std::marker::Unpin, - W: AsyncWriteExt + std::marker::Unpin, -{ - async fn help_command(&mut self) -> io::Result<()> { - println!("HELP wanted"); - match_parser!(parser: self.parser => parser.unparse(Response::Help, &mut self.writer).await?); - - self.writer.flush().await?; - Ok(()) - } - - async fn size_command(&mut self, canvas: Canvas) -> io::Result<()> { - let (x, y) = self.grids[canvas as usize].get_size(); - match_parser!(parser: self.parser => parser.unparse( - Response::Size(Coordinate::try_from(x).unwrap(), Coordinate::try_from(y).unwrap()), &mut self.writer).await?); - - self.writer.flush().await?; - Ok(()) - } - - async fn get_pixel_command( - &mut self, - canvas: Canvas, - x: Coordinate, - y: Coordinate, - ) -> io::Result<()> { - let color = match get_pixel(&self.grids, canvas, x, y) { - None => return Err(Error::from(ErrorKind::InvalidInput)), - Some(color) => color.to_be_bytes(), - }; - match_parser!(parser: self.parser => parser.unparse( - Response::GetPixel(x,y,[color[0], color[1], color[2]]), &mut self.writer).await? - ); - Ok(()) - } - - fn set_pixel_command(&mut self, canvas: Canvas, x: Coordinate, y: Coordinate, color: &Color) { - let c: u32 = match color { - Color::RGB24(red, green, blue) => u32::from_be_bytes([*red, *green, *blue, 0xff]), - Color::RGBA32(red, green, blue, alpha) => { - u32::from_be_bytes([*red, *green, *blue, *alpha]) - } - Color::W8(white) => u32::from_be_bytes([*white, *white, *white, 0xff]), - }; - set_pixel_rgba(self.grids.as_ref(), canvas, x, y, c); - self.counter += 1; - } - - fn change_canvas_command(&mut self, canvas: Canvas) -> io::Result<()> { - match_parser!(parser: self.parser => parser.change_canvas(canvas)) - } - - fn change_protocol(&mut self, protocol: &Protocol) { - match protocol { - Protocol::Text => self.parser = ParserTypes::TextParser(TextParser::new(0)), - Protocol::Binary => self.parser = ParserTypes::BinaryParser(BinaryParser::new()), - } - } - - pub fn new(reader: R, writer: W, grids: Arc<[grid::Flut]>) -> FlutClient { - FlutClient { - reader: BufReader::new(reader), - writer: BufWriter::new(writer), - grids, - parser: ParserTypes::TextParser(TextParser::new(0)), - counter: 0, - } - } - - pub async fn process_socket(&mut self) -> io::Result<()> { - loop { - match_parser!(parser: &self.parser.clone() => 'outer: loop { - for _ in 0..1000 { - let parsed = parser.parse(&mut self.reader).await; - match parsed { - Ok(Command::Help) => self.help_command().await?, - Ok(Command::Size(canvas)) => self.size_command(canvas).await?, - Ok(Command::GetPixel(canvas, x, y)) => self.get_pixel_command(canvas, x, y).await?, - Ok(Command::SetPixel(canvas, x, y, color)) => self.set_pixel_command(canvas, x, y, &color), - Ok(Command::ChangeCanvas(canvas)) => { - self.change_canvas_command(canvas)?; - break 'outer; - } - Ok(Command::ChangeProtocol(protocol)) => { - self.change_protocol(&protocol); - break 'outer; - } - Err(err) if err.kind() == ErrorKind::UnexpectedEof => return Ok(()), - Err(e) => return Err(e), - } - } - increment_counter(self.counter); - self.counter = 0; - }); - } - } -} - async fn save_image_frames(grids: Arc<[grid::Flut]>) -> io::Result<()> { let base_dir = Path::new("./recordings"); let mut timer = interval(Duration::from_secs(5)); diff --git a/src/protocols.rs b/src/protocols.rs new file mode 100644 index 0000000..dabdb38 --- /dev/null +++ b/src/protocols.rs @@ -0,0 +1,29 @@ +mod binary_protocol; +mod text_protocol; + +use std::io; + +pub use binary_protocol::BinaryParser; +pub use text_protocol::TextParser; +use tokio::io::AsyncWriteExt; + +use crate::{Canvas, Command, Response}; + +pub trait Parser +where + R: std::marker::Unpin + tokio::io::AsyncBufRead, +{ + async fn parse(&self, reader: &mut R) -> io::Result; +} + +pub trait IOProtocol { + fn change_canvas(&mut self, canvas: Canvas) -> io::Result<()>; +} + +pub trait Responder +where + W: AsyncWriteExt + std::marker::Unpin, +{ + async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()>; +} + diff --git a/src/binary_protocol.rs b/src/protocols/binary_protocol.rs similarity index 98% rename from src/binary_protocol.rs rename to src/protocols/binary_protocol.rs index b7e8f0d..2c7ea5c 100644 --- a/src/binary_protocol.rs +++ b/src/protocols/binary_protocol.rs @@ -2,7 +2,9 @@ use std::io::{self, Error, ErrorKind}; use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}; -use crate::{Canvas, Color, Command, IOProtocol, Parser, Responder, Response}; +use crate::{Canvas, Color, Command, Response}; + +use super::{IOProtocol, Parser, Responder}; const SIZE_BIN: u8 = 115; const HELP_BIN: u8 = 104; diff --git a/src/text_protocol.rs b/src/protocols/text_protocol.rs similarity index 98% rename from src/text_protocol.rs rename to src/protocols/text_protocol.rs index 57fefae..6b8f0a1 100644 --- a/src/text_protocol.rs +++ b/src/protocols/text_protocol.rs @@ -4,10 +4,12 @@ use atoi_radix10::parse_from_str; use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncWriteExt}; use crate::{ - Canvas, Color, Command, Coordinate, IOProtocol, Parser, Protocol, Responder, Response, - GRID_LENGTH, HELP_TEXT, + config::{GRID_LENGTH, HELP_TEXT}, + Canvas, Color, Command, Coordinate, Protocol, Response, }; +use super::{IOProtocol, Parser, Responder}; + #[derive(Clone)] pub struct TextParser { canvas: Canvas, diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..d882b7f --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,62 @@ +use std::task::Poll; + +use tokio::io::{AsyncRead, AsyncWrite}; + +pub struct RepeatSome { + bytes: &'static [u8], + len: usize, +} + +impl RepeatSome { + pub fn new(bytes: &'static [u8]) -> Self { + RepeatSome { + bytes, + len: bytes.len(), + } + } +} + +impl AsyncRead for RepeatSome { + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + while buf.remaining() > self.len { + buf.put_slice(self.bytes) + } + Poll::Ready(Ok(())) + } +} + +pub struct Drain {} + +impl Drain { + pub fn new() -> Self { + Drain {} + } +} + +impl AsyncWrite for Drain { + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> Poll> { + Poll::Ready(Ok(buf.len())) + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } +}