From 970bb5187a59011a63f886bf8e2078c039268f58 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Fri, 18 Jul 2025 22:17:22 +0200 Subject: [PATCH 01/24] 1 image --- src/main.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index 23bc058..3032b68 100644 --- a/src/main.rs +++ b/src/main.rs @@ -167,26 +167,11 @@ impl Display { } } } - - /// Clears the entire screen to black. - #[allow(dead_code)] - fn blank_screen(&mut self) { - for x in 0..DISPLAY_WIDTH { - for y in 0..DISPLAY_HEIGHT { - self.set_pixel(x as u16, y as u16, 0, 0, 0); - } - } - self.flush_frame(); - } } 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); From 57880ef70a42324f57bab7b1a18e328fdf29de53 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Fri, 18 Jul 2025 22:53:57 +0200 Subject: [PATCH 02/24] begin queu method --- src/buffer.rs | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 12 ++++- 2 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 src/buffer.rs diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..33f3877 --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,132 @@ +use std::sync::{Arc, Condvar, Mutex}; +use std::thread; +use std::time::Duration; +// Using MaybeUninit is the idiomatic way to handle a buffer +// of potentially uninitialized items without requiring a Default trait. +use std::mem::MaybeUninit; + +/// A fixed-capacity, overwriting ring buffer. +pub struct RingBuffer { + buffer: [MaybeUninit; N], + head: usize, + tail: usize, + size: usize, +} + +impl RingBuffer { + /// Creates a new, empty RingBuffer. + pub fn new() -> Self { + Self { + // This is a safe way to create an array of uninitialized data. + buffer: unsafe { MaybeUninit::uninit().assume_init() }, + head: 0, + tail: 0, + size: 0, + } + } + + /// Checks if the buffer is empty. + pub fn is_empty(&self) -> bool { + self.size == 0 + } + + /// Pushes an item into the buffer. + /// If the buffer is full, the oldest item is overwritten. + pub fn push(&mut self, item: T) { + // Write the item to the tail position. + // This is safe because we manage initialization with `size`. + self.buffer[self.tail].write(item); + self.tail = (self.tail + 1) % N; + + if self.size < N { + // Buffer is not full, just increment size. + self.size += 1; + } else { + // Buffer was full. The push overwrote the oldest item. + // The head must also advance to the new oldest item. + self.head = (self.head + 1) % N; + } + } + + /// Pops an item from the buffer. + /// Returns None if the buffer is empty. + pub fn pop(&mut self) -> Option { + if self.is_empty() { + return None; + } + + // Read the item from the head, leaving that slot uninitialized. + // This is safe because is_empty() check ensures `head` points to valid data. + let item = unsafe { self.buffer[self.head].assume_init_read() }; + self.head = (self.head + 1) % N; + self.size -= 1; + Some(item) + } +} + +fn main() { + const CAPACITY: usize = 5; + println!("Ring Buffer Capacity: {}", CAPACITY); + println!("Producer will produce 15 items, so overwrites are expected."); + + // The state is shared between threads using an Arc (Atomic Reference Counter). + // The Mutex ensures exclusive access, and the Condvar allows threads to wait efficiently. + let pair = Arc::new(( + Mutex::new(RingBuffer::::new()), + Condvar::new(), + )); + + // --- Producer Thread --- + let producer_pair = Arc::clone(&pair); + let producer_handle = thread::spawn(move || { + for i in 0..15 { + // Lock the mutex to get exclusive access to the buffer. + let (lock, cvar) = &*producer_pair; + let mut buffer = lock.lock().unwrap(); + + buffer.push(i); + println!("➡️ Produced: {}", i); + + // This is crucial: after adding an item, we notify one waiting thread. + cvar.notify_one(); + + // We don't need the lock anymore, so we can drop it explicitly + // before sleeping to allow the consumer to work. + drop(buffer); + thread::sleep(Duration::from_millis(100)); // Producer is fast + } + println!("✅ Producer finished."); + }); + + // --- Consumer Thread --- + let consumer_pair = Arc::clone(&pair); + let consumer_handle = thread::spawn(move || { + let mut items_processed = 0; + while items_processed < 15 { + let (lock, cvar) = &*consumer_pair; + let mut buffer = lock.lock().unwrap(); + + // Use a while loop to handle spurious wakeups. + // The thread will sleep until the buffer is no longer empty. + while buffer.is_empty() { + // `cvar.wait` atomically unlocks the mutex and waits. + // When woken, it re-locks the mutex before returning. + buffer = cvar.wait(buffer).unwrap(); + } + + // At this point, the buffer is not empty. + if let Some(item) = buffer.pop() { + println!(" Consumed: {}", item); + items_processed += 1; + } + + drop(buffer); + thread::sleep(Duration::from_millis(300)); // Consumer is slow + } + println!("✅ Consumer finished."); + }); + + producer_handle.join().unwrap(); + consumer_handle.join().unwrap(); +} + diff --git a/src/main.rs b/src/main.rs index 3032b68..d5139b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ +mod buffer; +use crate::buffer::{RingBuffer}; use std::fs::File; use std::io::BufReader; use std::net::{ToSocketAddrs, UdpSocket}; use std::time::Duration; +use std::sync::{Arc, Condvar, Mutex}; // Constants from the C code const QUEUE_LEN: usize = 1000; @@ -96,6 +99,7 @@ impl BouncingImage { struct Display { socket: UdpSocket, bufs: Vec<[u8; MSGSIZE]>, + pair: Arc<(Mutex>,Condvar)>, next_buf: usize, pos_in_buf: usize, } @@ -117,10 +121,14 @@ impl Display { buf[0] = 0x00; buf[1] = 0x01; } - + let pair = Arc::new(( + Mutex::new(RingBuffer::::new()), + Condvar::new(), + )); Display { socket, bufs, + pair, next_buf: 0, pos_in_buf: 0, } @@ -188,7 +196,7 @@ fn main() { } display.flush_frame(); frame_counter += 1; - + println!("test"); // A small delay to control the frame rate std::thread::sleep(Duration::from_millis(16)); } From c9d179c33f49d568b18938e1028c51e616781ca7 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Fri, 18 Jul 2025 23:03:45 +0200 Subject: [PATCH 03/24] threading --- src/main.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index d5139b8..3e56a03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use std::io::BufReader; use std::net::{ToSocketAddrs, UdpSocket}; use std::time::Duration; use std::sync::{Arc, Condvar, Mutex}; +use std::thread; // Constants from the C code const QUEUE_LEN: usize = 1000; @@ -175,6 +176,34 @@ impl Display { } } } + fn send_thread(&mut self) { + let consumer_pair = Arc::clone(&self.pair); + let consumer_handle = thread::spawn(move || { + let mut items_processed = 0; + loop { + let (lock, cvar) = &*consumer_pair; + let mut buffer = lock.lock().unwrap(); + + // Use a while loop to handle spurious wakeups. + // The thread will sleep until the buffer is no longer empty. + while buffer.is_empty() { + // `cvar.wait` atomically unlocks the mutex and waits. + // When woken, it re-locks the mutex before returning. + buffer = cvar.wait(buffer).unwrap(); + } + + // At this point, the buffer is not empty. + if let Some(item) = buffer.pop() { + println!(" Consumed: {}", item); + items_processed += 1; + } + + drop(buffer); + thread::sleep(Duration::from_millis(300)); // Consumer is slow + } + }); + + } } fn main() { @@ -185,7 +214,7 @@ fn main() { let mut display = Display::new(DISPLAY_HOST, DISPLAY_PORT); let mut frame_counter: u32 = 0; - // display.blank_screen(); + display.send_thread(); loop { for (i, bb) in images.iter_mut().enumerate() { From 24492eb15809494953f241c69e60fde42e31933a Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Fri, 18 Jul 2025 23:48:41 +0200 Subject: [PATCH 04/24] drawable --- src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 23bc058..65b2fbc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,6 +51,12 @@ struct BouncingImage { rate: u32, } +trait Drawable { + // Associated function signature; `Self` refers to the implementor type. + + fn draw_and_move(&mut self, display: &mut Display); + +} 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 { @@ -75,7 +81,8 @@ impl BouncingImage { } bb } - +} +impl Drawable for BouncingImage { /// 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); From 5ba778e8947e6f348cfadb404e19b90b0e63f0ca Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 01:25:09 +0200 Subject: [PATCH 05/24] move drawable --- src/main.rs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index 65b2fbc..b030c7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,6 +56,7 @@ trait Drawable { fn draw_and_move(&mut self, display: &mut Display); + fn draw_png(&mut self, display: &mut Display, x: i32, y: i32); } impl BouncingImage { /// Initializes a new BouncingImage. @@ -81,11 +82,29 @@ impl BouncingImage { } bb } + + + + } impl Drawable for BouncingImage { + + /// Draws a PNG image at the given coordinates. + fn draw_png(&mut self, display: &mut Display, x: i32, y: i32) { + + for sy in 0..self.img.height { + for sx in 0..self.img.width { + let index = (sy * self.img.width + sx) as usize * 4; + let rgba = &self.img.pixels[index..index + 4]; + if rgba[3] > 0 { // Check alpha channel + display.set_pixel((x + sx as i32) as u16, (y + sy as i32) as u16, rgba[0], rgba[1], rgba[2]); + } + } + } + } /// 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.draw_png(display, self.x, self.y); self.x += self.move_x; self.y += self.move_y; @@ -161,19 +180,7 @@ impl Display { self.flush_frame(); } } - - /// 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]); - } - } - } - } + /// Clears the entire screen to black. #[allow(dead_code)] From ca1a207c9944b754cd93475f435291f5ba5ef246 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 01:37:51 +0200 Subject: [PATCH 06/24] trait --- src/main.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index b030c7d..f27aa71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,7 +53,7 @@ struct BouncingImage { trait Drawable { // Associated function signature; `Self` refers to the implementor type. - + fn rate(&self) -> u32; fn draw_and_move(&mut self, display: &mut Display); fn draw_png(&mut self, display: &mut Display, x: i32, y: i32); @@ -88,7 +88,9 @@ impl BouncingImage { } impl Drawable for BouncingImage { - + fn rate(&self) -> u32 { + return self.rate; + } /// Draws a PNG image at the given coordinates. fn draw_png(&mut self, display: &mut Display, x: i32, y: i32) { @@ -195,12 +197,12 @@ impl Display { } 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 images:Vec> = vec![ + Box::new(BouncingImage::new("images/unicorn_cc.png", 13, -10, 1, -1, -1)), + Box::new(BouncingImage::new("images/windows_logo.png", -8, 3, 2, -1, -1)), + Box::new(BouncingImage::new("images/spade.png", 32, -12, 1, 0, 0)), + Box::new(BouncingImage::new("images/dvdvideo.png", 20, 6, 5, 1000, 800)), + Box::new(BouncingImage::new("images/hackaday.png", 40, 18, 3, 500, 800)), ]; let mut display = Display::new(DISPLAY_HOST, DISPLAY_PORT); @@ -210,7 +212,7 @@ fn main() { loop { for (i, bb) in images.iter_mut().enumerate() { - if bb.rate > 0 && frame_counter % bb.rate != 0 { + if bb.rate() > 0 && frame_counter % bb.rate() != 0 { continue; } bb.draw_and_move(&mut display); From ed8f12e179b6b777d26d65abd1b247429396bac9 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 01:40:48 +0200 Subject: [PATCH 07/24] move drawpn to main thing --- src/main.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index f27aa71..d00d10e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,6 @@ trait Drawable { fn rate(&self) -> u32; fn draw_and_move(&mut self, display: &mut Display); - fn draw_png(&mut self, display: &mut Display, x: i32, y: i32); } impl BouncingImage { /// Initializes a new BouncingImage. @@ -84,13 +83,6 @@ impl BouncingImage { } - - -} -impl Drawable for BouncingImage { - fn rate(&self) -> u32 { - return self.rate; - } /// Draws a PNG image at the given coordinates. fn draw_png(&mut self, display: &mut Display, x: i32, y: i32) { @@ -103,7 +95,14 @@ impl Drawable for BouncingImage { } } } + } + +} +impl Drawable for BouncingImage { + fn rate(&self) -> u32 { + return self.rate; } + /// Draws the image and updates its position. fn draw_and_move(&mut self, display: &mut Display) { self.draw_png(display, self.x, self.y); From eb6207e1198e7a4b06531e70baefc0a32f3cf407 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 02:19:11 +0200 Subject: [PATCH 08/24] squircle --- src/main.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/main.rs b/src/main.rs index d00d10e..87307df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -119,6 +119,80 @@ impl Drawable for BouncingImage { } } +struct Circle { + x: u32, + y: u32, + radius: i32 +} +impl Circle { + + fn new(x:u32, y: u32,radius:i32) -> Self { + Circle { + x, + y, + radius + } + } + + /// This exploits the eight-way symmetry of a circle. + fn draw_circle_octants(&mut self, display: &mut Display, cx: u16, cy: u16, x: u16, y: u16, r: u8, g: u8, b: u8) { + display.set_pixel(cx + x, cy + y, r, g, b); + display.set_pixel(cx - x, cy + y, r, g, b); + display.set_pixel(cx + x, cy - y, r, g, b); + display.set_pixel(cx - x, cy - y, r, g, b); + display.set_pixel(cx + y, cy + x, r, g, b); + display.set_pixel(cx - y, cy + x, r, g, b); + display.set_pixel(cx + y, cy - x, r, g, b); + display.set_pixel(cx - y, cy - x, r, g, b); + } + + /// Draws a circle using the Midpoint Circle Algorithm. + /// + /// # Arguments + /// * `center_x`: The x-coordinate of the circle's center. + /// * `center_y`: The y-coordinate of the circle's center. + /// * `radius`: The radius of the circle. Must be non-negative. + /// * `r`, `g`, `b`: The RGB color components for the circle. + pub fn draw_circle(&mut self, display: &mut Display, center_x: u32, center_y: u32, radius: i32, r: u8, g: u8, b: u8) { + if radius < 0 { + // Or return an error: Err("Radius cannot be negative".into()) + return; + } + + let mut x = 0; + let mut y = radius; + // Initial decision parameter + let mut d = 3 - 2 * radius; + + // Iterate through the first octant and draw points in all 8 octants + while y >= x { + self.draw_circle_octants(display,center_x.try_into().unwrap(), center_y.try_into().unwrap(), x.try_into().unwrap(), y.try_into().unwrap(), r, g, b); + + x += 1; + + // Update the decision parameter + if d > 0 { + y -= 1; + d = d + 4 * (x - y) + 10; + } else { + d = d + 4 * x + 6; + } + } + } + +} +impl Drawable for Circle { + + fn rate(&self) -> u32 { + 1 + } + + /// Helper method to draw the 8 symmetric points for a given (x, y) offset. + fn draw_and_move(&mut self, display: &mut Display) { + self.draw_circle(display,self.x,self.y,self.radius,255,255,255); + } +} + /// Manages the connection and data sent to the display. struct Display { socket: UdpSocket, @@ -202,6 +276,7 @@ fn main() { Box::new(BouncingImage::new("images/spade.png", 32, -12, 1, 0, 0)), Box::new(BouncingImage::new("images/dvdvideo.png", 20, 6, 5, 1000, 800)), Box::new(BouncingImage::new("images/hackaday.png", 40, 18, 3, 500, 800)), + Box::new(Circle::new(1920/2,1080/2,128)) ]; let mut display = Display::new(DISPLAY_HOST, DISPLAY_PORT); From 8f671745001e3f4c9ab52bc424673e833b84a6a0 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 02:23:33 +0200 Subject: [PATCH 09/24] grow --- src/main.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 87307df..cb2fd62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -189,7 +189,11 @@ 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) { - self.draw_circle(display,self.x,self.y,self.radius,255,255,255); + self.draw_circle(display,self.x,self.y,self.radius,255,255,255); + self.radius = self.radius + 1; + if self.radius > 1080/2 { + self.radius = 1; + } } } @@ -271,11 +275,11 @@ impl Display { fn main() { let mut images:Vec> = vec![ - Box::new(BouncingImage::new("images/unicorn_cc.png", 13, -10, 1, -1, -1)), - Box::new(BouncingImage::new("images/windows_logo.png", -8, 3, 2, -1, -1)), - Box::new(BouncingImage::new("images/spade.png", 32, -12, 1, 0, 0)), - Box::new(BouncingImage::new("images/dvdvideo.png", 20, 6, 5, 1000, 800)), - Box::new(BouncingImage::new("images/hackaday.png", 40, 18, 3, 500, 800)), +// Box::new(BouncingImage::new("images/unicorn_cc.png", 13, -10, 1, -1, -1)), +// Box::new(BouncingImage::new("images/windows_logo.png", -8, 3, 2, -1, -1)), +// Box::new(BouncingImage::new("images/spade.png", 32, -12, 1, 0, 0)), +// Box::new(BouncingImage::new("images/dvdvideo.png", 20, 6, 5, 1000, 800)), +// Box::new(BouncingImage::new("images/hackaday.png", 40, 18, 3, 500, 800)), Box::new(Circle::new(1920/2,1080/2,128)) ]; From 6d503a1e62513e31fc9b075006c59857e523d587 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 02:28:56 +0200 Subject: [PATCH 10/24] bigger? --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index cb2fd62..81577e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use std::time::Duration; // Constants from the C code const QUEUE_LEN: usize = 1000; -const MSG_PAYLOAD_SIZE: usize = 7 * 160; +const MSG_PAYLOAD_SIZE: usize = 7 * 211; const MSGSIZE: usize = 2 + MSG_PAYLOAD_SIZE; const DISPLAY_HOST: &str = "100.65.0.2"; @@ -255,7 +255,7 @@ impl Display { buf[6] = b; self.pos_in_buf += 1; - if self.pos_in_buf == 160 { + if self.pos_in_buf == 211 { self.flush_frame(); } } From 3de035e73cbdef36d2680a7030202d948b09d884 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 02:30:44 +0200 Subject: [PATCH 11/24] faster --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 81577e5..5fa9f4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -299,7 +299,7 @@ fn main() { frame_counter += 1; // A small delay to control the frame rate - std::thread::sleep(Duration::from_millis(16)); + //std::thread::sleep(Duration::from_millis(16)); } } From 079911b947b412f5ece3c1542eb9c7168952e779 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 03:00:35 +0200 Subject: [PATCH 12/24] color --- src/color.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 10 ++++++-- 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/color.rs diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000..2b3b9dd --- /dev/null +++ b/src/color.rs @@ -0,0 +1,69 @@ +use std::fmt; + +/// Represents a color in the RGB (Red, Green, Blue) model. +/// Values range from 0 to 255. +pub struct Rgb { + pub r: u8, + pub g: u8, + pub b: u8, +} + +/// Represents a color in the HSV (Hue, Saturation, Value) model. +/// - `h` (hue): 0-359 degrees +/// - `s` (saturation): 0.0-1.0 +/// - `v` (value/brightness): 0.0-1.0 +pub struct Hsv { + pub h: u16, + pub s: f32, + pub v: f32, +} + +// Implement the `Debug` trait for Rgb to allow pretty-printing. +impl fmt::Debug for Rgb { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Rgb({}, {}, {})", self.r, self.g, self.b) + } +} + + +/// Provides the conversion logic from HSV to RGB. +/// This uses the standard mathematical formula for the conversion. +impl From for Rgb { + fn from(hsv: Hsv) -> Self { + // Ensure saturation and value are within the valid range [0.0, 1.0] + let s = hsv.s.clamp(0.0, 1.0); + let v = hsv.v.clamp(0.0, 1.0); + + // When saturation is 0, the color is a shade of gray. + if s == 0.0 { + let val = (v * 255.0) as u8; + return Rgb { r: val, g: val, b: val }; + } + + // The hue is treated as a sector on a color wheel. + let h = hsv.h as f32 / 60.0; // Sector 0-5 + let i = h.floor() as i32; + let f = h - i as f32; // Fractional part of h + + let p = v * (1.0 - s); + let q = v * (1.0 - f * s); + let t = v * (1.0 - (1.0 - f) * s); + + // Determine the RGB values based on the hue sector. + let (r, g, b) = match i { + 0 => (v, t, p), + 1 => (q, v, p), + 2 => (p, v, t), + 3 => (p, q, v), + 4 => (t, p, v), + _ => (v, p, q), // Default case for sector 5 + }; + + // Convert the float RGB values (0.0-1.0) to u8 values (0-255). + Rgb { + r: (r * 255.0).round() as u8, + g: (g * 255.0).round() as u8, + b: (b * 255.0).round() as u8, + } + } +} diff --git a/src/main.rs b/src/main.rs index 5fa9f4b..0cbaa98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::fs::File; use std::io::BufReader; use std::net::{ToSocketAddrs, UdpSocket}; use std::time::Duration; - +mod color; // Constants from the C code const QUEUE_LEN: usize = 1000; const MSG_PAYLOAD_SIZE: usize = 7 * 211; @@ -189,7 +189,13 @@ 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) { - self.draw_circle(display,self.x,self.y,self.radius,255,255,255); + let hsv_color = color::Hsv { + h: 360, + s: 1.0, + v: 1.0, + }; + let rgb: color::Rgb = hsv_color.into(); + self.draw_circle(display,self.x,self.y,self.radius,rgb.r,rgb.g,rgb.b); self.radius = self.radius + 1; if self.radius > 1080/2 { self.radius = 1; From 85b30baabc270ec0b3450b96d385ed6a010ff7ab Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 03:02:23 +0200 Subject: [PATCH 13/24] fade --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 0cbaa98..687ae4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -190,7 +190,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) { let hsv_color = color::Hsv { - h: 360, + h: self.radius%360, s: 1.0, v: 1.0, }; From bccb1be2d446fe1e916069da7284eace4813e249 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 03:04:02 +0200 Subject: [PATCH 14/24] color --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 687ae4e..ae4bf04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -190,7 +190,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) { let hsv_color = color::Hsv { - h: self.radius%360, + h: (self.radius%360).try_into().unwrap(), s: 1.0, v: 1.0, }; From 7316805ad72038de5bc649a5814666f3d4c9b4ab Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 03:18:49 +0200 Subject: [PATCH 15/24] color --- src/main.rs | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/main.rs b/src/main.rs index ae4bf04..8495bd9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,7 +54,7 @@ struct BouncingImage { trait Drawable { // Associated function signature; `Self` refers to the implementor type. fn rate(&self) -> u32; - fn draw_and_move(&mut self, display: &mut Display); + fn draw_and_move(&mut self, display: &mut Display,tick:u32); } impl BouncingImage { @@ -104,7 +104,7 @@ impl Drawable for BouncingImage { } /// Draws the image and updates its position. - fn draw_and_move(&mut self, display: &mut Display) { + fn draw_and_move(&mut self, display: &mut Display,_: u32) { self.draw_png(display, self.x, self.y); self.x += self.move_x; @@ -122,15 +122,13 @@ impl Drawable for BouncingImage { struct Circle { x: u32, y: u32, - radius: i32 } impl Circle { - fn new(x:u32, y: u32,radius:i32) -> Self { + fn new(x:u32, y: u32) -> Self { Circle { x, - y, - radius + y } } @@ -153,16 +151,16 @@ impl Circle { /// * `center_y`: The y-coordinate of the circle's center. /// * `radius`: The radius of the circle. Must be non-negative. /// * `r`, `g`, `b`: The RGB color components for the circle. - pub fn draw_circle(&mut self, display: &mut Display, center_x: u32, center_y: u32, radius: i32, r: u8, g: u8, b: u8) { + pub fn draw_circle(&mut self, display: &mut Display, center_x: u32, center_y: u32, radius: u32, r: u8, g: u8, b: u8) { if radius < 0 { // Or return an error: Err("Radius cannot be negative".into()) return; } - - let mut x = 0; - let mut y = radius; + let radius_i32:i32 = radius.try_into().unwrap(); + let mut x:i32 = 0; + let mut y:i32 = radius_i32; // Initial decision parameter - let mut d = 3 - 2 * radius; + let mut d:i32 = 3 - 2 * radius_i32; // Iterate through the first octant and draw points in all 8 octants while y >= x { @@ -188,18 +186,16 @@ 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) { + fn draw_and_move(&mut self, display: &mut Display,tick:u32) { let hsv_color = color::Hsv { - h: (self.radius%360).try_into().unwrap(), + h: (tick%360).try_into().unwrap(), s: 1.0, v: 1.0, }; let rgb: color::Rgb = hsv_color.into(); - self.draw_circle(display,self.x,self.y,self.radius,rgb.r,rgb.g,rgb.b); - self.radius = self.radius + 1; - if self.radius > 1080/2 { - self.radius = 1; - } + + let radius = tick % 128; + self.draw_circle(display,self.x,self.y,radius.try_into().unwrap(),rgb.r,rgb.g,rgb.b); } } @@ -286,22 +282,23 @@ fn main() { // Box::new(BouncingImage::new("images/spade.png", 32, -12, 1, 0, 0)), // Box::new(BouncingImage::new("images/dvdvideo.png", 20, 6, 5, 1000, 800)), // Box::new(BouncingImage::new("images/hackaday.png", 40, 18, 3, 500, 800)), - Box::new(Circle::new(1920/2,1080/2,128)) + Box::new(Circle::new(1920/2,1080/2)) ]; let mut display = Display::new(DISPLAY_HOST, DISPLAY_PORT); let mut frame_counter: u32 = 0; // display.blank_screen(); - + let mut tick:u32 = 0; loop { for (i, bb) in images.iter_mut().enumerate() { if bb.rate() > 0 && frame_counter % bb.rate() != 0 { continue; } - bb.draw_and_move(&mut display); - } + bb.draw_and_move(&mut display,tick); + } display.flush_frame(); + tick+=1; frame_counter += 1; // A small delay to control the frame rate From 3a957a1c207b3d52ebfcec9c7f6c2eea5fa920eb Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 03:20:32 +0200 Subject: [PATCH 16/24] remove overflow --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 8495bd9..2282df0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -194,7 +194,7 @@ impl Drawable for Circle { }; let rgb: color::Rgb = hsv_color.into(); - let radius = tick % 128; + let radius = tick % (1080/2); self.draw_circle(display,self.x,self.y,radius.try_into().unwrap(),rgb.r,rgb.g,rgb.b); } } From 9f03b76880bc45c8c25a309e8e54c5d3d3e9ea25 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 03:21:43 +0200 Subject: [PATCH 17/24] 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, }; From 29261101e4cac2e8ebaa65bd03af76cbaf893da0 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 03:22:53 +0200 Subject: [PATCH 18/24] slow move --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 66ae2db..3f99673 100644 --- a/src/main.rs +++ b/src/main.rs @@ -194,7 +194,7 @@ impl Drawable for Circle { }; let rgb: color::Rgb = hsv_color.into(); - let radius = tick % (1080/2); + let radius = (tick/30) % (1080/2); self.draw_circle(display,self.x,self.y,radius.try_into().unwrap(),rgb.r,rgb.g,rgb.b); } } From 3b9ab3acae25c741bc57d2f15bc79d7c60b61504 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sat, 19 Jul 2025 03:23:51 +0200 Subject: [PATCH 19/24] slow move --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 3f99673..d3b2ec0 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/20)%360).try_into().unwrap(), + h: ((tick/200)%360).try_into().unwrap(), s: 1.0, v: 1.0, }; From 23db40c5771f506b634596cf7059ef35c81592b2 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sun, 20 Jul 2025 02:02:48 +0200 Subject: [PATCH 20/24] maybe work? --- src/main.rs | 123 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 105 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index d3b2ec0..d57040c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,8 @@ use std::fs::File; use std::io::BufReader; use std::net::{ToSocketAddrs, UdpSocket}; use std::time::Duration; +use std::thread; +use std::sync::{Mutex,Arc}; mod color; // Constants from the C code const QUEUE_LEN: usize = 1000; @@ -91,7 +93,7 @@ impl BouncingImage { let index = (sy * self.img.width + sx) as usize * 4; let rgba = &self.img.pixels[index..index + 4]; if rgba[3] > 0 { // Check alpha channel - display.set_pixel((x + sx as i32) as u16, (y + sy as i32) as u16, rgba[0], rgba[1], rgba[2]); + //display.set_pixel((x + sx as i32) as u16, (y + sy as i32) as u16, rgba[0], rgba[1], rgba[2]); } } } @@ -120,12 +122,12 @@ impl Drawable for BouncingImage { } struct Circle { - x: u32, - y: u32, + x: Arc>, + y: Arc>, } impl Circle { - fn new(x:u32, y: u32) -> Self { + fn new(x:Arc>, y: Arc>) -> Self { Circle { x, y @@ -133,7 +135,7 @@ impl Circle { } /// This exploits the eight-way symmetry of a circle. - fn draw_circle_octants(&mut self, display: &mut Display, cx: u16, cy: u16, x: u16, y: u16, r: u8, g: u8, b: u8) { + fn draw_circle_octants(&mut self, display: &mut Display, cx: i32, cy: i32, x: i32, y: i32, r: u8, g: u8, b: u8) { display.set_pixel(cx + x, cy + y, r, g, b); display.set_pixel(cx - x, cy + y, r, g, b); display.set_pixel(cx + x, cy - y, r, g, b); @@ -164,7 +166,7 @@ impl Circle { // Iterate through the first octant and draw points in all 8 octants while y >= x { - self.draw_circle_octants(display,center_x.try_into().unwrap(), center_y.try_into().unwrap(), x.try_into().unwrap(), y.try_into().unwrap(), r, g, b); + self.draw_circle_octants(display,center_x.try_into().unwrap(), center_y.try_into().unwrap(), x, y, r, g, b); x += 1; @@ -193,9 +195,10 @@ impl Drawable for Circle { v: 1.0, }; let rgb: color::Rgb = hsv_color.into(); - - let radius = (tick/30) % (1080/2); - self.draw_circle(display,self.x,self.y,radius.try_into().unwrap(),rgb.r,rgb.g,rgb.b); + let draw_y =*self.x.lock().unwrap(); + let draw_x = *self.y.lock().unwrap(); + let radius = (tick/30) % (1080/2); + self.draw_circle(display,draw_y,draw_x,radius.try_into().unwrap(),rgb.r,rgb.g,rgb.b); } } @@ -245,13 +248,14 @@ impl Display { } /// 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; + fn set_pixel(&mut self, x: i32, y: i32, r: u8, g: u8, b: u8) { + if let (Ok(output_x),Ok(output_y)) = (u16::try_from(x), u16::try_from(y)) { + 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[0] = output_x as u8; + buf[1] = (output_x >> 8) as u8; + buf[2] = output_y as u8; + buf[3] = (output_y >> 8) as u8; buf[4] = r; buf[5] = g; buf[6] = b; @@ -259,7 +263,10 @@ impl Display { self.pos_in_buf += 1; if self.pos_in_buf == 211 { self.flush_frame(); - } + } + } + + } @@ -268,25 +275,105 @@ impl Display { fn blank_screen(&mut self) { for x in 0..DISPLAY_WIDTH { for y in 0..DISPLAY_HEIGHT { - self.set_pixel(x as u16, y as u16, 0, 0, 0); + //self.set_pixel(x as u16, y as u16, 0, 0, 0); } } self.flush_frame(); } } + +/// Unpacks a 4-byte slice into two u16 values (little-endian). +fn unpack_coordinates(buffer: &[u8]) -> Option<(u16, u16)> { + if buffer.len() != 4 { + return None; + } + // Try to convert the first 2 bytes to a u16 for x. + let x_bytes: [u8; 2] = buffer[0..2].try_into().ok()?; + // Try to convert the next 2 bytes to a u16 for y. + let y_bytes: [u8; 2] = buffer[2..4].try_into().ok()?; + + // Reconstruct the u16 values from their little-endian byte representation. + let x = u16::from_le_bytes(x_bytes); + let y = u16::from_le_bytes(y_bytes); + + Some((x, y)) +} + fn main() { + + + let x:Arc> = Arc::new(Mutex::new(0)); + let x_thread = x.clone(); + + let y:Arc>= Arc::new(Mutex::new(0)); + let y_thread = y.clone(); + + let circle = Box::new(Circle::new(x,y)); let mut images:Vec> = vec![ // Box::new(BouncingImage::new("images/unicorn_cc.png", 13, -10, 1, -1, -1)), // Box::new(BouncingImage::new("images/windows_logo.png", -8, 3, 2, -1, -1)), // Box::new(BouncingImage::new("images/spade.png", 32, -12, 1, 0, 0)), // Box::new(BouncingImage::new("images/dvdvideo.png", 20, 6, 5, 1000, 800)), // Box::new(BouncingImage::new("images/hackaday.png", 40, 18, 3, 500, 800)), - Box::new(Circle::new(1920/2,1080/2)) + circle ]; let mut display = Display::new(DISPLAY_HOST, DISPLAY_PORT); let mut frame_counter: u32 = 0; + thread::spawn(move || { + let bind_address = format!("0.0.0.0:12345"); + + // Bind the UDP socket to the specified address and port. + let socket = match UdpSocket::bind(&bind_address) { + Ok(s) => s, + Err(e) => { + eprintln!("Error: Could not bind to address {}: {}", bind_address, e); + panic!("aaaa"); + } + }; + + println!("Listening for UDP packets on {}", bind_address); + + // Create a buffer to hold incoming data. 4 bytes for two u16 values. + let mut buf = [0u8; 4]; + + loop { + // Wait for a packet to arrive. + match socket.recv_from(&mut buf) { + Ok((number_of_bytes, src_addr)) => { + println!("\nReceived {} bytes from {}", number_of_bytes, src_addr); + + // Ensure we received the correct number of bytes. + if number_of_bytes == 4 { + // Unpack the buffer into coordinates. + if let Some((x_rev, y_rev)) = unpack_coordinates(&buf) { + let x_32:u32 = x_rev.into(); + let y_32:u32 = y_rev.into(); + *x_thread.lock().unwrap() = x_32; + *y_thread.lock().unwrap() = y_32; + println!("Received Coordinates: X = {}, Y = {}", x_rev, y_rev); + + } else { + // This case should ideally not be reached if number_of_bytes is 4. + eprintln!("Error: Failed to unpack coordinate data."); + } + } else { + eprintln!( + "Warning: Received packet with incorrect size ({} bytes). Expected 4.", + number_of_bytes + ); + } + } + Err(e) => { + eprintln!("Error receiving data: {}", e); + // Decide if you want to break the loop on an error. + // For a continuous server, you might just log and continue. + } + } + } + + }); // display.blank_screen(); let mut tick:u32 = 0; From 65fca6a5b23b1d36d41b140d4b7c9affeeb1a8c9 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sun, 20 Jul 2025 02:05:33 +0200 Subject: [PATCH 21/24] maybe work? --- src/main.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index d57040c..f1ab80c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -348,11 +348,12 @@ fn main() { if number_of_bytes == 4 { // Unpack the buffer into coordinates. if let Some((x_rev, y_rev)) = unpack_coordinates(&buf) { - let x_32:u32 = x_rev.into(); + + println!("Received Coordinates: X = {}, Y = {}", x_rev, y_rev); let x_32:u32 = x_rev.into(); let y_32:u32 = y_rev.into(); *x_thread.lock().unwrap() = x_32; *y_thread.lock().unwrap() = y_32; - println!("Received Coordinates: X = {}, Y = {}", x_rev, y_rev); + } else { // This case should ideally not be reached if number_of_bytes is 4. From 6c65b78e6cdbd3c655188ebdc2b2cbfa80b61cdf Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sun, 20 Jul 2025 02:45:04 +0200 Subject: [PATCH 22/24] smaller --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index f1ab80c..5ae3f78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -197,7 +197,7 @@ impl Drawable for Circle { let rgb: color::Rgb = hsv_color.into(); let draw_y =*self.x.lock().unwrap(); let draw_x = *self.y.lock().unwrap(); - let radius = (tick/30) % (1080/2); + let radius = (tick/30) % (300/2); self.draw_circle(display,draw_y,draw_x,radius.try_into().unwrap(),rgb.r,rgb.g,rgb.b); } } From fa4cfcd7b8ae558208bb6d4e7ef3a0e7cbcb5e98 Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sun, 20 Jul 2025 15:57:55 +0200 Subject: [PATCH 23/24] multicast --- Cargo.lock | 33 +++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 8 +++++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 68f3f8c..09fb07b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,7 @@ dependencies = [ "libc", "nix", "png", + "socket2", ] [[package]] @@ -131,3 +132,35 @@ name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 2a394d9..5afe95d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" png = "0.17" libc = "0.2" nix = { version = "0.29", features = ["socket", "uio"] } +socket2 = "0.4.7" diff --git a/src/main.rs b/src/main.rs index 5ae3f78..b677ada 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ use std::net::{ToSocketAddrs, UdpSocket}; use std::time::Duration; use std::thread; use std::sync::{Mutex,Arc}; +use socket2::{Domain, Socket, Type}; +use std::net::{Ipv4Addr, SocketAddr}; mod color; // Constants from the C code const QUEUE_LEN: usize = 1000; @@ -323,7 +325,11 @@ fn main() { let mut frame_counter: u32 = 0; thread::spawn(move || { let bind_address = format!("0.0.0.0:12345"); - + let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap(); + socket.set_reuse_address(true).unwrap(); + socket.set_nonblocking(true).unwrap(); + socket.join_multicast_v4(&Ipv4Addr::new(239, 1, 1, 1), &Ipv4Addr::new(0, 0, 0, 0)).unwrap(); + socket.bind(&"0.0.0.0:12345".parse::().unwrap().into()).unwrap(); // Bind the UDP socket to the specified address and port. let socket = match UdpSocket::bind(&bind_address) { Ok(s) => s, From 237e4534bf000a1bf15460420e13cd11878bdcac Mon Sep 17 00:00:00 2001 From: "0m.ax" Date: Sun, 20 Jul 2025 16:46:54 +0200 Subject: [PATCH 24/24] fixed things --- src/main.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index b677ada..8585c0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -327,17 +327,11 @@ fn main() { let bind_address = format!("0.0.0.0:12345"); let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap(); socket.set_reuse_address(true).unwrap(); - socket.set_nonblocking(true).unwrap(); + //socket.set_nonblocking(true).unwrap(); socket.join_multicast_v4(&Ipv4Addr::new(239, 1, 1, 1), &Ipv4Addr::new(0, 0, 0, 0)).unwrap(); - socket.bind(&"0.0.0.0:12345".parse::().unwrap().into()).unwrap(); + socket.bind(&"0.0.0.0:1234".parse::().unwrap().into()).unwrap(); // Bind the UDP socket to the specified address and port. - let socket = match UdpSocket::bind(&bind_address) { - Ok(s) => s, - Err(e) => { - eprintln!("Error: Could not bind to address {}: {}", bind_address, e); - panic!("aaaa"); - } - }; + let socket: UdpSocket = socket.into(); println!("Listening for UDP packets on {}", bind_address);