Merge branch 'main' into dependencies

This commit is contained in:
Noa Aarts 2024-12-11 20:10:18 +01:00 committed by GitHub
commit 56bfecfe7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 161 additions and 42 deletions

View file

@ -28,7 +28,7 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: nightly toolchain: nightly
- run: cargo test - run: cargo test --features all
clippy: clippy:
@ -42,4 +42,4 @@ jobs:
with: with:
toolchain: nightly toolchain: nightly
components: clippy components: clippy
- run: cargo clippy --tests -- -Dclippy::all - run: cargo clippy --features all --tests -- -Dclippy::all

View file

@ -25,6 +25,8 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[features] [features]
default = ["text", "binary"] default = ["text", "binary"]
# contains all the parsers
all = ["text", "binary"]
text = ["dep:atoi_radix10"] text = ["dep:atoi_radix10"]
binary = [] binary = []

View file

@ -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 IMAGE_SAVE_INTERVAL: Duration = Duration::from_secs(5);
pub const JPEG_UPDATE_INTERVAL: Duration = Duration::from_millis(17); pub const JPEG_UPDATE_INTERVAL: Duration = Duration::from_millis(17);
pub const WEB_UPDATE_INTERVAL: Duration = Duration::from_millis(50); 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 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 SIZE returns the size of the canvas

View file

