mirror of
https://github.com/kaesaecracker/servicepoint-simulator.git
synced 2025-01-18 10:30:14 +01:00
use PixelGrid for screen data
This commit is contained in:
parent
bce0c48c4d
commit
d07440f3d0
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2151,7 +2151,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "servicepoint2"
|
name = "servicepoint2"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/kaesaecracker/servicepoint.git#c7456f0a67b5488cca777ea702144cd9996698c0"
|
source = "git+https://github.com/kaesaecracker/servicepoint.git#571cf735105c39a11455b83392ce3b863bd590c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num",
|
"num",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
|
|
54
src/gui.rs
54
src/gui.rs
|
@ -1,21 +1,34 @@
|
||||||
use crate::DISPLAY;
|
|
||||||
use log::{trace, warn};
|
use log::{trace, warn};
|
||||||
use pixels::wgpu::TextureFormat;
|
use pixels::wgpu::TextureFormat;
|
||||||
use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
|
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::application::ApplicationHandler;
|
||||||
use winit::dpi::{LogicalSize, Size};
|
use winit::dpi::{LogicalSize, Size};
|
||||||
use winit::event::WindowEvent;
|
use winit::event::WindowEvent;
|
||||||
use winit::event_loop::ActiveEventLoop;
|
use winit::event_loop::ActiveEventLoop;
|
||||||
use winit::window::{Window, WindowId};
|
use winit::window::{Window, WindowId};
|
||||||
|
|
||||||
#[derive(Default)]
|
pub struct App<'t> {
|
||||||
pub struct App {
|
display: &'t RwLock<PixelGrid>,
|
||||||
window: Option<Window>,
|
window: Option<Window>,
|
||||||
pixels: Option<Pixels>,
|
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) {
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
let size = Size::from(LogicalSize::new(PIXEL_WIDTH as f64, PIXEL_HEIGHT as f64));
|
let size = Size::from(LogicalSize::new(PIXEL_WIDTH as f64, PIXEL_HEIGHT as f64));
|
||||||
let attributes = Window::default_attributes()
|
let attributes = Window::default_attributes()
|
||||||
|
@ -47,21 +60,30 @@ impl ApplicationHandler for App {
|
||||||
event_loop.exit();
|
event_loop.exit();
|
||||||
}
|
}
|
||||||
WindowEvent::RedrawRequested => {
|
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 window = self.window.as_ref().unwrap();
|
||||||
let pixels = self.pixels.as_mut().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;
|
let display = self.display.read().unwrap();
|
||||||
for pixel in frame {
|
|
||||||
let is_set = unsafe { DISPLAY[i] };
|
|
||||||
let color = if is_set {
|
|
||||||
[255u8, 255, 255, 255]
|
|
||||||
} else {
|
|
||||||
[0u8, 0, 0, 255]
|
|
||||||
};
|
|
||||||
|
|
||||||
pixel.copy_from_slice(&color);
|
let size = window.inner_size();
|
||||||
i += 1;
|
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");
|
pixels.render().expect("could not render");
|
||||||
|
|
184
src/main.rs
184
src/main.rs
|
@ -2,15 +2,19 @@
|
||||||
|
|
||||||
mod font;
|
mod font;
|
||||||
mod gui;
|
mod gui;
|
||||||
mod upd_loop;
|
|
||||||
|
|
||||||
use std::default::Default;
|
|
||||||
|
|
||||||
|
use crate::font::BitmapFont;
|
||||||
use crate::gui::App;
|
use crate::gui::App;
|
||||||
use crate::upd_loop::UdpThread;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use log::info;
|
use log::{debug, error, info, warn};
|
||||||
use servicepoint2::PIXEL_COUNT;
|
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};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
@ -19,23 +23,171 @@ struct Cli {
|
||||||
bind: String,
|
bind: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
static mut DISPLAY: [bool; PIXEL_COUNT] = [false; PIXEL_COUNT];
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
info!("starting with args: {:?}", &cli);
|
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");
|
let font = BitmapFont::load_file("Web437_IBM_BIOS.woff");
|
||||||
event_loop.set_control_flow(ControlFlow::Poll);
|
|
||||||
|
|
||||||
let mut app = App::default();
|
let display = PixelGrid::new(PIXEL_WIDTH as usize, PIXEL_HEIGHT as usize);
|
||||||
event_loop
|
let display_locked = RwLock::new(display);
|
||||||
.run_app(&mut app)
|
let display_locked_ref = &display_locked;
|
||||||
.expect("could not run event loop");
|
|
||||||
|
|
||||||
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…
Reference in a new issue