use crate::{ command_executor::ExecutionResult::{Failure, Shutdown, Success}, cp437_font::Cp437Font, font_renderer::FontRenderer8x8, }; use log::{debug, error, info, trace, warn}; use servicepoint::{ BinaryOperation, BitVecCommand, Bitmap, BitmapCommand, BrightnessGrid, BrightnessGridCommand, CharGridCommand, ClearCommand, CompressionCode, Cp437GridCommand, FadeOutCommand, GlobalBrightnessCommand, HardResetCommand, Origin, TypedCommand, PIXEL_COUNT, PIXEL_WIDTH, TILE_SIZE, }; use std::{ ops::{BitAnd, BitOr, BitXor}, sync::RwLock, }; #[derive(Debug)] pub struct CommandExecutionContext<'t> { display: &'t RwLock, luma: &'t RwLock, cp437_font: Cp437Font, font_renderer: FontRenderer8x8, } #[must_use] pub enum ExecutionResult { Success, Failure, Shutdown, } pub trait CommandExecute { fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult; } impl CommandExecute for ClearCommand { fn execute(&self, context: &CommandExecutionContext) -> ExecutionResult { info!("clearing display"); context.display.write().unwrap().fill(false); Success } } 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 = 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); let x = offset_x + inner_x; let y = offset_y + inner_y; if x >= display.width() || y >= display.height() { error!("stopping pixel grid draw because coordinate {x} {y} is out of bounds"); return Failure; } display.set(x, y, is_set); } } Success } } 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 .window_mut( tile_x * TILE_SIZE, tile_y * TILE_SIZE, TILE_SIZE, TILE_SIZE, ) .unwrap(), ) { error!( "stopping drawing text because char draw failed: {e}" ); return Failure; } } } Success } } impl CommandExecute for GlobalBrightnessCommand { 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(), } } }