feat: WOWA, ik workie :3

This commit is contained in:
Noa Aarts 2024-10-04 19:30:15 +02:00
parent ccf226f350
commit 9350e6bc63
Signed by: noa
GPG key ID: 1850932741EFF672
6 changed files with 718 additions and 247 deletions

26
Cargo.lock generated
View file

@ -39,6 +39,23 @@ dependencies = [
"syn",
]
[[package]]
name = "async-trait"
version = "0.1.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "atoi_radix10"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6970a22a33d6a8f862aac371bac48505a1bfaa230ecb268c7b86fa4ac6e7121"
[[package]]
name = "autocfg"
version = "1.3.0"
@ -88,7 +105,10 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
name = "flurry"
version = "0.1.0"
dependencies = [
"async-trait",
"atoi_radix10",
"bytes",
"hex",
"tokio",
"tokio-test",
]
@ -111,6 +131,12 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "libc"
version = "0.2.155"

View file

@ -4,7 +4,10 @@ version = "0.1.0"
edition = "2021"
[dependencies]
async-trait = "0.1.83"
atoi_radix10 = "0.0.1"
bytes = "1.6.0"
hex = "0.4.3"
tokio = { version = "1.38", features = ["full"] }
tokio-test = "*"

234
src/binary_protocol.rs Normal file
View file

@ -0,0 +1,234 @@
use std::io::{self, Error, ErrorKind};
use atoi_radix10::parse_from_str;
use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt};
use crate::{
increment_counter, Canvas, Color, Command, Coordinate, Parser, Protocol, Responder, Response,
MEEHHEH,
};
const SIZE_BIN: u8 = 115;
const HELP_BIN: u8 = 104;
const LOCK: u8 = 0;
const GET_PX_BIN: u8 = 32;
const SET_PX_RGB_BIN: u8 = 128;
const SET_PX_RGBA_BIN: u8 = 129;
const SET_PX_W_BIN: u8 = 130;
const SET_PX_RGB_BIN_LENGTH: usize = 8;
pub struct BinaryParser {}
impl BinaryParser {
pub fn new() -> BinaryParser {
return BinaryParser {};
}
}
impl<R: AsyncBufRead + AsyncBufReadExt + std::marker::Unpin> Parser<R> for BinaryParser {
async fn parse(&self, reader: &mut R) -> io::Result<Command> {
let fst = reader.read_u8().await;
match fst {
Ok(i) => match i {
HELP_BIN => return Ok(Command::Help),
SIZE_BIN => {
let canvas = reader.read_u8().await?;
return Ok(Command::Size(canvas));
}
GET_PX_BIN => {
let canvas = reader.read_u8().await?;
let x = reader.read_u16_le().await?;
let y = reader.read_u16_le().await?;
return Ok(Command::GetPixel(canvas, x, y));
}
SET_PX_W_BIN => {
let canvas = reader.read_u8().await?;
let x = reader.read_u16_le().await?;
let y = reader.read_u16_le().await?;
let w = reader.read_u8().await?;
return Ok(Command::SetPixel(canvas, x, y, Color::W8(w)));
}
SET_PX_RGB_BIN => {
let canvas = reader.read_u8().await?;
let x = reader.read_u16_le().await?;
let y = reader.read_u16_le().await?;
let r = reader.read_u8().await?;
let g = reader.read_u8().await?;
let b = reader.read_u8().await?;
return Ok(Command::SetPixel(canvas, x, y, Color::RGB24(r, g, b)));
}
SET_PX_RGBA_BIN => {
let canvas = reader.read_u8().await?;
let x = reader.read_u16_le().await?;
let y = reader.read_u16_le().await?;
let r = reader.read_u8().await?;
let g = reader.read_u8().await?;
let b = reader.read_u8().await?;
let a = reader.read_u8().await?;
return Ok(Command::SetPixel(canvas, x, y, Color::RGBA32(r, g, b, a)));
}
_ => {
eprintln!("received illegal command: {}", i);
return Err(Error::from(ErrorKind::InvalidInput));
}
},
Err(err) => {
eprintln!("{}", err);
return Err(err);
}
}
}
}
impl MEEHHEH for BinaryParser {
fn change_canvas(&mut self, _canvas: Canvas) -> io::Result<()> {
return Err(Error::from(ErrorKind::Unsupported));
}
}
impl<W: AsyncWriteExt + std::marker::Unpin> Responder<W> for BinaryParser {
async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()> {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grid::FlutGrid;
use tokio::io::BufReader;
use tokio_test::assert_ok;
#[tokio::test]
async fn test_bin_help_parse() {
let parser = BinaryParser::new();
let reader = tokio_test::io::Builder::new().read(&[HELP_BIN]).build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
assert_eq!(thingy.unwrap(), Command::Help)
}
#[tokio::test]
async fn test_bin_size_parse() {
let parser = BinaryParser::new();
let reader = tokio_test::io::Builder::new().read(&[SIZE_BIN, 3]).build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
assert_eq!(thingy.unwrap(), Command::Size(3))
}
#[tokio::test]
async fn test_bin_px_set_w_parse() {
let parser = BinaryParser::new();
let reader = tokio_test::io::Builder::new()
.read(&[SET_PX_W_BIN, 0x01, 0x69, 0x42, 0x42, 0x69, 0x82])
.build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
assert_eq!(
thingy.unwrap(),
Command::SetPixel(1, 0x4269, 0x6942, Color::W8(0x82))
)
}
#[tokio::test]
async fn test_bin_px_set_rgb_parse() {
let parser = BinaryParser::new();
let reader = tokio_test::io::Builder::new()
.read(&[
SET_PX_RGB_BIN,
0x01,
0x69,
0x42,
0x42,
0x69,
0x82,
0x00,
0xff,
])
.build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
assert_eq!(
thingy.unwrap(),
Command::SetPixel(1, 0x4269, 0x6942, Color::RGB24(0x82, 0x00, 0xff))
)
}
#[tokio::test]
async fn test_bin_px_set_rgba_parse() {
let parser = BinaryParser::new();
let reader = tokio_test::io::Builder::new()
.read(&[
SET_PX_RGBA_BIN,
0x01,
0x69,
0x42,
0x42,
0x69,
0x82,
0x00,
0xff,
0xa0,
])
.build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
assert_eq!(
thingy.unwrap(),
Command::SetPixel(1, 0x4269, 0x6942, Color::RGBA32(0x82, 0x00, 0xff, 0xa0))
)
}
#[tokio::test]
async fn test_bin_px_get_parse() {
let parser = BinaryParser::new();
let reader = tokio_test::io::Builder::new()
.read(&[GET_PX_BIN, 0x03, 0x69, 0x42, 0x42, 0x69])
.build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
assert_eq!(thingy.unwrap(), Command::GetPixel(3, 0x4269, 0x6942))
}
#[tokio::test]
async fn test_bin_parse_multiple() {
let parser = BinaryParser::new();
let reader = tokio_test::io::Builder::new()
.read(&[
SET_PX_RGB_BIN,
0x01,
0x69,
0x42,
0x42,
0x69,
0x82,
0x00,
0xff,
])
.read(&[
SET_PX_RGBA_BIN,
0x01,
0x69,
0x42,
0x42,
0x69,
0x82,
0x00,
0xff,
0xa0,
])
.build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
let thingy2 = parser.parse(&mut bufreader).await;
assert_eq!(
thingy.unwrap(),
Command::SetPixel(1, 0x4269, 0x6942, Color::RGB24(0x82, 0x00, 0xff))
);
assert_eq!(
thingy2.unwrap(),
Command::SetPixel(1, 0x4269, 0x6942, Color::RGBA32(0x82, 0x00, 0xff, 0xa0))
);
}
}

View file

@ -1,5 +1,7 @@
use std::cell::SyncUnsafeCell;
use crate::{Coordinate, Response};
pub trait Grid<I, V> {
fn get(&self, x: I, y: I) -> Option<&V>;
fn get_unchecked(&self, x: I, y: I) -> &V;
@ -24,10 +26,14 @@ impl<T: Clone> FlutGrid<T> {
cells: vec.into(),
};
}
pub fn get_size(&self) -> (usize, usize) {
return (self.size_x, self.size_y);
}
}
impl<T> FlutGrid<T> {
fn index(&self, x: u16, y: u16) -> Option<usize> {
fn index(&self, x: Coordinate, y: Coordinate) -> Option<usize> {
let x = x as usize;
let y = y as usize;
if x >= self.size_x || y >= self.size_y {
@ -37,22 +43,22 @@ impl<T> FlutGrid<T> {
}
}
impl<T> Grid<u16, T> for FlutGrid<T> {
fn get(&self, x: u16, y: u16) -> Option<&T> {
impl<T> Grid<Coordinate, T> for FlutGrid<T> {
fn get(&self, x: Coordinate, y: Coordinate) -> Option<&T> {
match self.index(x, y) {
None => None,
Some(idx) => Some(unsafe { &(*self.cells.get())[idx] }),
}
}
fn set(&self, x: u16, y: u16, value: T) {
fn set(&self, x: Coordinate, y: Coordinate, value: T) {
match self.index(x, y) {
None => (),
Some(idx) => unsafe { (*self.cells.get())[idx] = value },
}
}
fn get_unchecked(&self, x: u16, y: u16) -> &T {
fn get_unchecked(&self, x: Coordinate, y: Coordinate) -> &T {
let idx = y as usize * self.size_x + x as usize;
return unsafe { &(*self.cells.get())[idx] };
}
@ -81,7 +87,7 @@ mod tests {
#[tokio::test]
async fn test_grid_set() {
let mut grid = FlutGrid::init(3, 3, 0);
let grid = FlutGrid::init(3, 3, 0);
grid.set(1, 1, 255);
grid.set(2, 1, 256);
assert_eq!(grid.cells.into_inner(), vec![0, 0, 0, 0, 255, 256, 0, 0, 0])
@ -89,7 +95,7 @@ mod tests {
#[tokio::test]
async fn test_grid_set_out_of_range() {
let mut grid = FlutGrid::init(3, 3, 0);
let grid = FlutGrid::init(3, 3, 0);
grid.set(1, 1, 255);
grid.set(3, 1, 256);
assert_eq!(grid.cells.into_inner(), vec![0, 0, 0, 0, 255, 0, 0, 0, 0])
@ -97,14 +103,14 @@ mod tests {
#[tokio::test]
async fn test_grid_get() {
let mut grid = FlutGrid::init(3, 3, 0);
let grid = FlutGrid::init(3, 3, 0);
grid.set(1, 2, 222);
assert_eq!(grid.get(1, 2), Some(&222));
}
#[tokio::test]
async fn test_grid_get_out_of_range() {
let mut grid = FlutGrid::init(3, 3, 0);
let grid = FlutGrid::init(3, 3, 0);
grid.set(3, 1, 256);
assert_eq!(grid.get(3, 1), None);
assert_eq!(grid.get(1, 2), Some(&0));
@ -117,7 +123,7 @@ mod tests {
#[bench]
fn bench_set(b: &mut Bencher) {
let mut grid = FlutGrid::init(800, 600, 0 as u32);
let grid = FlutGrid::init(800, 600, 0 as u32);
b.iter(|| {
let x = test::black_box(293);
let y = test::black_box(222);

View file

@ -1,7 +1,9 @@
#![feature(test)]
#![feature(sync_unsafe_cell)]
mod binary_protocol;
mod grid;
mod text_protocol;
use std::{
cell::SyncUnsafeCell,
@ -11,38 +13,50 @@ use std::{
time::Duration,
};
use binary_protocol::BinaryParser;
use grid::{FlutGrid, Grid};
use text_protocol::TextParser;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter},
io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter},
net::TcpListener,
};
extern crate test;
const HELP_TEXT: u8 = 72;
const SIZE_TEXT: u8 = 83;
const PX_TEXT: u8 = 80;
const SIZE_BIN: u8 = 115;
const HELP_BIN: u8 = 104;
const LOCK: u8 = 0;
const GET_PX_BIN: u8 = 32;
const SET_PX_RGB_BIN: u8 = 128;
const SET_PX_RGBA_BIN: u8 = 129;
const SET_PX_W_BIN: u8 = 130;
const SET_PX_RGB_BIN_LENGTH: usize = 8;
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
";
const GRID_LENGTH: usize = 1;
static COUNTER: AtomicU64 = AtomicU64::new(0);
fn set_pixel_rgba(grids: &[grid::FlutGrid<u32>], canvas: u8, x: u16, y: u16, rgb: u32) {
type Canvas = u8;
type Coordinate = u16;
fn set_pixel_rgba(
grids: &[grid::FlutGrid<u32>],
canvas: Canvas,
x: Coordinate,
y: Coordinate,
rgb: u32,
) {
match grids.get(canvas as usize) {
Some(grid) => grid.set(x, y, rgb),
None => (),
}
}
fn get_pixel(grids: &[grid::FlutGrid<u32>], canvas: u8, x: u16, y: u16) -> Option<&u32> {
fn get_pixel(
grids: &[grid::FlutGrid<u32>],
canvas: Canvas,
x: Coordinate,
y: Coordinate,
) -> Option<&u32> {
match grids.get(canvas as usize) {
Some(grid) => return grid.get(x, y),
None => return None,
@ -53,175 +67,52 @@ fn increment_counter() {
COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
async fn process_lock<
R: AsyncReadExt + std::marker::Unpin,
W: AsyncWriteExt + std::marker::Unpin,
>(
reader: &mut R,
_writer: &mut W,
grids: &[grid::FlutGrid<u32>; GRID_LENGTH],
) -> io::Result<()> {
let amount = reader.read_u16_le().await?;
let command = reader.read_u8().await?;
let lockmask = reader.read_u16().await?;
let mut buf = vec![0; lockmask.count_ones() as usize];
let statics = reader.read_exact(&mut buf).await?;
match command {
GET_PX_BIN => todo!("GET pixel lock"),
SET_PX_RGB_BIN => {
let per = SET_PX_RGB_BIN_LENGTH - statics;
let mut j = 0;
let mut a;
let static_buf: Vec<Option<u8>> = (0..SET_PX_RGB_BIN_LENGTH)
.map(|i| match lockmask >> (15 - i) & 1 {
1 => {
let b = Some(buf[j]);
j += 1;
return b;
}
0 => None,
k => panic!("WTF, how is {} not 0 or 1", k),
})
.collect();
let mut mod_buf: Vec<u8> = vec![0; per];
for _ in 0..amount {
a = 0;
let _ = reader.read_exact(&mut mod_buf).await?;
let aa = static_buf
.iter()
.map(|x| *match x {
Some(val) => val,
None => {
let b = mod_buf[a];
a += 1;
return b;
}
})
.map(|z| z)
.collect::<Vec<_>>();
match grids.get(aa[0] as usize) {
Some(grid) => {
grid.set(
u16::from_le_bytes([aa[1], aa[2]]),
u16::from_le_bytes([aa[3], aa[4]]),
u32::from_be_bytes([aa[5], aa[6], aa[7], 0xff]),
);
increment_counter()
}
None => (),
}
}
}
SET_PX_RGBA_BIN => todo!("Set rgba lock"),
SET_PX_W_BIN => todo!("set w lock"),
_ => {
eprintln!("not a cmd");
return Err(Error::from(ErrorKind::InvalidInput));
}
}
return Ok(());
#[derive(Debug, PartialEq)]
enum Color {
RGB24(u8, u8, u8),
RGBA32(u8, u8, u8, u8),
W8(u8),
}
async fn process_msg<
R: AsyncReadExt + std::marker::Unpin,
W: AsyncWriteExt + std::marker::Unpin,
>(
reader: &mut R,
writer: &mut W,
grids: &[grid::FlutGrid<u32>; GRID_LENGTH],
) -> io::Result<()> {
let fst = reader.read_u8().await;
match fst {
Ok(i) => match i {
HELP_TEXT => todo!("HELP command check and message"),
SIZE_TEXT => todo!("SIZE command check and message"),
PX_TEXT => todo!("PX command handling"),
HELP_BIN => todo!("HELP command message"),
SIZE_BIN => todo!("SIZE command check and message"),
LOCK => process_lock(reader, writer, grids).await,
GET_PX_BIN => {
let canvas = reader.read_u8().await?;
let x = reader.read_u16_le().await?;
let y = reader.read_u16_le().await?;
match get_pixel(grids, canvas, x, y) {
None => (),
Some(color) => {
let towrite = &once(GET_PX_BIN)
.chain(once(canvas))
.chain(x.to_le_bytes())
.chain(y.to_le_bytes())
.chain(color.to_be_bytes().into_iter().take(3))
.collect::<Vec<_>>();
writer.write_all(towrite).await?;
}
}
return Ok(());
}
SET_PX_RGB_BIN => set_px_rgb_bin(reader, grids).await,
SET_PX_RGBA_BIN => todo!("SET rgba"),
SET_PX_W_BIN => todo!("SET w"),
_ => {
eprintln!("received illegal command: {}", i);
return Err(Error::from(ErrorKind::InvalidInput));
}
},
Err(err) => {
eprintln!("{}", err);
return Err(err);
}
}
#[derive(Debug, PartialEq)]
enum Protocol {
Text,
Binary,
}
async fn rgb_bin_read<R: AsyncReadExt + std::marker::Unpin>(
reader: &mut R,
) -> io::Result<(u8, u16, u16, u8, u8, u8)> {
let canvas = reader.read_u8().await?;
let x = reader.read_u16_le().await?;
let y = reader.read_u16_le().await?;
let r = reader.read_u8().await?;
let g = reader.read_u8().await?;
let b = reader.read_u8().await?;
return Ok((canvas, x, y, r, g, b));
#[derive(Debug, PartialEq)]
enum Command {
Help,
Size(Canvas),
GetPixel(Canvas, Coordinate, Coordinate),
SetPixel(Canvas, Coordinate, Coordinate, Color),
ChangeCanvas(Canvas),
ChangeProtocol(Protocol),
}
async fn set_px_rgb_bin<R: AsyncReadExt + std::marker::Unpin>(
reader: &mut R,
grids: &[grid::FlutGrid<u32>; GRID_LENGTH],
) -> io::Result<()> {
let (canvas, x, y, r, g, b) = rgb_bin_read(reader).await?;
let rgb = u32::from_be_bytes([r, g, b, 0xff]);
set_pixel_rgba(grids, canvas, x, y, rgb);
increment_counter();
return Ok(());
#[derive(Debug, PartialEq)]
enum Response {
Help,
Size(Coordinate, Coordinate),
GetPixel(Coordinate, Coordinate, [u8; 3]),
}
async fn process_socket<W, R>(
reader: R,
writer: W,
grids: &[grid::FlutGrid<u32>; GRID_LENGTH],
) -> io::Result<()>
trait Parser<R>
where
R: std::marker::Unpin + tokio::io::AsyncBufRead,
{
async fn parse(&self, reader: &mut R) -> io::Result<Command>;
}
trait MEEHHEH {
fn change_canvas(&mut self, canvas: Canvas) -> io::Result<()>;
}
trait Responder<W>
where
W: AsyncWriteExt + std::marker::Unpin,
R: AsyncReadExt + std::marker::Unpin,
{
let mut reader = BufReader::new(reader);
let mut writer = BufWriter::new(writer);
loop {
match process_msg(&mut reader, &mut writer, grids).await {
Ok(()) => {}
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
let _ = writer.flush().await;
return Ok(());
}
Err(e) => {
eprintln!("error with kind {}", e.kind());
return Err(e);
}
}
}
async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()>;
}
async fn listen_handle() {
@ -233,6 +124,144 @@ async fn listen_handle() {
}
}
macro_rules! build_parser_type_enum {
($($name:ident: $t:ty,)*) => {
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<[FlutGrid<u32>]>,
parser: ParserTypes,
}
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?;
return 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(x as Coordinate, y as Coordinate), &mut self.writer).await?);
self.writer.flush().await?;
return 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);
self.writer.flush().await?;
return Ok(());
}
async fn set_pixel_command(
&mut self,
canvas: Canvas,
x: Coordinate,
y: Coordinate,
color: Color,
) -> io::Result<()> {
let c: u32 = match color {
Color::RGB24(r, g, b) => u32::from_be_bytes([r, g, b, 0xff]),
Color::RGBA32(r, g, b, a) => u32::from_be_bytes([r, g, b, a]),
Color::W8(w) => u32::from_be_bytes([w, w, w, 0xff]),
};
println!("setting pixel {},{} to {}", x, y, c);
set_pixel_rgba(self.grids.as_ref(), canvas, x, y, c);
return Ok(());
}
async fn change_canvas_command(&mut self, canvas: Canvas) -> io::Result<()> {
match_parser!(parser: self.parser => parser.change_canvas(canvas))
}
async fn change_protocol(&mut self, protocol: Protocol) -> io::Result<()> {
match protocol {
Protocol::Text => self.parser = ParserTypes::TextParser(TextParser::new(0)),
Protocol::Binary => self.parser = ParserTypes::BinaryParser(BinaryParser::new()),
}
return Ok(());
}
pub fn new(reader: R, writer: W, grids: Arc<[grid::FlutGrid<u32>]>) -> FlutClient<R, W> {
return FlutClient {
reader: BufReader::new(reader),
writer: BufWriter::new(writer),
grids,
parser: ParserTypes::TextParser(TextParser::new(0)),
};
}
pub async fn process_socket(&mut self) -> io::Result<()> {
loop {
let parsed = match &self.parser {
ParserTypes::TextParser(parser) => parser.parse(&mut self.reader).await,
ParserTypes::BinaryParser(parser) => 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).await?
}
Ok(Command::ChangeCanvas(canvas)) => self.change_canvas_command(canvas).await?,
Ok(Command::ChangeProtocol(protocol)) => self.change_protocol(protocol).await?,
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
return Ok(());
}
Err(e) => {
return Err(e);
}
}
}
}
}
#[tokio::main]
async fn main() -> io::Result<()> {
println!("created grids");
@ -242,9 +271,6 @@ async fn main() -> io::Result<()> {
let flut_listener = TcpListener::bind("0.0.0.0:7791").await?;
println!("bound flut listener");
let web_listener = TcpListener::bind("0.0.0.0:7792").await?;
println!("bound web listener");
let _ = tokio::spawn(listen_handle());
loop {
@ -252,7 +278,9 @@ async fn main() -> io::Result<()> {
let grids = grids.clone();
let _ = tokio::spawn(async move {
let (reader, writer) = socket.split();
match process_socket(reader, writer, &grids).await {
let mut connection = FlutClient::new(reader, writer, grids);
let resp = connection.process_socket().await;
match resp {
Ok(()) => return Ok(()),
Err(err) => return Err(err),
}
@ -265,64 +293,4 @@ mod tests {
use super::*;
use crate::grid::FlutGrid;
use tokio_test::assert_ok;
#[tokio::test]
async fn test_set_rgb_bin() {
let mut grids = [FlutGrid::init(800, 600, 0xFF00FF)];
let reader = tokio_test::io::Builder::new()
.read(&[SET_PX_RGB_BIN, 0, 16, 0, 32, 0, 0, 0, 0])
.read(&[SET_PX_RGB_BIN, 0, 16, 0, 33, 0, 2, 3, 5])
.build();
let writer = tokio_test::io::Builder::new().build();
assert_ok!(process_socket(reader, writer, &mut grids).await);
assert_eq!(grids[0].get(16, 32), Some(&0x000000ff));
assert_eq!(grids[0].get(16, 33), Some(&0x020305ff));
}
#[tokio::test]
async fn test_set_rgb_lock() {
let mut grids = [FlutGrid::init(800, 600, 0xFF00FF)];
let reader = tokio_test::io::Builder::new()
.read(&[
LOCK,
3,
0,
SET_PX_RGB_BIN,
0b1011_1110,
0b0000_0000,
0,
0,
0,
0,
2,
3,
])
.read(&[100, 4])
.read(&[101, 5])
.read(&[102, 6])
.build();
let writer = tokio_test::io::Builder::new().build();
assert_ok!(process_socket(reader, writer, &mut grids).await);
assert_eq!(grids[0].get(100, 0), Some(&0x020304ff));
assert_eq!(grids[0].get(101, 0), Some(&0x020305ff));
assert_eq!(grids[0].get(102, 0), Some(&0x020306ff));
}
#[tokio::test]
async fn test_get_rgb_bin() {
let mut grids = [FlutGrid::init(800, 600, 0xFF00F0FF)];
let reader = tokio_test::io::Builder::new()
.read(&[GET_PX_BIN, 0, 15, 0, 21, 0])
.read(&[GET_PX_BIN, 0, 16, 0, 21, 0])
.read(&[GET_PX_BIN, 0, 17, 0, 21, 0])
.build();
let writer = tokio_test::io::Builder::new()
.write(&[GET_PX_BIN, 0, 15, 0, 21, 0, 0xff, 0x00, 0xf0])
.write(&[GET_PX_BIN, 0, 16, 0, 21, 0, 0xff, 0x00, 0xf0])
.write(&[GET_PX_BIN, 0, 17, 0, 21, 0, 0xff, 0x00, 0xf0])
.build();
assert_ok!(process_socket(reader, writer, &mut grids).await);
assert_eq!(grids[0].get(15, 21), Some(&0xff00f0ff));
}
}

234
src/text_protocol.rs Normal file
View file

@ -0,0 +1,234 @@
use std::io::{self, Error, ErrorKind};
use atoi_radix10::parse_from_str;
use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncWriteExt};
use crate::{
Canvas, Color, Command, Coordinate, Parser, Protocol, Responder, Response, GRID_LENGTH,
HELP_TEXT, MEEHHEH,
};
pub struct TextParser {
canvas: Canvas,
}
fn parse_coordinate(string: &str) -> io::Result<Coordinate> {
match parse_from_str(string) {
Ok(coord) => return Ok(coord),
Err(_) => return Err(Error::from(ErrorKind::InvalidInput)),
}
}
fn parse_color(color: &str) -> io::Result<Color> {
if let Ok(bytes) = hex::decode(color) {
match bytes.len() {
1 => return Ok(Color::W8(bytes[0])),
3 => return Ok(Color::RGB24(bytes[0], bytes[1], bytes[2])),
4 => return Ok(Color::RGBA32(bytes[0], bytes[1], bytes[2], bytes[3])),
_ => return Err(Error::from(ErrorKind::InvalidInput)),
}
}
return Err(Error::from(ErrorKind::InvalidInput));
}
impl TextParser {
pub fn new(canvas: Canvas) -> TextParser {
return TextParser { canvas };
}
fn parse_pixel(&self, line: &str) -> io::Result<Command> {
let mut split = line.trim().split(' ');
let _command = split.next().ok_or(Error::from(ErrorKind::InvalidInput))?;
let x_coordinate = split.next().ok_or(Error::from(ErrorKind::InvalidInput))?;
let y_coordinate = split.next().ok_or(Error::from(ErrorKind::InvalidInput))?;
if let (Ok(horizontal), Ok(vertical)) = (x_coordinate.parse(), y_coordinate.parse()) {
match split.next() {
None => return Ok(Command::GetPixel(self.canvas, horizontal, vertical)),
Some(color) => match parse_color(color) {
Ok(color) => {
return Ok(Command::SetPixel(self.canvas, horizontal, vertical, color))
}
Err(err) => return Err(err),
},
}
} else {
return Err(Error::from(ErrorKind::InvalidInput));
}
}
fn parse_canvas(&self, line: &str) -> io::Result<Command> {
let mut split = line.trim().split(' ');
let _command = split.next().ok_or(Error::from(ErrorKind::InvalidInput))?;
let canvas = split.next().ok_or(Error::from(ErrorKind::InvalidInput))?;
println!("{:?}", canvas);
if let Ok(canvas) = canvas.parse() {
return Ok(Command::ChangeCanvas(canvas));
} else {
return Err(Error::from(ErrorKind::InvalidInput));
}
}
fn parse_protocol(&self, line: &str) -> io::Result<Command> {
let mut split = line.trim().split(' ');
let _command = split.next().ok_or(Error::from(ErrorKind::InvalidInput))?;
let protocol = split.next().ok_or(Error::from(ErrorKind::InvalidInput))?;
match protocol {
"binary" => return Ok(Command::ChangeProtocol(Protocol::Binary)),
"text" => return Ok(Command::ChangeProtocol(Protocol::Text)),
_ => return Err(Error::from(ErrorKind::InvalidInput)),
}
}
}
impl<R: AsyncBufRead + AsyncBufReadExt + std::marker::Unpin> Parser<R> for TextParser {
async fn parse(&self, reader: &mut R) -> io::Result<Command> {
let mut line = "".to_string();
if let Ok(_) = reader.read_line(&mut line).await {
println!("{:?}", line);
if line.starts_with("HELP") {
return Ok(Command::Help);
} else if line.starts_with("SIZE") {
return Ok(Command::Size(self.canvas));
} else if line.starts_with("PX ") {
return self.parse_pixel(&line);
} else if line.starts_with("CANVAS ") {
return self.parse_canvas(&line);
} else if line.starts_with("PROTOCOL ") {
return self.parse_protocol(&line);
}
}
return Err(Error::from(ErrorKind::InvalidInput));
}
}
impl MEEHHEH for TextParser {
fn change_canvas(&mut self, canvas: Canvas) -> io::Result<()> {
if (canvas as usize) < GRID_LENGTH {
self.canvas = canvas;
return Ok(());
} else {
return Err(Error::from(ErrorKind::InvalidInput));
}
}
}
impl<W: AsyncWriteExt + std::marker::Unpin> Responder<W> for TextParser {
async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()> {
match response {
Response::Help => writer.write_all(HELP_TEXT).await,
Response::Size(x, y) => {
writer
.write_all(format!("SIZE {} {}\n", x, y).as_bytes())
.await
}
Response::GetPixel(x, y, color) => {
writer
.write_all(format!("PX {} {} {}\n", x, y, hex::encode_upper(color)).as_bytes())
.await
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grid::FlutGrid;
use tokio::io::BufReader;
use tokio_test::assert_ok;
#[tokio::test]
async fn test_help_parse() {
let parser = TextParser::new(0);
let reader = tokio_test::io::Builder::new().read(b"HELP\n").build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
assert_eq!(thingy.unwrap(), Command::Help)
}
#[tokio::test]
async fn test_size_parse() {
let parser = TextParser::new(0);
let reader = tokio_test::io::Builder::new().read(b"SIZE\n").build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
assert_eq!(thingy.unwrap(), Command::Size(0))
}
#[tokio::test]
async fn test_canvas_parse() {
let parser = TextParser::new(0);
let reader = tokio_test::io::Builder::new().read(b"CANVAS 12\n").build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
assert_eq!(thingy.unwrap(), Command::ChangeCanvas(12))
}
#[tokio::test]
async fn test_px_set_w_parse() {
let parser = TextParser::new(0);
let reader = tokio_test::io::Builder::new()
.read(b"PX 28283 29991 81\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(0x81))
)
}
#[tokio::test]
async fn test_px_set_rgb_parse() {
let parser = TextParser::new(0);
let reader = tokio_test::io::Builder::new()
.read(b"PX 28283 29991 8800ff\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, 0xff))
)
}
#[tokio::test]
async fn test_px_set_rgba_parse() {
let parser = TextParser::new(0);
let reader = tokio_test::io::Builder::new()
.read(b"PX 28283 29991 8800ff28\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(0x88, 0x00, 0xff, 0x28))
)
}
#[tokio::test]
async fn test_px_get_parse() {
let parser = TextParser::new(0);
let reader = tokio_test::io::Builder::new()
.read(b"PX 28283 29991\n")
.build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
assert_eq!(thingy.unwrap(), Command::GetPixel(0, 28283, 29991))
}
#[tokio::test]
async fn parse_multiple() {
let parser = TextParser::new(0);
let reader = tokio_test::io::Builder::new()
.read(b"CANVAS 12\n")
.read(b"SIZE\n")
.build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await;
let thingy2 = parser.parse(&mut bufreader).await;
assert_eq!(thingy.unwrap(), Command::ChangeCanvas(12));
assert_eq!(thingy2.unwrap(), Command::Size(0));
}
}