@ -10,7 +10,7 @@ use crate::{
grid::{self, Flut}, grid::{self, Flut},
increment_counter, increment_counter,
protocols::{BinaryParser, IOProtocol, Parser, Responder, TextParser}, 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 { macro_rules! build_parser_type_enum {
@ -26,16 +26,27 @@ macro_rules! build_parser_type_enum {
impl std::default::Default for ParserTypes { impl std::default::Default for ParserTypes {
// add code here // add code here
#[allow(unreachable_code)]
fn default() -> Self { fn default() -> Self {
$( $(
#[cfg(feature = $feat)] #[cfg(feature = $feat)]
#[allow(unreachable_code)]
return ParserTypes::$name(<$t>::default()); return ParserTypes::$name(<$t>::default());
)* )*
} }
} }
impl ParserTypes { 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() { pub fn announce() {
$( $(
#[cfg(feature = $feat)] #[cfg(feature = $feat)]
@ -88,6 +99,14 @@ where
Ok(()) 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<()> { async fn size_command(&mut self, canvas: Canvas) -> io::Result<()> {
let (x, y) = self.grids[canvas as usize].get_size(); let (x, y) = self.grids[canvas as usize].get_size();
match_parser!(parser: self.parser => parser.unparse( match_parser!(parser: self.parser => parser.unparse(
@ -166,6 +185,7 @@ where
match parsed { match parsed {
Ok(Command::Help) => self.help_command().await?, Ok(Command::Help) => self.help_command().await?,
Ok(Command::Size(canvas)) => self.size_command(canvas).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::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::SetPixel(canvas, x, y, color)) => self.set_pixel_command(canvas, x, y, &color),
Ok(Command::ChangeCanvas(canvas)) => { Ok(Command::ChangeCanvas(canvas)) => {

View file

@ -53,6 +53,12 @@ fn increment_counter(amount: u64) {
COUNTER.fetch_add(amount, std::sync::atomic::Ordering::Relaxed); COUNTER.fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
} }
#[derive(Debug, PartialEq)]
pub enum ProtocolStatus {
Enabled(&'static str),
Disabled(&'static str),
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Protocol { pub enum Protocol {
Text, Text,
@ -62,6 +68,7 @@ pub enum Protocol {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Command { pub enum Command {
Help, Help,
Protocols,
Size(Canvas), Size(Canvas),
GetPixel(Canvas, Coordinate, Coordinate), GetPixel(Canvas, Coordinate, Coordinate),
SetPixel(Canvas, Coordinate, Coordinate, Color), SetPixel(Canvas, Coordinate, Coordinate, Color),
@ -72,6 +79,7 @@ pub enum Command {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Response { pub enum Response {
Help, Help,
Protocols(Vec<ProtocolStatus>),
Size(Coordinate, Coordinate), Size(Coordinate, Coordinate),
GetPixel(Coordinate, Coordinate, [u8; 3]), GetPixel(Coordinate, Coordinate, [u8; 3]),
} }

View file

@ -8,7 +8,9 @@ use std::{
}; };
use flurry::{ 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}, flutclient::{FlutClient, ParserTypes},
grid::{self, Flut}, grid::{self, Flut},
webapi::WebApiContext, webapi::WebApiContext,
@ -20,7 +22,7 @@ use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _
/// This function logs the current amount of changed pixels to stdout every second /// This function logs the current amount of changed pixels to stdout every second
async fn pixel_change_stdout_log() -> AsyncResult<Never> { async fn pixel_change_stdout_log() -> AsyncResult<Never> {
let mut interval = tokio::time::interval(Duration::from_millis(1000)); let mut interval = tokio::time::interval(STDOUT_STATISTICS_INTERVAL);
loop { loop {
interval.tick().await; interval.tick().await;
let cnt = COUNTER.load(std::sync::atomic::Ordering::Relaxed); let cnt = COUNTER.load(std::sync::atomic::Ordering::Relaxed);

View file

@ -7,6 +7,7 @@ use crate::{Canvas, Color, Command, Response};
use super::{IOProtocol, Parser, Responder}; use super::{IOProtocol, Parser, Responder};
const SIZE_BIN: u8 = 115; const SIZE_BIN: u8 = 115;
const PROTOCOLS_BIN: u8 = 116;
const HELP_BIN: u8 = 104; const HELP_BIN: u8 = 104;
const GET_PX_BIN: u8 = 32; const GET_PX_BIN: u8 = 32;
const SET_PX_RGB_BIN: u8 = 128; const SET_PX_RGB_BIN: u8 = 128;
@ -22,6 +23,7 @@ impl<R: AsyncBufRead + AsyncBufReadExt + std::marker::Unpin> Parser<R> for Binar
match fst { match fst {
Ok(command) => match command { Ok(command) => match command {
HELP_BIN => Ok(Command::Help), HELP_BIN => Ok(Command::Help),
PROTOCOLS_BIN => Ok(Command::Protocols),
SIZE_BIN => { SIZE_BIN => {
let canvas = reader.read_u8().await?; let canvas = reader.read_u8().await?;
Ok(Command::Size(canvas)) Ok(Command::Size(canvas))
@ -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 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) => { Response::Size(x, y) => {
writer.write_u16(x).await?; writer.write_u16(x).await?;
writer.write_u16(y).await writer.write_u16(y).await

View file

@ -117,6 +117,8 @@ impl<R: AsyncBufRead + AsyncBufReadExt + std::marker::Unpin> Parser<R> for TextP
if reader.read_line(&mut line).await.is_ok() { if reader.read_line(&mut line).await.is_ok() {
if line.starts_with("HELP") { if line.starts_with("HELP") {
return Ok(Command::Help); return Ok(Command::Help);
} else if line.starts_with("PROTOCOLS") {
return Ok(Command::Protocols);
} else if line.starts_with("SIZE") { } else if line.starts_with("SIZE") {
return Ok(Command::Size(self.canvas)); return Ok(Command::Size(self.canvas));
} else if line.starts_with("PX ") { } else if line.starts_with("PX ") {
@ -146,6 +148,23 @@ impl<W: AsyncWriteExt + std::marker::Unpin> Responder<W> for TextParser {
async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()> { async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()> {
match response { match response {
Response::Help => writer.write_all(HELP_TEXT).await, 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::Size(x, y) => writer.write_all(format!("SIZE {x} {y}\n").as_bytes()).await,
Response::GetPixel(x, y, color) => { Response::GetPixel(x, y, color) => {
writer writer
@ -209,6 +228,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] #[tokio::test]
async fn test_px_set_rgb_parse() { async fn test_px_set_rgb_parse() {
let parser = TextParser::default(); let parser = TextParser::default();
@ -223,6 +256,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] #[tokio::test]
async fn test_px_set_rgba_parse() { async fn test_px_set_rgba_parse() {
let parser = TextParser::default(); let parser = TextParser::default();
@ -237,6 +284,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] #[tokio::test]
async fn test_px_get_parse() { async fn test_px_get_parse() {
let parser = TextParser::default(); let parser = TextParser::default();

View file

@ -28,6 +28,42 @@ impl Multipart {
} }
} }
impl Multipart {
fn write_multipart_frame<T>(
obj: T,
boundary: Vec<u8>,
headers: HeaderMap,
first: bool,
) -> Result<Vec<u8>, axum::Error>
where
T: IntoIterator<Item = u8>,
{
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<T> StreamingFormat<T> for Multipart impl<T> StreamingFormat<T> for Multipart
where where
T: Send + Sync + IntoIterator<Item = u8> + 'static, T: Send + Sync + IntoIterator<Item = u8> + 'static,
@ -37,40 +73,6 @@ where
stream: futures::stream::BoxStream<'b, Result<T, axum::Error>>, stream: futures::stream::BoxStream<'b, Result<T, axum::Error>>,
_options: &'a axum_streams::StreamBodyAsOptions, _options: &'a axum_streams::StreamBodyAsOptions,
) -> futures::stream::BoxStream<'b, Result<axum::body::Bytes, axum::Error>> { ) -> futures::stream::BoxStream<'b, Result<axum::body::Bytes, axum::Error>> {
fn write_multipart_frame<T>(
obj: T,
boundary: Vec<u8>,
headers: HeaderMap,
first: bool,
) -> Result<Vec<u8>, axum::Error>
where
T: IntoIterator<Item = u8>,
{
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 boundary = self.boundary.clone();
let headers = self.headers.clone(); let headers = self.headers.clone();
let first = self.first; let first = self.first;
@ -79,8 +81,12 @@ where
stream.map(move |obj_res| match obj_res { stream.map(move |obj_res| match obj_res {
Err(e) => Err(e), Err(e) => Err(e),
Ok(obj) => { Ok(obj) => {
let picture_framed = let picture_framed = Multipart::write_multipart_frame(
write_multipart_frame(obj, boundary.clone(), headers.clone(), first); obj,
boundary.clone(),
headers.clone(),
first,
);
picture_framed.map(axum::body::Bytes::from) picture_framed.map(axum::body::Bytes::from)
} }