From 9f03b76880bc45c8c25a309e8e54c5d3d3e9ea25 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 03:21:43 +0200 Subject: [PATCH] slower c --- src/main-new.rs | 272 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 +- 2 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 src/main-new.rs diff --git a/src/main-new.rs b/src/main-new.rs new file mode 100644 index 0000000..7af1867 --- /dev/null +++ b/src/main-new.rs @@ -0,0 +1,272 @@ + +use std::fs::File; +use std::io::BufReader; +use std::net::{ToSocketAddrs, UdpSocket}; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::time::Duration; + +// Nix crate for sendmmsg +use nix::sys::socket::{sendmmsg, MsgFlags, SendMmsgData}; +use nix::sys::uio::IoVec; + +// Constants from the C code +const QUEUE_LEN: usize = 1000; +const MSG_PAYLOAD_SIZE: usize = 7 * 160; +const MSGSIZE: usize = 2 + MSG_PAYLOAD_SIZE; + +const DISPLAY_HOST: &str = "100.65.0.2"; +const DISPLAY_PORT: u16 = 5005; +const DISPLAY_WIDTH: i32 = 1920; +const DISPLAY_HEIGHT: i32 = 1080; + +/// Represents the data decoded from a PNG file. +struct PngData { + width: u32, + height: u32, + pixels: Vec, +} + +impl PngData { + /// Loads and decodes a PNG image from the given path. + fn open(path: &str) -> Result { + let file = File::open(path).expect("Failed to open PNG file"); + let decoder = png::Decoder::new(BufReader::new(file)); + let mut reader = decoder.read_info()?; + let mut buf = vec![0; reader.output_buffer_size()]; + let info = reader.next_frame(&mut buf)?; + + Ok(PngData { + width: info.width, + height: info.height, + pixels: buf, + }) + } +} + +/// Represents a bouncing image on the screen. +struct BouncingImage { + img: PngData, + x: i32, + y: i32, + x1: i32, + y1: i32, + x2: i32, + y2: i32, + move_x: i32, + move_y: i32, + rate: u32, +} + +impl BouncingImage { + /// Initializes a new BouncingImage. + fn new(img_file: &str, move_x: i32, move_y: i32, rate: u32, start_x: i32, start_y: i32) -> Self { + let img = PngData::open(img_file).expect("Could not load image"); + let mut bb = BouncingImage { + x2: DISPLAY_WIDTH - img.width as i32, + y2: DISPLAY_HEIGHT - img.height as i32, + img, + x: start_x, + y: start_y, + x1: 0, + y1: 0, + move_x, + move_y, + rate, + }; + if bb.x == -1 { + bb.x = (bb.x1 + bb.x2) / 2; + } + if bb.y == -1 { + bb.y = (bb.y1 + bb.y2) / 2; + } + bb + } + + /// Draws the image and updates its position. + fn draw_and_move(&mut self, display: &mut Display) { + display.draw_png(&self.img, self.x, self.y); + + self.x += self.move_x; + self.y += self.move_y; + + if self.x < self.x1 || self.x > self.x2 { + self.move_x *= -1; + } + if self.y < self.y1 || self.y > self.y2 { + self.move_y *= -1; + } + } +} + +/// Manages the connection and data sent to the display. +struct Display { + fd: RawFd, + bufs: Vec<[u8; MSGSIZE]>, + lens: Vec, // Stores the actual length of data in each buffer + next_buf: usize, // The next buffer in the queue to be filled + send_next: usize, // The next buffer in the queue to be sent + pos_in_buf: usize, + // Keep the socket alive to keep the file descriptor valid + _socket: UdpSocket, +} + +impl Display { + /// Creates a new Display and connects to the specified host and port. + fn new(host: &str, port: u16) -> Self { + let remote_addr = (host, port) + .to_socket_addrs() + .expect("Invalid remote address") + .next() + .expect("Could not resolve host"); + + let socket = UdpSocket::bind("0.0.0.0:0").expect("Could not bind to local port"); + socket.connect(remote_addr).expect("Could not connect to remote"); + let fd = socket.as_raw_fd(); + + let mut bufs = vec![[0; MSGSIZE]; QUEUE_LEN]; + for buf in bufs.iter_mut() { + buf[0] = 0x00; + buf[1] = 0x01; + } + + Display { + fd, + bufs, + lens: vec![0; QUEUE_LEN], + next_buf: 0, + send_next: 0, + pos_in_buf: 0, + _socket: socket, + } + } + + /// Marks the current buffer as ready to be sent and moves to the next one. + fn mark_buffer_ready(&mut self) { + if self.pos_in_buf > 0 { + self.lens[self.next_buf] = 2 + self.pos_in_buf * 7; + self.next_buf = (self.next_buf + 1) % QUEUE_LEN; + self.pos_in_buf = 0; + + // If we've wrapped around and caught up to the send queue, + // we must flush to avoid overwriting data that hasn't been sent. + if self.next_buf == self.send_next { + eprintln!("Warning: Buffer queue full. Forcing a flush."); + self.flush_all_pending(); + } + } + } + + /// Sends all queued packets using the efficient `sendmmsg` syscall. + fn flush_all_pending(&mut self) { + // First, ensure the current, partially-filled buffer is marked as ready. + self.mark_buffer_ready(); + + if self.send_next == self.next_buf { + return; // Nothing to send. + } + + // We build a temporary list of message headers to pass to sendmmsg. + // This is the cleanest way to handle the circular buffer. + let mut iovecs_storage = Vec::new(); + let mut messages_to_send = Vec::new(); + + let mut current_idx = self.send_next; + while current_idx != self.next_buf { + let data_slice = &self.bufs[current_idx][..self.lens[current_idx]]; + iovecs_storage.push(IoVec::from_slice(data_slice)); + current_idx = (current_idx + 1) % QUEUE_LEN; + } + + // Since we used `connect()`, the kernel knows the destination address, + // so we can pass `None` for the address in `SendMmsgData`. + for iov in &iovecs_storage { + messages_to_send.push(SendMmsgData { + iov: &[*iov], + addr: None, + cmsgs: &[], + _phantom: std::marker::PhantomData, + }); + } + + if messages_to_send.is_empty() { + return; + } + + // Perform the `sendmmsg` syscall + match sendmmsg(self.fd, &messages_to_send, MsgFlags::empty()) { + Ok(num_sent) => { + // Advance the send queue by the number of packets actually sent. + self.send_next = (self.send_next + num_sent) % QUEUE_LEN; + } + Err(e) => { + // Non-blocking sockets might return an error indicating to try again. + // For this example, we'll just log other errors. + if e != nix::errno::Errno::EAGAIN && e != nix::errno::Errno::EWOULDBLOCK { + eprintln!("Failed to send messages with sendmmsg: {}", e); + } + } + } + } + + /// Sets a pixel color at a specific coordinate. + fn set_pixel(&mut self, x: u16, y: u16, r: u8, g: u8, b: u8) { + let offset = 2 + self.pos_in_buf * 7; + let buf = &mut self.bufs[self.next_buf][offset..offset + 7]; + buf[0] = x as u8; + buf[1] = (x >> 8) as u8; + buf[2] = y as u8; + buf[3] = (y >> 8) as u8; + buf[4] = r; + buf[5] = g; + buf[6] = b; + + self.pos_in_buf += 1; + if self.pos_in_buf == 160 { + self.mark_buffer_ready(); + } + } + + /// Draws a PNG image at the given coordinates. + fn draw_png(&mut self, png: &PngData, x: i32, y: i32) { + for sy in 0..png.height { + for sx in 0..png.width { + let index = (sy * png.width + sx) as usize * 4; + let rgba = &png.pixels[index..index + 4]; + if rgba[3] > 0 { // Check alpha channel + self.set_pixel((x + sx as i32) as u16, (y + sy as i32) as u16, rgba[0], rgba[1], rgba[2]); + } + } + } + } +} + +fn main() { + let mut images = vec![ + BouncingImage::new("images/unicorn_cc.png", 13, -10, 1, -1, -1), + BouncingImage::new("images/windows_logo.png", -8, 3, 2, -1, -1), + BouncingImage::new("images/spade.png", 32, -12, 1, 0, 0), + BouncingImage::new("images/dvdvideo.png", 20, 6, 5, 1000, 800), + BouncingImage::new("images/hackaday.png", 40, 18, 3, 500, 800), + ]; + + let mut display = Display::new(DISPLAY_HOST, DISPLAY_PORT); + let mut frame_counter: u32 = 0; + + loop { + for bb in images.iter_mut() { + if bb.rate > 0 && frame_counter % bb.rate != 0 { + continue; + } + bb.draw_and_move(&mut display); + } + + // Send all queued packets for this frame in a single batch. + display.flush_all_pending(); + + frame_counter += 1; + + // A small delay to control the frame rate (approx 60 FPS). + std::thread::sleep(Duration::from_millis(16)); + } +} + diff --git a/src/main.rs b/src/main.rs index 2282df0..66ae2db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -188,7 +188,7 @@ impl Drawable for Circle { /// Helper method to draw the 8 symmetric points for a given (x, y) offset. fn draw_and_move(&mut self, display: &mut Display,tick:u32) { let hsv_color = color::Hsv { - h: (tick%360).try_into().unwrap(), + h: ((tick/20)%360).try_into().unwrap(), s: 1.0, v: 1.0, };