refactor: split into more logical files

This commit is contained in:
Noa Aarts 2024-10-18 01:56:23 +02:00
parent 19eb943865
commit 9dc681086d
Signed by: noa
GPG key ID: 1850932741EFF672
11 changed files with 728 additions and 276 deletions

11
src/config.rs Normal file
View file

@ -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
";

151
src/flutclient.rs Normal file
View file

@ -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<R, W>
where
R: AsyncReadExt + std::marker::Unpin,
W: AsyncWriteExt + std::marker::Unpin,
{
reader: BufReader<R>,
writer: BufWriter<W>,
grids: Arc<[Flut<u32>]>,
parser: ParserTypes,
counter: u64,
}
impl<R, W> FlutClient<R, W>
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<u32>]>) -> 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;
});
}
}
}

View file

@ -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<u32> {
}
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<u32> {
#[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)
});
}
}

77
src/lib.rs Normal file
View file

@ -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<u32>],
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<u32>],
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]),
}

View file

@ -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<u32>],
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<u32>],
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<R>
where
R: std::marker::Unpin + tokio::io::AsyncBufRead,
{
async fn parse(&self, reader: &mut R) -> io::Result<Command>;
}
trait IOProtocol {
fn change_canvas(&mut self, canvas: Canvas) -> io::Result<()>;
}
trait Responder<W>
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<R, W>
where
R: AsyncReadExt + std::marker::Unpin,
W: AsyncWriteExt + std::marker::Unpin,
{
reader: BufReader<R>,
writer: BufWriter<W>,
grids: Arc<[Flut<u32>]>,
parser: ParserTypes,
counter: u64,
}
impl<R, W> FlutClient<R, W>
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<u32>]>) -> FlutClient<R, W> {
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<u32>]>) -> io::Result<()> {
let base_dir = Path::new("./recordings");
let mut timer = interval(Duration::from_secs(5));

29
src/protocols.rs Normal file
View file

@ -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<R>
where
R: std::marker::Unpin + tokio::io::AsyncBufRead,
{
async fn parse(&self, reader: &mut R) -> io::Result<Command>;
}
pub trait IOProtocol {
fn change_canvas(&mut self, canvas: Canvas) -> io::Result<()>;
}
pub trait Responder<W>
where
W: AsyncWriteExt + std::marker::Unpin,
{
async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()>;
}

View file

@ -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;

View file

@ -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,

62
src/utils.rs Normal file
View file

@ -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<std::io::Result<()>> {
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<Result<usize, std::io::Error>> {
Poll::Ready(Ok(buf.len()))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
Poll::Ready(Ok(()))
}
}