From 92ae8586e2f8c7661476b20454980897a5727b70 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Tue, 10 Dec 2024 23:38:23 +0100 Subject: [PATCH 01/10] add `Clone` and `to_bytes()` to Color as utilities --- src/color.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/color.rs b/src/color.rs index b372a72..2341176 100644 --- a/src/color.rs +++ b/src/color.rs @@ -2,13 +2,23 @@ use std::fmt::Display; use rand::{distributions::Standard, prelude::Distribution}; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Color { RGB24(u8, u8, u8), RGBA32(u8, u8, u8, u8), W8(u8), } +impl Color { + pub fn to_bytes(&self) -> [u8; 4] { + match self { + Color::RGB24(r, g, b) => [*r, *g, *b, 0xff], + Color::RGBA32(r, g, b, a) => [*r, *g, *b, *a], + Color::W8(w) => [*w, *w, *w, 0xff], + } + } +} + impl Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { From fb7e9310aed8d5d262db8d4c28cc1beb6b2d81df Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Tue, 10 Dec 2024 23:40:50 +0100 Subject: [PATCH 02/10] add the palette protocol, with up to 256 colors in the palette --- Cargo.toml | 1 + src/flutclient.rs | 12 +++- src/lib.rs | 1 + src/protocols.rs | 2 + src/protocols/palette_protocol.rs | 105 ++++++++++++++++++++++++++++++ src/protocols/text_protocol.rs | 1 + 6 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 src/protocols/palette_protocol.rs diff --git a/Cargo.toml b/Cargo.toml index 7f87449..3670c11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } default = ["text", "binary"] text = [] binary = [] +palette = [] [dev-dependencies] tempfile = "*" diff --git a/src/flutclient.rs b/src/flutclient.rs index ddd9f2f..9fbc31c 100644 --- a/src/flutclient.rs +++ b/src/flutclient.rs @@ -9,7 +9,7 @@ use crate::{ get_pixel, grid::{self, Flut}, increment_counter, - protocols::{BinaryParser, IOProtocol, Parser, Responder, TextParser}, + protocols::{BinaryParser, IOProtocol, PaletteParser, Parser, Responder, TextParser}, set_pixel_rgba, Canvas, Color, Command, Coordinate, Protocol, Response, }; @@ -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()); )* } @@ -62,6 +62,7 @@ macro_rules! build_parser_type_enum { build_parser_type_enum! { TextParser: TextParser: "text", BinaryParser: BinaryParser: "binary", + PaletteParser: PaletteParser: "palette", } pub struct FlutClient @@ -145,6 +146,13 @@ where self.writer.write(b"feature \"binary\" is not enabled."); self.writer.flush(); } + #[cfg(feature = "palette")] + Protocol::Palette => self.parser = ParserTypes::Palette(PaletteParser::default()), + #[cfg(not(feature = "palette"))] + Protocol::Palette => { + self.writer.write(b"feature \"binary\" is not enabled."); + self.writer.flush(); + } } } diff --git a/src/lib.rs b/src/lib.rs index 4b35c6f..28ffa76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,7 @@ fn increment_counter(amount: u64) { pub enum Protocol { Text, Binary, + Palette, } #[derive(Debug, PartialEq)] diff --git a/src/protocols.rs b/src/protocols.rs index 12aa1a6..5fe8c4c 100644 --- a/src/protocols.rs +++ b/src/protocols.rs @@ -1,9 +1,11 @@ mod binary_protocol; +mod palette_protocol; mod text_protocol; use std::io; pub use binary_protocol::BinaryParser; +pub use palette_protocol::PaletteParser; pub use text_protocol::TextParser; use tokio::io::AsyncWriteExt; diff --git a/src/protocols/palette_protocol.rs b/src/protocols/palette_protocol.rs new file mode 100644 index 0000000..c053597 --- /dev/null +++ b/src/protocols/palette_protocol.rs @@ -0,0 +1,105 @@ +use std::io::{self, Error, ErrorKind}; + +use image::EncodableLayout; +use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}; + +use crate::{Canvas, Color, Command, Response}; + +use super::{IOProtocol, Parser, Responder}; + +const SIZE_BIN: u8 = 115; +const HELP_BIN: u8 = 104; +const GET_PX_BIN: u8 = 32; +const SET_PX_BIN: u8 = 33; + +#[derive(Clone)] +pub struct PaletteParser { + colors: [Color; 256], +} + +impl Default for PaletteParser { + fn default() -> Self { + PaletteParser { + colors: [const { Color::RGB24(0, 0, 0) }; 256], + } + } +} + +impl Parser for PaletteParser { + async fn parse(&self, reader: &mut R) -> io::Result { + let fst = reader.read_u8().await; + match fst { + Ok(command) => match command { + HELP_BIN => Ok(Command::Help), + SIZE_BIN => { + let canvas = reader.read_u8().await?; + Ok(Command::Size(canvas)) + } + GET_PX_BIN => { + let canvas = reader.read_u8().await?; + let horizontal = reader.read_u16().await?; + let vertical = reader.read_u16().await?; + Ok(Command::GetPixel(canvas, horizontal, vertical)) + } + SET_PX_BIN => { + let canvas = reader.read_u8().await?; + let horizontal = reader.read_u16().await?; + let vertical = reader.read_u16().await?; + let color = reader.read_u8().await?; + Ok(Command::SetPixel(canvas, horizontal, vertical, unsafe { + self.colors.get_unchecked(color as usize).clone() + })) + } + _ => { + tracing::error!("received illegal command: {command}"); + Err(Error::from(ErrorKind::InvalidInput)) + } + }, + Err(err) => { + tracing::error!("{err}"); + Err(err) + } + } + } +} + +impl IOProtocol for PaletteParser { + fn change_canvas(&mut self, _canvas: Canvas) -> io::Result<()> { + Err(Error::from(ErrorKind::Unsupported)) + } +} + +impl Responder for PaletteParser { + async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()> { + match response { + Response::Help => { + writer + .write_all( + self.colors + .iter() + .map(|c| c.to_bytes()) + .collect::>() + .concat() + .as_bytes(), + ) + .await + } + Response::Size(x, y) => { + writer.write_u16(x).await?; + writer.write_u16(y).await + } + Response::GetPixel(_, _, c) => { + writer.write_u8(c[0]).await?; + writer.write_u8(c[1]).await?; + writer.write_u8(c[2]).await + } + } + } +} + +#[cfg(test)] +#[allow(clippy::needless_return)] +mod tests { + use super::*; + use tokio::io::BufReader; +} diff --git a/src/protocols/text_protocol.rs b/src/protocols/text_protocol.rs index 1c711a2..cebf21f 100644 --- a/src/protocols/text_protocol.rs +++ b/src/protocols/text_protocol.rs @@ -106,6 +106,7 @@ impl TextParser { match protocol { "binary" => Ok(Command::ChangeProtocol(Protocol::Binary)), "text" => Ok(Command::ChangeProtocol(Protocol::Text)), + "palette" => Ok(Command::ChangeProtocol(Protocol::Palette)), _ => Err(Error::from(ErrorKind::InvalidInput)), } } From 95a408910ca99b4f70a755bc3b5f71de5d6535d2 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 11:47:41 +0100 Subject: [PATCH 03/10] feature flags for use statements --- src/flutclient.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/flutclient.rs b/src/flutclient.rs index 9fbc31c..a905a29 100644 --- a/src/flutclient.rs +++ b/src/flutclient.rs @@ -5,11 +5,17 @@ use std::{ use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}; +#[cfg(feature = "binary")] +use crate::protocols::BinaryParser; +#[cfg(feature = "palette")] +use crate::protocols::PaletteParser; +#[cfg(feature = "text")] +use crate::protocols::TextParser; use crate::{ get_pixel, grid::{self, Flut}, increment_counter, - protocols::{BinaryParser, IOProtocol, PaletteParser, Parser, Responder, TextParser}, + protocols::{IOProtocol, Parser, Responder}, set_pixel_rgba, Canvas, Color, Command, Coordinate, Protocol, Response, }; From 676dc184140df658e4f3fdfc538a109d549294d2 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 11:47:55 +0100 Subject: [PATCH 04/10] make the writes and flush await --- src/flutclient.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/flutclient.rs b/src/flutclient.rs index a905a29..8c222a9 100644 --- a/src/flutclient.rs +++ b/src/flutclient.rs @@ -136,30 +136,37 @@ where match_parser!(parser: self.parser => parser.change_canvas(canvas)) } - fn change_protocol(&mut self, protocol: &Protocol) { + async fn change_protocol(&mut self, protocol: &Protocol) -> io::Result<()> { match protocol { #[cfg(feature = "text")] Protocol::Text => self.parser = ParserTypes::TextParser(TextParser::default()), #[cfg(not(feature = "text"))] Protocol::Text => { - self.writer.write(b"feature \"text\" is not enabled."); - self.writer.flush(); + self.writer + .write_all(b"feature \"text\" is not enabled.") + .await?; + self.writer.flush().await?; } #[cfg(feature = "binary")] Protocol::Binary => self.parser = ParserTypes::BinaryParser(BinaryParser::default()), #[cfg(not(feature = "binary"))] Protocol::Binary => { - self.writer.write(b"feature \"binary\" is not enabled."); - self.writer.flush(); + self.writer + .write_all(b"feature \"binary\" is not enabled.") + .await?; + self.writer.flush().await?; } #[cfg(feature = "palette")] - Protocol::Palette => self.parser = ParserTypes::Palette(PaletteParser::default()), + Protocol::Palette => self.parser = ParserTypes::PaletteParser(PaletteParser::default()), #[cfg(not(feature = "palette"))] Protocol::Palette => { - self.writer.write(b"feature \"binary\" is not enabled."); - self.writer.flush(); + self.writer + .write_all(b"feature \"binary\" is not enabled.") + .await?; + self.writer.flush().await?; } } + Ok(()) } pub fn new(reader: R, writer: W, grids: Arc<[grid::Flut]>) -> Self { @@ -187,7 +194,7 @@ where break 'outer; } Ok(Command::ChangeProtocol(protocol)) => { - self.change_protocol(&protocol); + self.change_protocol(&protocol).await?; break 'outer; } Err(err) if err.kind() == ErrorKind::UnexpectedEof => { From 2cde50964d4321461dbb913b6b4c41a830904637 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 11:48:45 +0100 Subject: [PATCH 05/10] initialize with random palette --- src/protocols/palette_protocol.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/protocols/palette_protocol.rs b/src/protocols/palette_protocol.rs index c053597..7c42b27 100644 --- a/src/protocols/palette_protocol.rs +++ b/src/protocols/palette_protocol.rs @@ -1,6 +1,7 @@ use std::io::{self, Error, ErrorKind}; use image::EncodableLayout; +use rand::random; use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}; use crate::{Canvas, Color, Command, Response}; @@ -20,7 +21,7 @@ pub struct PaletteParser { impl Default for PaletteParser { fn default() -> Self { PaletteParser { - colors: [const { Color::RGB24(0, 0, 0) }; 256], + colors: [0; 256].map(|_| random()), } } } From ee86585d77a3c02c49b4ca3e2561a125e9f5a6ba Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 11:49:02 +0100 Subject: [PATCH 06/10] rename constant to be more descriptive --- src/protocols/palette_protocol.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocols/palette_protocol.rs b/src/protocols/palette_protocol.rs index 7c42b27..b122f35 100644 --- a/src/protocols/palette_protocol.rs +++ b/src/protocols/palette_protocol.rs @@ -11,7 +11,7 @@ use super::{IOProtocol, Parser, Responder}; const SIZE_BIN: u8 = 115; const HELP_BIN: u8 = 104; const GET_PX_BIN: u8 = 32; -const SET_PX_BIN: u8 = 33; +const SET_PX_PALETTE_BIN: u8 = 33; #[derive(Clone)] pub struct PaletteParser { @@ -42,7 +42,7 @@ impl Parser for Palet let vertical = reader.read_u16().await?; Ok(Command::GetPixel(canvas, horizontal, vertical)) } - SET_PX_BIN => { + SET_PX_PALETTE_BIN => { let canvas = reader.read_u8().await?; let horizontal = reader.read_u16().await?; let vertical = reader.read_u16().await?; From a91ca399a980d7b2bd044af7159da96398c0ea79 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 11:49:16 +0100 Subject: [PATCH 07/10] add test for parsing palette set command --- src/protocols/palette_protocol.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/protocols/palette_protocol.rs b/src/protocols/palette_protocol.rs index b122f35..ed60df3 100644 --- a/src/protocols/palette_protocol.rs +++ b/src/protocols/palette_protocol.rs @@ -103,4 +103,18 @@ impl Responder for PaletteParser { mod tests { use super::*; use tokio::io::BufReader; + + #[tokio::test] + async fn test_palette_px_set_parse() { + let parser = PaletteParser::default(); + let reader = tokio_test::io::Builder::new() + .read(&[SET_PX_PALETTE_BIN, 0x01, 0x69, 0x42, 0x42, 0x69, 0x82]) + .build(); + let mut bufreader = BufReader::new(reader); + let thingy = parser.parse(&mut bufreader).await.unwrap(); + assert_eq!( + thingy, + Command::SetPixel(1, 0x6942, 0x4269, parser.colors[0x82].clone()) + ); + } } From 0d0e11aa47f001b850d95ec6a4686eaca07e7b99 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 19:44:29 +0100 Subject: [PATCH 08/10] add option to set palette color --- src/flutclient.rs | 12 ++++++++++++ src/lib.rs | 2 ++ src/protocols/palette_protocol.rs | 15 +++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/src/flutclient.rs b/src/flutclient.rs index 8c222a9..49c1e57 100644 --- a/src/flutclient.rs +++ b/src/flutclient.rs @@ -136,6 +136,14 @@ where match_parser!(parser: self.parser => parser.change_canvas(canvas)) } + #[cfg(feature = "palette")] + fn change_color(&mut self, index: u8, color: Color) -> () { + match &mut self.parser { + ParserTypes::PaletteParser(ref mut parser) => parser.set_color(index, color), + _ => {} + }; + } + async fn change_protocol(&mut self, protocol: &Protocol) -> io::Result<()> { match protocol { #[cfg(feature = "text")] @@ -197,6 +205,10 @@ where self.change_protocol(&protocol).await?; break 'outer; } + #[cfg(feature = "palette")] + Ok(Command::ChangeColor(index, color)) => { + self.change_color(index, color); + } Err(err) if err.kind() == ErrorKind::UnexpectedEof => { increment_counter(self.counter); return Ok(())}, diff --git a/src/lib.rs b/src/lib.rs index 28ffa76..9e23463 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,8 @@ pub enum Command { SetPixel(Canvas, Coordinate, Coordinate, Color), ChangeCanvas(Canvas), ChangeProtocol(Protocol), + #[cfg(feature = "palette")] + ChangeColor(u8, Color), } #[derive(Debug, PartialEq)] diff --git a/src/protocols/palette_protocol.rs b/src/protocols/palette_protocol.rs index ed60df3..8c318ac 100644 --- a/src/protocols/palette_protocol.rs +++ b/src/protocols/palette_protocol.rs @@ -12,12 +12,19 @@ const SIZE_BIN: u8 = 115; const HELP_BIN: u8 = 104; const GET_PX_BIN: u8 = 32; const SET_PX_PALETTE_BIN: u8 = 33; +const SET_PALETTE_COLOR: u8 = 34; #[derive(Clone)] pub struct PaletteParser { colors: [Color; 256], } +impl PaletteParser { + pub fn set_color(&mut self, index: u8, color: Color) { + self.colors[index as usize] = color; + } +} + impl Default for PaletteParser { fn default() -> Self { PaletteParser { @@ -51,6 +58,14 @@ impl Parser for Palet self.colors.get_unchecked(color as usize).clone() })) } + #[cfg(feature = "palette")] + SET_PALETTE_COLOR => { + let index = reader.read_u8().await?; + let r = reader.read_u8().await?; + let g = reader.read_u8().await?; + let b = reader.read_u8().await?; + Ok(Command::ChangeColor(index, Color::RGB24(r, g, b))) + } _ => { tracing::error!("received illegal command: {command}"); Err(Error::from(ErrorKind::InvalidInput)) From 3947cc7d35e601c11e5114fe0d879b730d676932 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 19:53:34 +0100 Subject: [PATCH 09/10] add tests for palette set_color --- src/protocols/palette_protocol.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/protocols/palette_protocol.rs b/src/protocols/palette_protocol.rs index 8c318ac..0d4436a 100644 --- a/src/protocols/palette_protocol.rs +++ b/src/protocols/palette_protocol.rs @@ -12,6 +12,7 @@ const SIZE_BIN: u8 = 115; const HELP_BIN: u8 = 104; const GET_PX_BIN: u8 = 32; const SET_PX_PALETTE_BIN: u8 = 33; +#[cfg(feature = "palette")] const SET_PALETTE_COLOR: u8 = 34; #[derive(Clone)] @@ -132,4 +133,30 @@ mod tests { Command::SetPixel(1, 0x6942, 0x4269, parser.colors[0x82].clone()) ); } + + #[cfg(feature = "palette")] + #[tokio::test] + async fn test_palette_set_palette_color_parse() { + let parser = PaletteParser::default(); + let reader = tokio_test::io::Builder::new() + .read(&[SET_PALETTE_COLOR, 0x04, 0xfa, 0xbd, 0x2f]) + .build(); + let mut bufreader = BufReader::new(reader); + let thingy = parser.parse(&mut bufreader).await.unwrap(); + assert_eq!( + thingy, + Command::ChangeColor(0x04, Color::RGB24(0xfa, 0xbd, 0x2f)) + ); + } + + #[tokio::test] + async fn test_palette_set_color() { + let mut parser = PaletteParser::default(); + parser.set_color(0x04, Color::RGB24(0xfa, 0xbd, 0x2f)); + parser.set_color(0x13, Color::RGB24(0x23, 0x45, 0x67)); + parser.set_color(0x69, Color::RGB24(0xb0, 0x07, 0x1e)); + assert_eq!(parser.colors[0x04], Color::RGB24(0xfa, 0xbd, 0x2f)); + assert_eq!(parser.colors[0x13], Color::RGB24(0x23, 0x45, 0x67)); + assert_eq!(parser.colors[0x69], Color::RGB24(0xb0, 0x07, 0x1e)); + } } From 51d1d392d940f444f82436790ca93c39674e4977 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 11 Dec 2024 20:14:06 +0100 Subject: [PATCH 10/10] add protocol response for paletteparser --- src/protocols/palette_protocol.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/protocols/palette_protocol.rs b/src/protocols/palette_protocol.rs index 0d4436a..2e3c543 100644 --- a/src/protocols/palette_protocol.rs +++ b/src/protocols/palette_protocol.rs @@ -9,6 +9,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_PALETTE_BIN: u8 = 33; @@ -40,6 +41,7 @@ impl Parser for Palet 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)) @@ -101,6 +103,23 @@ impl Responder for PaletteParser { ) .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