use PixelGrid for screen data
This commit is contained in:
		
							parent
							
								
									bce0c48c4d
								
							
						
					
					
						commit
						d07440f3d0
					
				
					 4 changed files with 207 additions and 176 deletions
				
			
		
							
								
								
									
										2
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -2151,7 +2151,7 @@ dependencies = [ | |||
| [[package]] | ||||
| name = "servicepoint2" | ||||
| version = "0.1.0" | ||||
| source = "git+https://github.com/kaesaecracker/servicepoint.git#c7456f0a67b5488cca777ea702144cd9996698c0" | ||||
| source = "git+https://github.com/kaesaecracker/servicepoint.git#571cf735105c39a11455b83392ce3b863bd590c1" | ||||
| dependencies = [ | ||||
|  "num", | ||||
|  "num-derive", | ||||
|  |  | |||
							
								
								
									
										54
									
								
								src/gui.rs
									
										
									
									
									
								
							
							
						
						
									
										54
									
								
								src/gui.rs
									
										
									
									
									
								
							|  | @ -1,21 +1,34 @@ | |||
| use crate::DISPLAY; | ||||
| use log::{trace, warn}; | ||||
| use pixels::wgpu::TextureFormat; | ||||
| use pixels::{Pixels, PixelsBuilder, SurfaceTexture}; | ||||
| use servicepoint2::{PIXEL_HEIGHT, PIXEL_WIDTH}; | ||||
| use servicepoint2::{PixelGrid, PIXEL_HEIGHT, PIXEL_WIDTH}; | ||||
| use std::sync::mpsc::Receiver; | ||||
| use std::sync::RwLock; | ||||
| use winit::application::ApplicationHandler; | ||||
| use winit::dpi::{LogicalSize, Size}; | ||||
| use winit::event::WindowEvent; | ||||
| use winit::event_loop::ActiveEventLoop; | ||||
| use winit::window::{Window, WindowId}; | ||||
| 
 | ||||
| #[derive(Default)] | ||||
| pub struct App { | ||||
| pub struct App<'t> { | ||||
|     display: &'t RwLock<PixelGrid>, | ||||
|     window: Option<Window>, | ||||
|     pixels: Option<Pixels>, | ||||
|     stop_ui_rx: Receiver<()>, | ||||
| } | ||||
| 
 | ||||
| impl ApplicationHandler for App { | ||||
| impl<'t> App<'t> { | ||||
|     pub fn new(display: &RwLock<PixelGrid>, stop_ui_rx: Receiver<()>) -> App { | ||||
|         App { | ||||
|             display, | ||||
|             stop_ui_rx, | ||||
|             pixels: None, | ||||
|             window: None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ApplicationHandler for App<'_> { | ||||
|     fn resumed(&mut self, event_loop: &ActiveEventLoop) { | ||||
|         let size = Size::from(LogicalSize::new(PIXEL_WIDTH as f64, PIXEL_HEIGHT as f64)); | ||||
|         let attributes = Window::default_attributes() | ||||
|  | @ -47,21 +60,30 @@ impl ApplicationHandler for App { | |||
|                 event_loop.exit(); | ||||
|             } | ||||
|             WindowEvent::RedrawRequested => { | ||||
|                 if self.stop_ui_rx.try_recv().is_ok() { | ||||
|                     warn!("ui thread stopping"); | ||||
|                     event_loop.exit(); | ||||
|                 } | ||||
| 
 | ||||
|                 let window = self.window.as_ref().unwrap(); | ||||
|                 let pixels = self.pixels.as_mut().unwrap(); | ||||
|                 let frame = pixels.frame_mut().chunks_exact_mut(4); | ||||
|                 let mut frame = pixels.frame_mut().chunks_exact_mut(4); | ||||
| 
 | ||||
|                 let mut i = 0; | ||||
|                 for pixel in frame { | ||||
|                     let is_set = unsafe { DISPLAY[i] }; | ||||
|                     let color = if is_set { | ||||
|                         [255u8, 255, 255, 255] | ||||
|                     } else { | ||||
|                         [0u8, 0, 0, 255] | ||||
|                     }; | ||||
|                 let display = self.display.read().unwrap(); | ||||
| 
 | ||||
|                     pixel.copy_from_slice(&color); | ||||
|                     i += 1; | ||||
|                 let size = window.inner_size(); | ||||
|                 for y in 0..size.height { | ||||
|                     for x in 0..size.width { | ||||
|                         let is_set = display.get(x as usize, y as usize); | ||||
|                         let color = if is_set { | ||||
|                             [255u8, 255, 255, 255] | ||||
|                         } else { | ||||
|                             [0u8, 0, 0, 255] | ||||
|                         }; | ||||
| 
 | ||||
|                         let pixel = frame.next().unwrap(); | ||||
|                         pixel.copy_from_slice(&color); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 pixels.render().expect("could not render"); | ||||
|  |  | |||
							
								
								
									
										184
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										184
									
								
								src/main.rs
									
										
									
									
									
								
							|  | @ -2,15 +2,19 @@ | |||
| 
 | ||||
| mod font; | ||||
| mod gui; | ||||
| mod upd_loop; | ||||
| 
 | ||||
| use std::default::Default; | ||||
| 
 | ||||
| use crate::font::BitmapFont; | ||||
| use crate::gui::App; | ||||
| use crate::upd_loop::UdpThread; | ||||
| use clap::Parser; | ||||
| use log::info; | ||||
| use servicepoint2::PIXEL_COUNT; | ||||
| use log::{debug, error, info, warn}; | ||||
| use servicepoint2::{ | ||||
|     Command, Origin, Packet, PixelGrid, Size, Window, PIXEL_HEIGHT, PIXEL_WIDTH, TILE_SIZE, | ||||
| }; | ||||
| use std::io::ErrorKind; | ||||
| use std::net::UdpSocket; | ||||
| use std::sync::{mpsc, RwLock, RwLockWriteGuard}; | ||||
| use std::thread; | ||||
| use std::time::Duration; | ||||
| use winit::event_loop::{ControlFlow, EventLoop}; | ||||
| 
 | ||||
| #[derive(Parser, Debug)] | ||||
|  | @ -19,23 +23,171 @@ struct Cli { | |||
|     bind: String, | ||||
| } | ||||
| 
 | ||||
| static mut DISPLAY: [bool; PIXEL_COUNT] = [false; PIXEL_COUNT]; | ||||
| 
 | ||||
| fn main() { | ||||
|     env_logger::init(); | ||||
| 
 | ||||
|     let cli = Cli::parse(); | ||||
|     info!("starting with args: {:?}", &cli); | ||||
| 
 | ||||
|     let thread = UdpThread::start_new(cli.bind); | ||||
|     let socket = UdpSocket::bind(cli.bind).expect("could not bind socket"); | ||||
|     socket | ||||
|         .set_nonblocking(true) | ||||
|         .expect("could not enter non blocking mode"); | ||||
| 
 | ||||
|     let event_loop = EventLoop::new().expect("could not create event loop"); | ||||
|     event_loop.set_control_flow(ControlFlow::Poll); | ||||
|     let font = BitmapFont::load_file("Web437_IBM_BIOS.woff"); | ||||
| 
 | ||||
|     let mut app = App::default(); | ||||
|     event_loop | ||||
|         .run_app(&mut app) | ||||
|         .expect("could not run event loop"); | ||||
|     let display = PixelGrid::new(PIXEL_WIDTH as usize, PIXEL_HEIGHT as usize); | ||||
|     let display_locked = RwLock::new(display); | ||||
|     let display_locked_ref = &display_locked; | ||||
| 
 | ||||
|     thread.stop_and_wait(); | ||||
|     std::thread::scope(move |scope| { | ||||
|         let (stop_udp_tx, stop_udp_rx) = mpsc::channel(); | ||||
|         let (stop_ui_tx, stop_ui_rx) = mpsc::channel(); | ||||
| 
 | ||||
|         let udp_thread = scope.spawn(move || { | ||||
|             let mut buf = [0; 8985]; | ||||
| 
 | ||||
|             while stop_udp_rx.try_recv().is_err() { | ||||
|                 let (amount, _) = match socket.recv_from(&mut buf) { | ||||
|                     Err(err) if err.kind() == ErrorKind::WouldBlock => { | ||||
|                         thread::sleep(Duration::from_millis(1)); | ||||
|                         continue; | ||||
|                     } | ||||
|                     Ok(result) => result, | ||||
|                     other => other.unwrap(), | ||||
|                 }; | ||||
| 
 | ||||
|                 if amount == buf.len() { | ||||
|                     warn!( | ||||
|                         "the received package may have been truncated to a length of {}", | ||||
|                         amount | ||||
|                     ); | ||||
|                 } | ||||
| 
 | ||||
|                 let vec = buf[..amount].to_vec(); | ||||
|                 let package = servicepoint2::Packet::from(vec); | ||||
| 
 | ||||
|                 let mut display = display_locked_ref.write().unwrap(); | ||||
|                 handle_package(package, &font, &mut display); | ||||
|             } | ||||
| 
 | ||||
|             stop_ui_tx.send(()).expect("could not stop ui thread"); | ||||
|         }); | ||||
| 
 | ||||
|         let mut app = App::new(display_locked_ref, stop_ui_rx); | ||||
| 
 | ||||
|         let event_loop = EventLoop::new().expect("could not create event loop"); | ||||
|         event_loop.set_control_flow(ControlFlow::Poll); | ||||
| 
 | ||||
|         event_loop | ||||
|             .run_app(&mut app) | ||||
|             .expect("could not run event loop"); | ||||
| 
 | ||||
|         stop_udp_tx.send(()).expect("could not stop udp thread"); | ||||
| 
 | ||||
|         udp_thread.join().expect("could not join udp thread"); | ||||
|     }); | ||||
| 
 | ||||
|     fn handle_package( | ||||
|         received: Packet, | ||||
|         font: &BitmapFont, | ||||
|         display: &mut RwLockWriteGuard<PixelGrid>, | ||||
|     ) { | ||||
|         // TODO handle error case
 | ||||
|         let command = match Command::try_from(received) { | ||||
|             Err(err) => { | ||||
|                 warn!("could not read command for packet: {:?}", err); | ||||
|                 return; | ||||
|             } | ||||
|             Ok(val) => val, | ||||
|         }; | ||||
| 
 | ||||
|         match command { | ||||
|             Command::Clear => { | ||||
|                 info!("clearing display"); | ||||
|                 display.fill(false); | ||||
|             } | ||||
|             Command::HardReset => { | ||||
|                 warn!("display shutting down"); | ||||
|                 return; | ||||
|             } | ||||
|             Command::BitmapLinearWin(Origin(x, y), pixels) => { | ||||
|                 print_pixel_grid(x as usize, y as usize, &pixels, display); | ||||
|             } | ||||
|             Command::Cp437Data(window, payload) => { | ||||
|                 print_cp437_data(window, &payload, font, display); | ||||
|             } | ||||
|             Command::BitmapLegacy => { | ||||
|                 warn!("ignoring deprecated command {:?}", command); | ||||
|             } | ||||
|             Command::BitmapLinear(offset, vec) => {} | ||||
|             Command::BitmapLinearAnd(_, _) => {} | ||||
|             Command::BitmapLinearOr(_, _) => {} | ||||
|             Command::BitmapLinearXor(_, _) => {} | ||||
| 
 | ||||
|             Command::FadeOut => {} | ||||
|             Command::CharBrightness(_, _) => {} | ||||
|             Command::Brightness(_) => {} | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     fn check_payload_size(buf: &[u8], expected: usize) -> bool { | ||||
|         let actual = buf.len(); | ||||
|         if actual == expected { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         error!( | ||||
|             "expected a payload length of {} but got {}", | ||||
|             expected, actual | ||||
|         ); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     fn print_cp437_data( | ||||
|         window: Window, | ||||
|         payload: &[u8], | ||||
|         font: &BitmapFont, | ||||
|         display: &mut RwLockWriteGuard<PixelGrid>, | ||||
|     ) { | ||||
|         let Window(Origin(x, y), Size(w, h)) = window; | ||||
|         if !check_payload_size(payload, (w * h) as usize) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         for char_y in 0usize..h as usize { | ||||
|             for char_x in 0usize..w as usize { | ||||
|                 let char_code = payload[char_y * w as usize + char_x]; | ||||
| 
 | ||||
|                 let tile_x = char_x + x as usize; | ||||
|                 let tile_y = char_y + y as usize; | ||||
| 
 | ||||
|                 let bitmap = font.get_bitmap(char_code); | ||||
|                 print_pixel_grid( | ||||
|                     tile_x * TILE_SIZE as usize, | ||||
|                     tile_y * TILE_SIZE as usize, | ||||
|                     bitmap, | ||||
|                     display, | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn print_pixel_grid( | ||||
|         offset_x: usize, | ||||
|         offset_y: usize, | ||||
|         pixels: &PixelGrid, | ||||
|         display: &mut RwLockWriteGuard<PixelGrid>, | ||||
|     ) { | ||||
|         debug!( | ||||
|             "printing {}x{} grid at {offset_x} {offset_y}", | ||||
|             pixels.width, pixels.height | ||||
|         ); | ||||
|         for inner_y in 0..pixels.height { | ||||
|             for inner_x in 0..pixels.width { | ||||
|                 let is_set = pixels.get(inner_x, inner_y); | ||||
|                 display.set(offset_x + inner_x, offset_y + inner_y, is_set); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										143
									
								
								src/upd_loop.rs
									
										
									
									
									
								
							
							
						
						
									
										143
									
								
								src/upd_loop.rs
									
										
									
									
									
								
							|  | @ -1,143 +0,0 @@ | |||
| use crate::font::BitmapFont; | ||||
| use crate::DISPLAY; | ||||
| use log::{debug, error, info, warn}; | ||||
| use servicepoint2::{Command, Origin, Packet, PixelGrid, PIXEL_WIDTH, TILE_SIZE, Window, Size}; | ||||
| use std::io::ErrorKind; | ||||
| use std::net::{ToSocketAddrs, UdpSocket}; | ||||
| use std::sync::mpsc; | ||||
| use std::sync::mpsc::Sender; | ||||
| use std::thread; | ||||
| use std::thread::JoinHandle; | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| pub struct UdpThread { | ||||
|     thread: JoinHandle<()>, | ||||
|     stop_tx: Sender<()>, | ||||
| } | ||||
| 
 | ||||
| impl UdpThread { | ||||
|     pub fn start_new(bind: impl ToSocketAddrs) -> Self { | ||||
|         let (stop_tx, stop_rx) = mpsc::channel(); | ||||
| 
 | ||||
|         let socket = UdpSocket::bind(bind).expect("could not bind socket"); | ||||
|         socket | ||||
|             .set_nonblocking(true) | ||||
|             .expect("could not enter non blocking mode"); | ||||
| 
 | ||||
|         let font = BitmapFont::load_file("Web437_IBM_BIOS.woff"); | ||||
| 
 | ||||
|         let thread = thread::spawn(move || { | ||||
|             let mut buf = [0; 8985]; | ||||
| 
 | ||||
|             while stop_rx.try_recv().is_err() { | ||||
|                 let (amount, _) = match socket.recv_from(&mut buf) { | ||||
|                     Err(err) if err.kind() == ErrorKind::WouldBlock => { | ||||
|                         thread::sleep(Duration::from_millis(1)); | ||||
|                         continue; | ||||
|                     } | ||||
|                     Ok(result) => result, | ||||
|                     other => other.unwrap(), | ||||
|                 }; | ||||
| 
 | ||||
|                 if amount == buf.len() { | ||||
|                     warn!( | ||||
|                         "the received package may have been truncated to a length of {}", | ||||
|                         amount | ||||
|                     ); | ||||
|                 } | ||||
| 
 | ||||
|                 let vec = buf[..amount].to_vec(); | ||||
|                 let package = servicepoint2::Packet::from(vec); | ||||
| 
 | ||||
|                 Self::handle_package(package, &font); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         return Self { stop_tx, thread }; | ||||
|     } | ||||
| 
 | ||||
|     pub fn stop_and_wait(self) { | ||||
|         self.stop_tx.send(()).expect("could not send stop packet"); | ||||
|         self.thread.join().expect("could not wait on udp thread"); | ||||
|     } | ||||
| 
 | ||||
|     fn handle_package(received: Packet, font: &BitmapFont) { | ||||
|         // TODO handle error case
 | ||||
|         let command = Command::try_from(received).unwrap(); | ||||
| 
 | ||||
|         match command { | ||||
|             Command::Clear => { | ||||
|                 info!("clearing display"); | ||||
|                 for v in unsafe { DISPLAY.iter_mut() } { | ||||
|                     *v = false; | ||||
|                 } | ||||
|             } | ||||
|             Command::HardReset => { | ||||
|                 warn!("display shutting down"); | ||||
|                 return; | ||||
|             } | ||||
|             Command::BitmapLinearWin(Origin(x, y), pixels) => { | ||||
|                 Self::print_pixel_grid(x as usize, y as usize, &pixels); | ||||
|             } | ||||
|             Command::Cp437Data(window, payload) => { | ||||
|                 Self::print_cp437_data(window, &payload, font); | ||||
|             } | ||||
|             _ => { | ||||
|                 error!("command {:?} not implemented yet", command); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn check_payload_size(buf: &[u8], expected: usize) -> bool { | ||||
|         let actual = buf.len(); | ||||
|         if actual == expected { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         error!( | ||||
|             "expected a payload length of {} but got {}", | ||||
|             expected, actual | ||||
|         ); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     fn print_cp437_data(window: Window, payload: &[u8], font: &BitmapFont) { | ||||
|         let Window(Origin(x,y), Size(w, h)) = window; | ||||
|         if !UdpThread::check_payload_size(payload, (w * h) as usize) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         for char_y in 0usize..h as usize { | ||||
|             for char_x in 0usize..w as usize { | ||||
|                 let char_code = payload[char_y * w as usize + char_x]; | ||||
| 
 | ||||
|                 let tile_x = char_x + x as usize; | ||||
|                 let tile_y = char_y + y as usize; | ||||
| 
 | ||||
|                 let bitmap = font.get_bitmap(char_code); | ||||
|                 Self::print_pixel_grid( | ||||
|                     tile_x * TILE_SIZE as usize, | ||||
|                     tile_y * TILE_SIZE as usize, | ||||
|                     bitmap, | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn print_pixel_grid(offset_x: usize, offset_y: usize, pixels: &PixelGrid) { | ||||
|         debug!( | ||||
|             "printing {}x{} grid at {offset_x} {offset_y}", | ||||
|             pixels.width, pixels.height | ||||
|         ); | ||||
|         for inner_y in 0..pixels.height { | ||||
|             for inner_x in 0..pixels.width { | ||||
|                 let is_set = pixels.get(inner_x, inner_y); | ||||
|                 let display_index = | ||||
|                     (offset_x + inner_x) + ((offset_y + inner_y) * PIXEL_WIDTH as usize); | ||||
|                 unsafe { | ||||
|                     DISPLAY[display_index] = is_set; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter