From 62d6dd76d36ed29cfbe159a6e271e38504fee443 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Tue, 10 Dec 2024 22:38:14 +0100 Subject: [PATCH 01/11] add configuration for the stdout pixel-changed interval --- src/config.rs | 1 + src/main.rs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 3318dea..230f9f6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,7 @@ pub const WEB_HOST: &str = "127.0.0.1:3000"; pub const IMAGE_SAVE_INTERVAL: Duration = Duration::from_secs(5); pub const JPEG_UPDATE_INTERVAL: Duration = Duration::from_millis(17); pub const WEB_UPDATE_INTERVAL: Duration = Duration::from_millis(50); +pub const STDOUT_STATISTICS_INTERVAL: Duration = Duration::from_millis(5000); 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 diff --git a/src/main.rs b/src/main.rs index 3581e50..f07ee47 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,9 @@ use std::{ use chrono::Local; use flurry::{ - config::{GRID_LENGTH, HOST, IMAGE_SAVE_INTERVAL, JPEG_UPDATE_INTERVAL}, + config::{ + GRID_LENGTH, HOST, IMAGE_SAVE_INTERVAL, JPEG_UPDATE_INTERVAL, STDOUT_STATISTICS_INTERVAL, + }, flutclient::{FlutClient, ParserTypes}, grid::{self, Flut}, webapi::WebApiContext, @@ -21,7 +23,7 @@ use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _ /// This function logs the current amount of changed pixels to stdout every second async fn pixel_change_stdout_log() -> AsyncResult { - let mut interval = tokio::time::interval(Duration::from_millis(1000)); + let mut interval = tokio::time::interval(STDOUT_STATISTICS_INTERVAL); loop { interval.tick().await; let cnt = COUNTER.load(std::sync::atomic::Ordering::Relaxed); From b7b05d2865c771d55e96d842295f17f97e1b368e Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Tue, 10 Dec 2024 22:39:59 +0100 Subject: [PATCH 02/11] add tests for caps versions in text protocol --- src/protocols/text_protocol.rs | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/protocols/text_protocol.rs b/src/protocols/text_protocol.rs index 1c711a2..160ec75 100644 --- a/src/protocols/text_protocol.rs +++ b/src/protocols/text_protocol.rs @@ -209,6 +209,20 @@ mod tests { ); } + #[tokio::test] + async fn test_px_set_w_parse_caps() { + let parser = TextParser::default(); + let reader = tokio_test::io::Builder::new() + .read(b"PX 28283 29991 AB\n") + .build(); + let mut bufreader = BufReader::new(reader); + let thingy = parser.parse(&mut bufreader).await; + assert_eq!( + thingy.unwrap(), + Command::SetPixel(0, 28283, 29991, Color::W8(0xAB)) + ); + } + #[tokio::test] async fn test_px_set_rgb_parse() { let parser = TextParser::default(); @@ -223,6 +237,20 @@ mod tests { ); } + #[tokio::test] + async fn test_px_set_rgb_parse_caps() { + let parser = TextParser::default(); + let reader = tokio_test::io::Builder::new() + .read(b"PX 28283 29991 8800FA\n") + .build(); + let mut bufreader = BufReader::new(reader); + let thingy = parser.parse(&mut bufreader).await; + assert_eq!( + thingy.unwrap(), + Command::SetPixel(0, 28283, 29991, Color::RGB24(0x88, 0x00, 0xfa)) + ); + } + #[tokio::test] async fn test_px_set_rgba_parse() { let parser = TextParser::default(); @@ -237,6 +265,20 @@ mod tests { ); } + #[tokio::test] + async fn test_px_set_rgba_parse_caps() { + let parser = TextParser::default(); + let reader = tokio_test::io::Builder::new() + .read(b"PX 28283 29991 AB0c3F88\n") + .build(); + let mut bufreader = BufReader::new(reader); + let thingy = parser.parse(&mut bufreader).await; + assert_eq!( + thingy.unwrap(), + Command::SetPixel(0, 28283, 29991, Color::RGBA32(0xab, 0x0c, 0x3f, 0x88)) + ); + } + #[tokio::test] async fn test_px_get_parse() { let parser = TextParser::default(); From c5fb0f5437250b4f569d4345f19af62c5b1b751a Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Tue, 10 Dec 2024 22:40:53 +0100 Subject: [PATCH 03/11] move the locally scoped function to an impl to improve readability --- src/stream.rs | 78 +++++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index bb3f8d3..45b6686 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -28,6 +28,42 @@ impl Multipart { } } +impl Multipart { + fn write_multipart_frame( + obj: T, + boundary: Vec, + headers: HeaderMap, + first: bool, + ) -> Result, axum::Error> + where + T: IntoIterator, + { + let mut frame_vec = Vec::new(); + if first { + frame_vec.extend_from_slice(b"--"); + } else { + frame_vec.extend_from_slice(b"\r\n--"); + } + frame_vec.extend(boundary); + frame_vec.extend_from_slice(b"\r\n"); + for (header_name, header_value) in headers { + match header_name { + Some(header) => { + frame_vec.extend_from_slice(header.as_str().as_bytes()); + frame_vec.extend_from_slice(b": "); + frame_vec.extend_from_slice(header_value.as_bytes()); + frame_vec.extend_from_slice(b"\r\n"); + } + None => todo!(), + } + } + frame_vec.extend_from_slice(b"\r\n"); + frame_vec.extend(obj); + + Ok(frame_vec) + } +} + impl StreamingFormat for Multipart where T: Send + Sync + IntoIterator + 'static, @@ -37,40 +73,6 @@ where stream: futures::stream::BoxStream<'b, Result>, _options: &'a axum_streams::StreamBodyAsOptions, ) -> futures::stream::BoxStream<'b, Result> { - fn write_multipart_frame( - obj: T, - boundary: Vec, - headers: HeaderMap, - first: bool, - ) -> Result, axum::Error> - where - T: IntoIterator, - { - let mut frame_vec = Vec::new(); - if first { - frame_vec.extend_from_slice(b"--"); - } else { - frame_vec.extend_from_slice(b"\r\n--"); - } - frame_vec.extend(boundary); - frame_vec.extend_from_slice(b"\r\n"); - for (header_name, header_value) in headers { - match header_name { - Some(header) => { - frame_vec.extend_from_slice(header.as_str().as_bytes()); - frame_vec.extend_from_slice(b": "); - frame_vec.extend_from_slice(header_value.as_bytes()); - frame_vec.extend_from_slice(b"\r\n"); - } - None => todo!(), - } - } - frame_vec.extend_from_slice(b"\r\n"); - frame_vec.extend(obj); - - Ok(frame_vec) - } - let boundary = self.boundary.clone(); let headers = self.headers.clone(); let first = self.first; @@ -79,8 +81,12 @@ where stream.map(move |obj_res| match obj_res { Err(e) => Err(e), Ok(obj) => { - let picture_framed = - write_multipart_frame(obj, boundary.clone(), headers.clone(), first); + let picture_framed = Multipart::write_multipart_frame( + obj, + boundary.clone(), + headers.clone(), + first, + ); picture_framed.map(axum::body::Bytes::from) } From b8c01befde09bc918504c858662f5e3d47751840 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 00:30:05 +0100 Subject: [PATCH 04/11] fix allow unreachable code directive --- src/flutclient.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flutclient.rs b/src/flutclient.rs index ddd9f2f..fcda60c 100644 --- a/src/flutclient.rs +++ b/src/flutclient.rs @@ -26,10 +26,10 @@ macro_rules! build_parser_type_enum { impl std::default::Default for ParserTypes { // add code here + #[allow(unreachable_code)] fn default() -> Self { $( #[cfg(feature = $feat)] - #[allow(unreachable_code)] return ParserTypes::$name(<$t>::default()); )* } From 232fe6b6e2b1c4c236a0914140e2ac7508374e3c Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 12:47:40 +0100 Subject: [PATCH 05/11] add response for protocol status --- src/lib.rs | 7 +++++++ src/protocols/binary_protocol.rs | 17 +++++++++++++++++ src/protocols/text_protocol.rs | 17 +++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4b35c6f..ec943c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,12 @@ fn increment_counter(amount: u64) { COUNTER.fetch_add(amount, std::sync::atomic::Ordering::Relaxed); } +#[derive(Debug, PartialEq)] +pub enum ProtocolStatus { + Enabled(&'static str), + Disabled(&'static str), +} + #[derive(Debug, PartialEq)] pub enum Protocol { Text, @@ -72,6 +78,7 @@ pub enum Command { #[derive(Debug, PartialEq)] pub enum Response { Help, + Protocols(Vec), Size(Coordinate, Coordinate), GetPixel(Coordinate, Coordinate, [u8; 3]), } diff --git a/src/protocols/binary_protocol.rs b/src/protocols/binary_protocol.rs index e505112..b7c0996 100644 --- a/src/protocols/binary_protocol.rs +++ b/src/protocols/binary_protocol.rs @@ -106,6 +106,23 @@ To set a pixel using RGB, use ({SET_PX_RGB_BIN:02X}) (u8 canvas) (x as u16_le) ( ); writer.write_all(help_text.as_bytes()).await } + Response::Protocols(protos) => { + for protocol in protos { + match protocol { + crate::ProtocolStatus::Enabled(proto) => { + writer + .write_all(format!("Enabled: {}\n", proto).as_bytes()) + .await?; + } + crate::ProtocolStatus::Disabled(proto) => { + writer + .write_all(format!("Disabled: {}\n", proto).as_bytes()) + .await?; + } + } + } + Ok(()) + } Response::Size(x, y) => { writer.write_u16(x).await?; writer.write_u16(y).await diff --git a/src/protocols/text_protocol.rs b/src/protocols/text_protocol.rs index 1c711a2..f5e48bf 100644 --- a/src/protocols/text_protocol.rs +++ b/src/protocols/text_protocol.rs @@ -146,6 +146,23 @@ impl Responder for TextParser { async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()> { match response { Response::Help => writer.write_all(HELP_TEXT).await, + Response::Protocols(protos) => { + for protocol in protos { + match protocol { + crate::ProtocolStatus::Enabled(proto) => { + writer + .write_all(format!("Enabled: {}\n", proto).as_bytes()) + .await?; + } + crate::ProtocolStatus::Disabled(proto) => { + writer + .write_all(format!("Disabled: {}\n", proto).as_bytes()) + .await?; + } + } + } + Ok(()) + } Response::Size(x, y) => writer.write_all(format!("SIZE {x} {y}\n").as_bytes()).await, Response::GetPixel(x, y, color) => { writer From b52af7d44fdf815af06d2a10883f4b1a128a62dd Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 12:48:11 +0100 Subject: [PATCH 06/11] add command for protocols --- src/flutclient.rs | 22 +++++++++++++++++++++- src/lib.rs | 1 + 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/flutclient.rs b/src/flutclient.rs index ddd9f2f..fcbb0f2 100644 --- a/src/flutclient.rs +++ b/src/flutclient.rs @@ -10,7 +10,7 @@ use crate::{ grid::{self, Flut}, increment_counter, protocols::{BinaryParser, IOProtocol, Parser, Responder, TextParser}, - set_pixel_rgba, Canvas, Color, Command, Coordinate, Protocol, Response, + set_pixel_rgba, Canvas, Color, Command, Coordinate, Protocol, ProtocolStatus, Response, }; macro_rules! build_parser_type_enum { @@ -36,6 +36,17 @@ macro_rules! build_parser_type_enum { } impl ParserTypes { + pub fn get_status() -> Vec { + let mut protos = Vec::new(); + $( + #[cfg(feature = $feat)] + protos.push(ProtocolStatus::Enabled($feat)); + #[cfg(not(feature = $feat))] + protos.push(ProtocolStatus::Disabled($feat)); + )* + protos + } + pub fn announce() { $( #[cfg(feature = $feat)] @@ -88,6 +99,14 @@ where Ok(()) } + async fn protocols_command(&mut self) -> io::Result<()> { + match_parser! { + parser: self.parser => parser.unparse(Response::Protocols(ParserTypes::get_status()), &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( @@ -166,6 +185,7 @@ where match parsed { Ok(Command::Help) => self.help_command().await?, Ok(Command::Size(canvas)) => self.size_command(canvas).await?, + Ok(Command::Protocols) => self.protocols_command().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)) => { diff --git a/src/lib.rs b/src/lib.rs index ec943c7..ca49c1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ pub enum Protocol { #[derive(Debug, PartialEq)] pub enum Command { Help, + Protocols, Size(Canvas), GetPixel(Canvas, Coordinate, Coordinate), SetPixel(Canvas, Coordinate, Coordinate, Color), From 823f4a306a4dcc93133baf19af564154f4c16ec4 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 12:49:02 +0100 Subject: [PATCH 07/11] allow calling `Protocols` from BinaryParser --- src/protocols/binary_protocol.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/protocols/binary_protocol.rs b/src/protocols/binary_protocol.rs index b7c0996..5bb0a13 100644 --- a/src/protocols/binary_protocol.rs +++ b/src/protocols/binary_protocol.rs @@ -7,6 +7,7 @@ use crate::{Canvas, Color, Command, Response}; use super::{IOProtocol, Parser, Responder}; const SIZE_BIN: u8 = 115; +const PROTOCOLS_BIN: u8 = 116; const HELP_BIN: u8 = 104; const GET_PX_BIN: u8 = 32; const SET_PX_RGB_BIN: u8 = 128; @@ -22,6 +23,7 @@ impl Parser for Binar match fst { Ok(command) => match command { HELP_BIN => Ok(Command::Help), + PROTOCOLS_BIN => Ok(Command::Protocols), SIZE_BIN => { let canvas = reader.read_u8().await?; Ok(Command::Size(canvas)) From 6dc11526d836145e931d9a8d19cd35ba2e5056b0 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 12:50:34 +0100 Subject: [PATCH 08/11] allow calling `Protocols` from textparser --- src/protocols/text_protocol.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/protocols/text_protocol.rs b/src/protocols/text_protocol.rs index f5e48bf..0255e8f 100644 --- a/src/protocols/text_protocol.rs +++ b/src/protocols/text_protocol.rs @@ -117,6 +117,8 @@ impl Parser for TextP if reader.read_line(&mut line).await.is_ok() { if line.starts_with("HELP") { return Ok(Command::Help); + } else if line.starts_with("PROTOCOLS") { + return Ok(Command::Protocols); } else if line.starts_with("SIZE") { return Ok(Command::Size(self.canvas)); } else if line.starts_with("PX ") { From fe07344cb41f6013a4c7f975a31ccc11e2f2090d Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 12:55:05 +0100 Subject: [PATCH 09/11] use `vec![]` for the `get_status` vector --- src/flutclient.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/flutclient.rs b/src/flutclient.rs index fcbb0f2..f8a771a 100644 --- a/src/flutclient.rs +++ b/src/flutclient.rs @@ -37,14 +37,14 @@ macro_rules! build_parser_type_enum { impl ParserTypes { pub fn get_status() -> Vec { - let mut protos = Vec::new(); + vec![ $( #[cfg(feature = $feat)] - protos.push(ProtocolStatus::Enabled($feat)); + ProtocolStatus::Enabled($feat), #[cfg(not(feature = $feat))] - protos.push(ProtocolStatus::Disabled($feat)); + ProtocolStatus::Disabled($feat), )* - protos + ] } pub fn announce() { From 8bc27cf1d38b43af7504b6403ee5d63e18906f1c Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 19:54:46 +0100 Subject: [PATCH 10/11] add all parsers feature to cargo.toml --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 7f87449..66c10ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,8 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [features] default = ["text", "binary"] +# contains all the parsers +all = ["text", "binary"] text = [] binary = [] From abf43e7c7e0059095375d4611477ec5fbb962153 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 19:57:19 +0100 Subject: [PATCH 11/11] add `--features all` to github workflows --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fa323f9..61fef75 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -28,7 +28,7 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: nightly - - run: cargo test + - run: cargo test --features all clippy: @@ -42,4 +42,4 @@ jobs: with: toolchain: nightly components: clippy - - run: cargo clippy --tests -- -Dclippy::all + - run: cargo clippy --features all --tests -- -Dclippy::all