Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
default = ["text", "binary"]
text = []
binary = []
palette = []

[dev-dependencies]
tempfile = "*"
Expand Down
12 changes: 11 additions & 1 deletion src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
59 changes: 50 additions & 9 deletions src/flutclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ 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, Parser, Responder, TextParser},
set_pixel_rgba, Canvas, Color, Command, Coordinate, Protocol, Response,
protocols::{IOProtocol, Parser, Responder},
set_pixel_rgba, Canvas, Color, Command, Coordinate, Protocol, ProtocolStatus, Response,
};

macro_rules! build_parser_type_enum {
Expand All @@ -26,16 +32,27 @@ 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());
)*
}
}

impl ParserTypes {
pub fn get_status() -> Vec<ProtocolStatus> {
vec![
$(
#[cfg(feature = $feat)]
ProtocolStatus::Enabled($feat),
#[cfg(not(feature = $feat))]
ProtocolStatus::Disabled($feat),
)*
]
}

pub fn announce() {
$(
#[cfg(feature = $feat)]
Expand All @@ -62,6 +79,7 @@ macro_rules! build_parser_type_enum {
build_parser_type_enum! {
TextParser: TextParser: "text",
BinaryParser: BinaryParser: "binary",
PaletteParser: PaletteParser: "palette",
}

pub struct FlutClient<R, W>
Expand All @@ -88,6 +106,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(
Expand Down Expand Up @@ -129,23 +155,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::PaletteParser(PaletteParser::default()),
#[cfg(not(feature = "palette"))]
Protocol::Palette => {
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<u32>]>) -> Self {
Expand All @@ -166,14 +206,15 @@ 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)) => {
self.change_canvas_command(canvas)?;
break 'outer;
}
Ok(Command::ChangeProtocol(protocol)) => {
self.change_protocol(&protocol);
self.change_protocol(&protocol).await?;
break 'outer;
}
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
Expand Down
9 changes: 9 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,23 @@ 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,
Binary,
Palette,
}

#[derive(Debug, PartialEq)]
pub enum Command {
Help,
Protocols,
Size(Canvas),
GetPixel(Canvas, Coordinate, Coordinate),
SetPixel(Canvas, Coordinate, Coordinate, Color),
Expand All @@ -72,6 +80,7 @@ pub enum Command {
#[derive(Debug, PartialEq)]
pub enum Response {
Help,
Protocols(Vec<ProtocolStatus>),
Size(Coordinate, Coordinate),
GetPixel(Coordinate, Coordinate, [u8; 3]),
}
2 changes: 2 additions & 0 deletions src/protocols.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
19 changes: 19 additions & 0 deletions src/protocols/binary_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,6 +23,7 @@ impl<R: AsyncBufRead + AsyncBufReadExt + std::marker::Unpin> Parser<R> 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))
Expand Down Expand Up @@ -106,6 +108,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
Expand Down
139 changes: 139 additions & 0 deletions src/protocols/palette_protocol.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
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};

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;

#[derive(Clone)]
pub struct PaletteParser {
colors: [Color; 256],
}

impl Default for PaletteParser {
fn default() -> Self {
PaletteParser {
colors: [0; 256].map(|_| random()),
}
}
}

impl<R: AsyncBufRead + AsyncBufReadExt + std::marker::Unpin> Parser<R> for PaletteParser {
async fn parse(&self, reader: &mut R) -> io::Result<Command> {
let fst = reader.read_u8().await;
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))
}
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_PALETTE_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<W: AsyncWriteExt + std::marker::Unpin> Responder<W> 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::<Vec<_>>()
.concat()
.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
}
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;

#[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())
);
}
}
Loading