split execution of commands
Some checks failed
Rust / build (pull_request) Failing after 1m43s

This commit is contained in:
Vinzenz Schroeter 2025-03-08 15:45:20 +01:00
parent 3264eaa567
commit b307ee689b
3 changed files with 215 additions and 213 deletions

View file

@ -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 as usize + 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(),
}
}
}

View file

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

View file

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