slower c
This commit is contained in:
		
							parent
							
								
									3a957a1c20
								
							
						
					
					
						commit
						9f03b76880
					
				
					 2 changed files with 273 additions and 1 deletions
				
			
		
							
								
								
									
										272
									
								
								src/main-new.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								src/main-new.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<u8>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PngData {
 | 
			
		||||
    /// Loads and decodes a PNG image from the given path.
 | 
			
		||||
    fn open(path: &str) -> Result<Self, png::DecodingError> {
 | 
			
		||||
        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<usize>, // 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));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue