diff --git a/src/command_executor.rs b/src/command_executor.rs index 974d3b6..8ddeaab 100644 --- a/src/command_executor.rs +++ b/src/command_executor.rs @@ -5,10 +5,10 @@ use crate::{ }; use log::{debug, error, info, trace, warn}; use servicepoint::{ - BinaryOperation, BitVec, BitVecCommand, Bitmap, BitmapCommand, - BrightnessCommand, BrightnessGrid, BrightnessGridCommand, CharGrid, - CharGridCommand, Cp437Grid, Cp437GridCommand, Grid, Offset, Origin, Tiles, - TypedCommand, PIXEL_COUNT, PIXEL_WIDTH, TILE_SIZE, + BinaryOperation, BitVecCommand, Bitmap, BitmapCommand, BrightnessCommand, + BrightnessGrid, BrightnessGridCommand, CharGridCommand, ClearCommand, + CompressionCode, Cp437GridCommand, FadeOutCommand, Grid, HardResetCommand, + Origin, TypedCommand, PIXEL_COUNT, PIXEL_WIDTH, TILE_SIZE, }; use std::{ ops::{BitAnd, BitOr, BitXor}, @@ -16,7 +16,7 @@ use std::{ }; #[derive(Debug)] -pub struct CommandExecutor<'t> { +pub struct CommandExecutionContext<'t> { display: &'t RwLock, luma: &'t RwLock, cp437_font: Cp437Font, @@ -30,210 +30,36 @@ pub enum ExecutionResult { Shutdown, } -impl<'t> CommandExecutor<'t> { - pub fn new( - display: &'t RwLock, - luma: &'t RwLock, - font_renderer: FontRenderer8x8, - ) -> Self { - CommandExecutor { - display, - luma, - font_renderer, - cp437_font: Cp437Font::default(), - } - } +pub trait CommandExecute { + fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult; +} - pub(crate) fn execute(&self, command: TypedCommand) -> ExecutionResult { - debug!("received {command:?}"); - match command { - TypedCommand::Clear(_) => { - info!("clearing display"); - self.display.write().unwrap().fill(false); - Success - } - TypedCommand::HardReset(_) => { - warn!("display shutting down"); - Shutdown - } - TypedCommand::Bitmap(BitmapCommand { - origin: Origin { x, y, .. }, - bitmap, - .. - }) => self.print_pixel_grid(x, y, &bitmap), - TypedCommand::Cp437Grid(Cp437GridCommand { origin, grid }) => { - self.print_cp437_data(origin, &grid) - } - #[allow(deprecated)] - TypedCommand::BitmapLegacy(_) => { - warn!("ignoring deprecated command {:?}", command); - Failure - } - TypedCommand::BitVec(command) => { - let BitVecCommand { - offset, - bitvec, - operation, - .. - } = command; - fn overwrite(_: bool, new: bool) -> bool { - new - } - let operation = match operation { - BinaryOperation::Overwrite => overwrite, - BinaryOperation::And => BitAnd::bitand, - BinaryOperation::Or => BitOr::bitor, - BinaryOperation::Xor => BitXor::bitxor, - }; - self.execute_bitmap_linear(offset, bitvec, operation) - } - TypedCommand::BrightnessGrid(BrightnessGridCommand { - origin, - grid, - }) => self.execute_char_brightness(origin, grid), - TypedCommand::Brightness(BrightnessCommand { brightness }) => { - self.luma.write().unwrap().fill(brightness); - Success - } - TypedCommand::FadeOut(_) => { - error!("command not implemented: {command:?}"); - Success - } - TypedCommand::CharGrid(CharGridCommand { origin, grid }) => { - self.print_utf8_data(origin, &grid) - } - } - } - - fn execute_char_brightness( - &self, - origin: Origin, - grid: BrightnessGrid, - ) -> ExecutionResult { - let mut luma = self.luma.write().unwrap(); - for inner_y in 0..grid.height() { - for inner_x in 0..grid.width() { - let brightness = grid.get(inner_x, inner_y); - luma.set(origin.x + inner_x, origin.y + inner_y, brightness); - } - } +impl CommandExecute for ClearCommand { + fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult { + info!("clearing display"); + context.display.write().unwrap().fill(false); Success } +} - fn execute_bitmap_linear( - &self, - offset: Offset, - vec: BitVec, - op: Op, - ) -> ExecutionResult - where - Op: Fn(bool, bool) -> bool, - { - if !Self::check_bitmap_valid(offset as u16, vec.len()) { - return Failure; - } - let mut display = self.display.write().unwrap(); - for bitmap_index in 0..vec.len() { - let (x, y) = Self::get_coordinates_for_index(offset, bitmap_index); - let old_value = display.get(x, y); - display.set(x, y, op(old_value, vec[bitmap_index])); - } - Success - } - - fn check_bitmap_valid(offset: u16, payload_len: usize) -> bool { - if offset as usize + payload_len > PIXEL_COUNT { - error!( - "bitmap with offset {offset} is too big ({payload_len} bytes)" - ); - return false; - } - - true - } - - fn print_cp437_data( - &self, - origin: Origin, - grid: &Cp437Grid, - ) -> ExecutionResult { - let font = &self.cp437_font; - let Origin { x, y, .. } = origin; - for char_y in 0usize..grid.height() { - for char_x in 0usize..grid.width() { - let char_code = grid.get(char_x, char_y); - trace!( - "drawing char_code {char_code:#04x} (if this was UTF-8, it would be {})", - char::from(char_code) - ); - - let tile_x = char_x + x; - let tile_y = char_y + y; - - match self.print_pixel_grid( - tile_x * TILE_SIZE, - tile_y * TILE_SIZE, - &font[char_code], - ) { - Success => {} - Failure => { - error!( - "stopping drawing text because char draw failed" - ); - return Failure; - } - Shutdown => return Shutdown, - } - } - } - - Success - } - - fn print_utf8_data( - &self, - origin: Origin, - grid: &CharGrid, - ) -> ExecutionResult { - let mut display = self.display.write().unwrap(); - - let Origin { x, y, .. } = origin; - for char_y in 0usize..grid.height() { - for char_x in 0usize..grid.width() { - let char = grid.get(char_x, char_y); - trace!("drawing {char}"); - - let tile_x = char_x + x; - let tile_y = char_y + y; - - if let Err(e) = self.font_renderer.render( - char, - &mut display, - Origin::new(tile_x * TILE_SIZE, tile_y * TILE_SIZE), - ) { - error!( - "stopping drawing text because char draw failed: {e}" - ); - return Failure; - } - } - } - - Success - } - - fn print_pixel_grid( - &self, - offset_x: usize, - offset_y: usize, - pixels: &Bitmap, - ) -> ExecutionResult { +impl CommandExecute for BitmapCommand { + fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult { + let Self { + origin: + Origin { + x: offset_x, + y: offset_y, + .. + }, + bitmap: pixels, + .. + } = self; debug!( "printing {}x{} grid at {offset_x} {offset_y}", pixels.width(), pixels.height() ); - let mut display = self.display.write().unwrap(); + let mut display = context.display.write().unwrap(); for inner_y in 0..pixels.height() { for inner_x in 0..pixels.width() { let is_set = pixels.get(inner_x, inner_y); @@ -251,12 +77,186 @@ impl<'t> CommandExecutor<'t> { Success } +} - fn get_coordinates_for_index( - offset: usize, - index: usize, - ) -> (usize, usize) { - let pixel_index = offset + index; - (pixel_index % PIXEL_WIDTH, pixel_index / PIXEL_WIDTH) +impl CommandExecute for HardResetCommand { + fn execute(&self, _: &CommandExecutionContext) -> ExecutionResult { + warn!("display shutting down"); + Shutdown + } +} + +impl CommandExecute for BitVecCommand { + fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult { + let BitVecCommand { + offset, + bitvec, + operation, + .. + } = self; + fn overwrite(_: bool, new: bool) -> bool { + new + } + let operation = match operation { + BinaryOperation::Overwrite => overwrite, + BinaryOperation::And => BitAnd::bitand, + BinaryOperation::Or => BitOr::bitor, + BinaryOperation::Xor => BitXor::bitxor, + }; + + if self.offset + bitvec.len() > PIXEL_COUNT { + error!( + "bitmap with offset {offset} is too big ({} bytes)", + bitvec.len() + ); + return Failure; + } + + let mut display = context.display.write().unwrap(); + for bitmap_index in 0..bitvec.len() { + let pixel_index = offset + bitmap_index; + let (x, y) = (pixel_index % PIXEL_WIDTH, pixel_index / PIXEL_WIDTH); + let old_value = display.get(x, y); + display.set(x, y, operation(old_value, bitvec[bitmap_index])); + } + Success + } +} + +impl CommandExecute for Cp437GridCommand { + fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult { + let Cp437GridCommand { origin, grid } = self; + let Origin { x, y, .. } = origin; + for char_y in 0usize..grid.height() { + for char_x in 0usize..grid.width() { + let char_code = grid.get(char_x, char_y); + trace!( + "drawing char_code {char_code:#04x} (if this was UTF-8, it would be {})", + char::from(char_code) + ); + + let tile_x = char_x + x; + let tile_y = char_y + y; + + let execute_result = BitmapCommand { + origin: Origin::new(tile_x * TILE_SIZE, tile_y * TILE_SIZE), + bitmap: context.cp437_font[char_code].clone(), + compression: CompressionCode::default(), + } + .execute(context); + match execute_result { + Success => {} + Failure => { + error!( + "stopping drawing text because char draw failed" + ); + return Failure; + } + Shutdown => return Shutdown, + } + } + } + + Success + } +} + +#[allow(deprecated)] +impl CommandExecute for servicepoint::BitmapLegacyCommand { + fn execute(&self, _: &CommandExecutionContext) -> ExecutionResult { + warn!("ignoring deprecated command {:?}", self); + Failure + } +} + +impl CommandExecute for BrightnessGridCommand { + fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult { + let BrightnessGridCommand { origin, grid } = self; + let mut luma = context.luma.write().unwrap(); + for inner_y in 0..grid.height() { + for inner_x in 0..grid.width() { + let brightness = grid.get(inner_x, inner_y); + luma.set(origin.x + inner_x, origin.y + inner_y, brightness); + } + } + Success + } +} + +impl CommandExecute for CharGridCommand { + fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult { + let CharGridCommand { origin, grid } = self; + let mut display = context.display.write().unwrap(); + + let Origin { x, y, .. } = origin; + for char_y in 0usize..grid.height() { + for char_x in 0usize..grid.width() { + let char = grid.get(char_x, char_y); + trace!("drawing {char}"); + + let tile_x = char_x + x; + let tile_y = char_y + y; + + if let Err(e) = context.font_renderer.render( + char, + &mut display, + Origin::new(tile_x * TILE_SIZE, tile_y * TILE_SIZE), + ) { + error!( + "stopping drawing text because char draw failed: {e}" + ); + return Failure; + } + } + } + + Success + } +} + +impl CommandExecute for BrightnessCommand { + fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult { + context.luma.write().unwrap().fill(self.brightness); + Success + } +} + +impl CommandExecute for FadeOutCommand { + fn execute(&self, _: &CommandExecutionContext) -> ExecutionResult { + error!("command not implemented: {self:?}"); + Success + } +} + +impl CommandExecute for TypedCommand { + fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult { + match self { + TypedCommand::Clear(command) => command.execute(context), + TypedCommand::HardReset(command) => command.execute(context), + TypedCommand::Bitmap(command) => command.execute(context), + TypedCommand::Cp437Grid(command) => command.execute(context), + #[allow(deprecated)] + TypedCommand::BitmapLegacy(command) => command.execute(context), + TypedCommand::BitVec(command) => command.execute(context), + TypedCommand::BrightnessGrid(command) => command.execute(context), + TypedCommand::Brightness(command) => command.execute(context), + TypedCommand::FadeOut(command) => command.execute(context), + TypedCommand::CharGrid(command) => command.execute(context), + } + } +} + +impl<'t> CommandExecutionContext<'t> { + pub fn new( + display: &'t RwLock, + luma: &'t RwLock, + font_renderer: FontRenderer8x8, + ) -> Self { + CommandExecutionContext { + display, + luma, + font_renderer, + cp437_font: Cp437Font::default(), + } } } diff --git a/src/main.rs b/src/main.rs index 7218896..dd7f3e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use crate::font_renderer::FontRenderer8x8; use crate::udp_server::UdpServer; -use crate::{command_executor::CommandExecutor, gui::Gui}; +use crate::{command_executor::CommandExecutionContext, gui::Gui}; use clap::Parser; use cli::Cli; use log::{info, LevelFilter}; @@ -39,7 +39,7 @@ fn main() { .font .map(FontRenderer8x8::from_name) .unwrap_or_else(FontRenderer8x8::default); - let command_executor = CommandExecutor::new(&display, &luma, font_renderer); + let command_executor = CommandExecutionContext::new(&display, &luma, font_renderer); let mut udp_server = UdpServer::new( cli.bind, stop_udp_rx, diff --git a/src/udp_server.rs b/src/udp_server.rs index 7f35761..b661067 100644 --- a/src/udp_server.rs +++ b/src/udp_server.rs @@ -1,8 +1,9 @@ +use crate::command_executor::CommandExecute; use crate::{ - command_executor::{CommandExecutor, ExecutionResult}, + command_executor::{CommandExecutionContext, ExecutionResult}, gui::AppEvents, }; -use log::{error, warn}; +use log::{debug, error, warn}; use servicepoint::TypedCommand; use std::{ io::ErrorKind, net::UdpSocket, sync::mpsc::Receiver, time::Duration, @@ -15,7 +16,7 @@ const BUF_SIZE: usize = 8985; pub struct UdpServer<'t> { socket: UdpSocket, stop_rx: Receiver<()>, - command_executor: CommandExecutor<'t>, + command_executor: CommandExecutionContext<'t>, app_events: EventLoopProxy, buf: [u8; BUF_SIZE], } @@ -24,7 +25,7 @@ impl<'t> UdpServer<'t> { pub fn new( bind: String, stop_rx: Receiver<()>, - command_executor: CommandExecutor<'t>, + command_executor: CommandExecutionContext<'t>, app_events: EventLoopProxy, ) -> Self { let socket = UdpSocket::bind(bind).expect("could not bind socket"); @@ -46,7 +47,8 @@ impl<'t> UdpServer<'t> { if let Some(cmd) = self.receive_into_buf().and_then(|amount| { Self::command_from_slice(&self.buf[..amount]) }) { - match self.command_executor.execute(cmd) { + debug!("received {cmd:?}"); + match cmd.execute(&self.command_executor) { ExecutionResult::Success => { self.app_events .send_event(AppEvents::UdpPacketHandled)