split execution of commands
This commit is contained in:
		
							parent
							
								
									3264eaa567
								
							
						
					
					
						commit
						4d3ea24e3d
					
				
					 3 changed files with 215 additions and 213 deletions
				
			
		|  | @ -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<Bitmap>, | ||||
|     luma: &'t RwLock<BrightnessGrid>, | ||||
|     cp437_font: Cp437Font, | ||||
|  | @ -30,210 +30,36 @@ pub enum ExecutionResult { | |||
|     Shutdown, | ||||
| } | ||||
| 
 | ||||
| impl<'t> CommandExecutor<'t> { | ||||
|     pub fn new( | ||||
|         display: &'t RwLock<Bitmap>, | ||||
|         luma: &'t RwLock<BrightnessGrid>, | ||||
|         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<Tiles>, | ||||
|         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<Op>( | ||||
|         &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<Tiles>, | ||||
|         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<Tiles>, | ||||
|         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<Bitmap>, | ||||
|         luma: &'t RwLock<BrightnessGrid>, | ||||
|         font_renderer: FontRenderer8x8, | ||||
|     ) -> Self { | ||||
|         CommandExecutionContext { | ||||
|             display, | ||||
|             luma, | ||||
|             font_renderer, | ||||
|             cp437_font: Cp437Font::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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<AppEvents>, | ||||
|     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<AppEvents>, | ||||
|     ) -> 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) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter