Merge pull request #4 from cccb/additional-grid-features
Additional grid features
This commit is contained in:
		
						commit
						15a3c4d596
					
				
					 22 changed files with 619 additions and 473 deletions
				
			
		
							
								
								
									
										40
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										40
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -95,6 +95,18 @@ version = "2.5.0" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "bitvec" | ||||
| version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" | ||||
| dependencies = [ | ||||
|  "funty", | ||||
|  "radium", | ||||
|  "tap", | ||||
|  "wyz", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "bzip2" | ||||
| version = "0.4.4" | ||||
|  | @ -267,6 +279,12 @@ dependencies = [ | |||
|  "miniz_oxide", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "funty" | ||||
| version = "2.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "getrandom" | ||||
| version = "0.2.15" | ||||
|  | @ -419,6 +437,12 @@ dependencies = [ | |||
|  "proc-macro2", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "radium" | ||||
| version = "0.7.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rand" | ||||
| version = "0.8.5" | ||||
|  | @ -542,6 +566,7 @@ dependencies = [ | |||
| name = "servicepoint" | ||||
| version = "0.5.1" | ||||
| dependencies = [ | ||||
|  "bitvec", | ||||
|  "bzip2", | ||||
|  "clap 4.5.4", | ||||
|  "flate2", | ||||
|  | @ -602,6 +627,12 @@ dependencies = [ | |||
|  "unicode-ident", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "tap" | ||||
| version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "tempfile" | ||||
| version = "3.10.1" | ||||
|  | @ -766,6 +797,15 @@ version = "0.52.5" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "wyz" | ||||
| version = "0.5.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" | ||||
| dependencies = [ | ||||
|  "tap", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zstd" | ||||
| version = "0.13.1" | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ crate-type = ["rlib"] | |||
| 
 | ||||
| [dependencies] | ||||
| log = "0.4" | ||||
| bitvec = "1.0" | ||||
| flate2 = { version = "1.0", optional = true } | ||||
| bzip2 = { version = "0.4", optional = true } | ||||
| zstd = { version = "0.13", optional = true } | ||||
|  |  | |||
							
								
								
									
										37
									
								
								crates/servicepoint/examples/brightness_tester.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								crates/servicepoint/examples/brightness_tester.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| //! Show a brightness level test pattern on screen
 | ||||
| 
 | ||||
| use clap::Parser; | ||||
| 
 | ||||
| use servicepoint::Command::BitmapLinearWin; | ||||
| use servicepoint::*; | ||||
| 
 | ||||
| #[derive(Parser, Debug)] | ||||
| struct Cli { | ||||
|     #[arg(short, long, default_value = "localhost:2342")] | ||||
|     destination: String, | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let cli = Cli::parse(); | ||||
|     let connection = Connection::open(cli.destination).unwrap(); | ||||
| 
 | ||||
|     let mut pixels = PixelGrid::max_sized(); | ||||
|     pixels.fill(true); | ||||
| 
 | ||||
|     connection | ||||
|         .send(BitmapLinearWin( | ||||
|             Origin(0, 0), | ||||
|             pixels, | ||||
|             CompressionCode::Uncompressed, | ||||
|         )) | ||||
|         .expect("send failed"); | ||||
| 
 | ||||
|     let mut brightnesses = ByteGrid::new(TILE_WIDTH, TILE_HEIGHT); | ||||
|     for (index, byte) in brightnesses.data_ref_mut().iter_mut().enumerate() { | ||||
|         *byte = (index % u8::MAX as usize) as u8; | ||||
|     } | ||||
| 
 | ||||
|     connection | ||||
|         .send(Command::CharBrightness(Origin(0, 0), brightnesses)) | ||||
|         .expect("send failed"); | ||||
| } | ||||
|  | @ -4,7 +4,7 @@ use std::time::Duration; | |||
| 
 | ||||
| use clap::Parser; | ||||
| 
 | ||||
| use servicepoint::*; | ||||
| use servicepoint::{bitvec::prelude::BitVec, *}; | ||||
| 
 | ||||
| #[derive(Parser, Debug)] | ||||
| struct Cli { | ||||
|  | @ -34,7 +34,7 @@ fn main() { | |||
| 
 | ||||
|         // this works because the pixel grid has max size
 | ||||
|         let pixel_data: Vec<u8> = enabled_pixels.clone().into(); | ||||
|         let bit_vec = BitVec::from(&*pixel_data); | ||||
|         let bit_vec = BitVec::from_vec(pixel_data); | ||||
| 
 | ||||
|         connection | ||||
|             .send(Command::BitmapLinearAnd(0, bit_vec, CompressionCode::Lzma)) | ||||
|  |  | |||
|  | @ -1,228 +0,0 @@ | |||
| use crate::DataRef; | ||||
| 
 | ||||
| /// A fixed-size vector of bits
 | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub struct BitVec { | ||||
|     size: usize, | ||||
|     data: Vec<u8>, | ||||
| } | ||||
| 
 | ||||
| impl BitVec { | ||||
|     /// Create a new `BitVec`.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `size`: size in bits.
 | ||||
|     ///
 | ||||
|     /// returns: `BitVec` with all bits set to false.
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// When `size` is not divisible by 8.
 | ||||
|     #[must_use] | ||||
|     pub fn new(size: usize) -> BitVec { | ||||
|         assert_eq!(size % 8, 0); | ||||
|         Self { | ||||
|             size, | ||||
|             data: vec![0; size / 8], | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Sets the value of a bit.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `index`: the bit index to edit
 | ||||
|     /// * `value`: the value to set the bit to
 | ||||
|     ///
 | ||||
|     /// returns: old value of the bit
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// When accessing `index` out of bounds.
 | ||||
|     pub fn set(&mut self, index: usize, value: bool) -> bool { | ||||
|         let (byte_index, bit_mask) = self.get_indexes(index); | ||||
| 
 | ||||
|         let byte = self.data[byte_index]; | ||||
|         let old_value = byte & bit_mask != 0; | ||||
| 
 | ||||
|         self.data[byte_index] = if value { | ||||
|             byte | bit_mask | ||||
|         } else { | ||||
|             byte & (u8::MAX ^ bit_mask) | ||||
|         }; | ||||
| 
 | ||||
|         old_value | ||||
|     } | ||||
| 
 | ||||
|     /// Gets the value of a bit.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `index`: the bit index to read
 | ||||
|     ///
 | ||||
|     /// returns: value of the bit
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// When accessing `index` out of bounds.
 | ||||
|     #[must_use] | ||||
|     pub fn get(&self, index: usize) -> bool { | ||||
|         let (byte_index, bit_mask) = self.get_indexes(index); | ||||
|         self.data[byte_index] & bit_mask != 0 | ||||
|     } | ||||
| 
 | ||||
|     /// Sets all bits to the specified value
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `value`: the value to set all bits to
 | ||||
|     ///
 | ||||
|     /// # Examples
 | ||||
|     /// ```
 | ||||
|     ///  use servicepoint::BitVec;
 | ||||
|     ///  let mut vec = BitVec::new(8);
 | ||||
|     ///  vec.fill(true);
 | ||||
|     /// ```
 | ||||
|     pub fn fill(&mut self, value: bool) { | ||||
|         let byte: u8 = if value { 0xFF } else { 0x00 }; | ||||
|         self.data.fill(byte); | ||||
|     } | ||||
| 
 | ||||
|     /// Gets the length in bits
 | ||||
|     #[must_use] | ||||
|     pub fn len(&self) -> usize { | ||||
|         self.size | ||||
|     } | ||||
| 
 | ||||
|     /// returns true if length is 0.
 | ||||
|     #[must_use] | ||||
|     pub fn is_empty(&self) -> bool { | ||||
|         self.data.is_empty() | ||||
|     } | ||||
| 
 | ||||
|     /// Calculates the byte index and bitmask for a specific bit in the vector
 | ||||
|     fn get_indexes(&self, bit_index: usize) -> (usize, u8) { | ||||
|         assert!( | ||||
|             bit_index < self.size, | ||||
|             "bit index {bit_index} is outside of range 0..<{}", | ||||
|             self.size | ||||
|         ); | ||||
| 
 | ||||
|         let byte_index = bit_index / 8; | ||||
|         let bit_in_byte_index = 7 - bit_index % 8; | ||||
|         let bit_mask: u8 = 1 << bit_in_byte_index; | ||||
|         (byte_index, bit_mask) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl DataRef for BitVec { | ||||
|     fn data_ref_mut(&mut self) -> &mut [u8] { | ||||
|         self.data.as_mut_slice() | ||||
|     } | ||||
| 
 | ||||
|     fn data_ref(&self) -> &[u8] { | ||||
|         self.data.as_slice() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<BitVec> for Vec<u8> { | ||||
|     /// Turns the `BitVec` into the underlying `Vec<u8>`
 | ||||
|     fn from(value: BitVec) -> Self { | ||||
|         value.data | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<&[u8]> for BitVec { | ||||
|     /// Interpret the data as a series of bits and load then into a new `BitVec` instance.
 | ||||
|     fn from(value: &[u8]) -> Self { | ||||
|         Self { | ||||
|             size: value.len() * 8, | ||||
|             data: Vec::from(value), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::{BitVec, DataRef}; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn fill() { | ||||
|         let mut vec = BitVec::new(8 * 3); | ||||
|         assert_eq!(vec.data, [0x00, 0x00, 0x00]); | ||||
| 
 | ||||
|         vec.fill(true); | ||||
|         assert_eq!(vec.data, [0xFF, 0xFF, 0xFF]); | ||||
| 
 | ||||
|         vec.fill(false); | ||||
|         assert_eq!(vec.data, [0x00, 0x00, 0x00]); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn get_set() { | ||||
|         let mut vec = BitVec::new(8 * 3); | ||||
|         assert!(!vec.get(1)); | ||||
|         assert!(!vec.get(11)); | ||||
| 
 | ||||
|         vec.set(1, true); | ||||
|         vec.set(11, true); | ||||
|         assert_eq!(vec.data, [0x40, 0x10, 0x00]); | ||||
|         assert!(!vec.get(0)); | ||||
|         assert!(vec.get(1)); | ||||
|         assert!(vec.get(11)); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn load() { | ||||
|         let mut vec = BitVec::new(8 * 3); | ||||
|         vec.set(6, true); | ||||
|         vec.set(7, true); | ||||
|         vec.set(8, true); | ||||
|         vec.set(9, true); | ||||
|         vec.set(10, true); | ||||
|         vec.set(vec.len() - 1, true); | ||||
| 
 | ||||
|         assert_eq!(vec.data, [0x03, 0xE0, 0x01]); | ||||
| 
 | ||||
|         let data: Vec<u8> = vec.into(); | ||||
| 
 | ||||
|         let vec = BitVec::from(&*data); | ||||
|         assert_eq!(vec.data, [0x03, 0xE0, 0x01]); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn mut_data_ref() { | ||||
|         let mut vec = BitVec::new(8 * 3); | ||||
| 
 | ||||
|         let data_ref = vec.data_ref_mut(); | ||||
|         data_ref.copy_from_slice(&[0x40, 0x10, 0x00]); | ||||
| 
 | ||||
|         assert_eq!(vec.data, [0x40, 0x10, 0x00]); | ||||
|         assert!(vec.get(1)); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn is_empty() { | ||||
|         let vec = BitVec::new(8 * 3); | ||||
|         assert!(!vec.is_empty()); | ||||
| 
 | ||||
|         let vec = BitVec::new(0); | ||||
|         assert!(vec.is_empty()); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn get_returns_old() { | ||||
|         let mut vec = BitVec::new(8); | ||||
|         assert!(!vec.set(1, true)); | ||||
|         assert!(vec.set(1, true)); | ||||
|         assert!(vec.set(1, false)); | ||||
|         assert!(!vec.set(1, false)); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn debug_print() { | ||||
|         let vec = BitVec::new(8 * 3); | ||||
|         format!("{vec:?}"); | ||||
|     } | ||||
| } | ||||
|  | @ -1,3 +1,5 @@ | |||
| use std::slice::{Iter, IterMut}; | ||||
| 
 | ||||
| use crate::{DataRef, Grid}; | ||||
| 
 | ||||
| /// A 2D grid of bytes
 | ||||
|  | @ -9,6 +11,22 @@ pub struct ByteGrid { | |||
| } | ||||
| 
 | ||||
| impl ByteGrid { | ||||
|     /// Creates a new `ByteGrid` with the specified dimensions.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// - width: size in x-direction
 | ||||
|     /// - height: size in y-direction
 | ||||
|     ///
 | ||||
|     /// returns: `ByteGrid` initialized to 0.
 | ||||
|     pub fn new(width: usize, height: usize) -> Self { | ||||
|         Self { | ||||
|             data: vec![0; width * height], | ||||
|             width, | ||||
|             height, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Loads a `ByteGrid` with the specified dimensions from the provided data.
 | ||||
|     ///
 | ||||
|     /// returns: `ByteGrid` that contains a copy of the provided data
 | ||||
|  | @ -26,32 +44,72 @@ impl ByteGrid { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn check_indexes(&self, x: usize, y: usize) { | ||||
|         assert!( | ||||
|             x < self.width, | ||||
|             "cannot access byte {x}-{y} because x is outside of bounds 0..{}", | ||||
|             self.width | ||||
|         ); | ||||
|         assert!( | ||||
|             y < self.height, | ||||
|             "cannot access byte {x}-{y} because y is outside of bounds 0..{}", | ||||
|             self.height | ||||
|         ); | ||||
|     /// Iterate over all cells in `ByteGrid`.
 | ||||
|     ///
 | ||||
|     /// Order is equivalent to the following loop:
 | ||||
|     /// ```
 | ||||
|     /// # use servicepoint::{ByteGrid, Grid};
 | ||||
|     /// # let grid = ByteGrid::new(2,2);
 | ||||
|     /// for y in 0..grid.height() {
 | ||||
|     ///     for x in 0..grid.width() {
 | ||||
|     ///         grid.get(x, y);
 | ||||
|     ///     }
 | ||||
|     /// }
 | ||||
|     /// ```
 | ||||
|     pub fn iter(&self) -> Iter<u8> { | ||||
|         self.data.iter() | ||||
|     } | ||||
| 
 | ||||
|     /// Iterate over all rows in `ByteGrid` top to bottom.
 | ||||
|     pub fn iter_rows(&self) -> IterRows { | ||||
|         IterRows { | ||||
|             byte_grid: self, | ||||
|             row: 0, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Returns an iterator that allows modifying each value.
 | ||||
|     ///
 | ||||
|     /// The iterator yields all cells from top left to bottom right.
 | ||||
|     pub fn iter_mut(&mut self) -> IterMut<u8> { | ||||
|         self.data.iter_mut() | ||||
|     } | ||||
| 
 | ||||
|     /// Get a mutable reference to the current value at the specified position.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `x` and `y`: position of the cell
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// When accessing `x` or `y` out of bounds.
 | ||||
|     pub fn get_ref_mut(&mut self, x: usize, y: usize) -> &mut u8 { | ||||
|         self.assert_in_bounds(x, y); | ||||
|         &mut self.data[x + y * self.width] | ||||
|     } | ||||
| 
 | ||||
|     /// Get a mutable reference to the current value at the specified position if position is in bounds.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `x` and `y`: position of the cell
 | ||||
|     ///
 | ||||
|     /// returns: Reference to cell or None
 | ||||
|     pub fn get_ref_mut_optional( | ||||
|         &mut self, | ||||
|         x: isize, | ||||
|         y: isize, | ||||
|     ) -> Option<&mut u8> { | ||||
|         if self.is_in_bounds(x, y) { | ||||
|             Some(&mut self.data[x as usize + y as usize * self.width]) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Grid<u8> for ByteGrid { | ||||
|     /// Creates a new `ByteGrid` with the specified dimensions.
 | ||||
|     ///
 | ||||
|     /// returns: `ByteGrid` initialized to 0.
 | ||||
|     fn new(width: usize, height: usize) -> Self { | ||||
|         Self { | ||||
|             data: vec![0; width * height], | ||||
|             width, | ||||
|             height, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Sets the value of the cell at the specified position in the `ByteGrid.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|  | @ -59,17 +117,12 @@ impl Grid<u8> for ByteGrid { | |||
|     /// * `x` and `y`: position of the cell
 | ||||
|     /// * `value`: the value to write to the cell
 | ||||
|     ///
 | ||||
|     /// returns: old value of the cell.
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// When accessing `x` or `y` out of bounds.
 | ||||
|     fn set(&mut self, x: usize, y: usize, value: u8) -> u8 { | ||||
|         self.check_indexes(x, y); | ||||
|         let pos = &mut self.data[x + y * self.width]; | ||||
|         let old_val = *pos; | ||||
|         *pos = value; | ||||
|         old_val | ||||
|     fn set(&mut self, x: usize, y: usize, value: u8) { | ||||
|         self.assert_in_bounds(x, y); | ||||
|         self.data[x + y * self.width] = value; | ||||
|     } | ||||
| 
 | ||||
|     /// Gets the current value at the specified position.
 | ||||
|  | @ -82,7 +135,7 @@ impl Grid<u8> for ByteGrid { | |||
|     ///
 | ||||
|     /// When accessing `x` or `y` out of bounds.
 | ||||
|     fn get(&self, x: usize, y: usize) -> u8 { | ||||
|         self.check_indexes(x, y); | ||||
|         self.assert_in_bounds(x, y); | ||||
|         self.data[x + y * self.width] | ||||
|     } | ||||
| 
 | ||||
|  | @ -97,17 +150,6 @@ impl Grid<u8> for ByteGrid { | |||
|     fn height(&self) -> usize { | ||||
|         self.height | ||||
|     } | ||||
| 
 | ||||
|     fn window(&self, x: usize, y: usize, w: usize, h: usize) -> Self { | ||||
|         let mut win = Self::new(w, h); | ||||
|         for win_x in 0..w { | ||||
|             for win_y in 0..h { | ||||
|                 let value = self.get(x + win_x, y + win_y); | ||||
|                 win.set(win_x, win_y, value); | ||||
|             } | ||||
|         } | ||||
|         win | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl DataRef for ByteGrid { | ||||
|  | @ -129,6 +171,27 @@ impl From<ByteGrid> for Vec<u8> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct IterRows<'t> { | ||||
|     byte_grid: &'t ByteGrid, | ||||
|     row: usize, | ||||
| } | ||||
| 
 | ||||
| impl<'t> Iterator for IterRows<'t> { | ||||
|     type Item = Iter<'t, u8>; | ||||
| 
 | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
|         if self.row >= self.byte_grid.height { | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|         let start = self.row * self.byte_grid.width; | ||||
|         let end = start + self.byte_grid.width; | ||||
|         let result = self.byte_grid.data[start..end].iter(); | ||||
|         self.row += 1; | ||||
|         Some(result) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::{ByteGrid, DataRef, Grid}; | ||||
|  | @ -184,4 +247,73 @@ mod tests { | |||
|         assert_eq!(vec.data, [1, 2, 3, 4]); | ||||
|         assert_eq!(vec.get(1, 0), 2) | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn iter() { | ||||
|         let mut vec = ByteGrid::new(2, 2); | ||||
|         vec.set(1, 1, 5); | ||||
| 
 | ||||
|         let mut iter = vec.iter(); | ||||
|         assert_eq!(*iter.next().unwrap(), 0); | ||||
|         assert_eq!(*iter.next().unwrap(), 0); | ||||
|         assert_eq!(*iter.next().unwrap(), 0); | ||||
|         assert_eq!(*iter.next().unwrap(), 5); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn iter_mut() { | ||||
|         let mut vec = ByteGrid::new(2, 3); | ||||
|         for (index, cell) in vec.iter_mut().enumerate() { | ||||
|             *cell = index as u8; | ||||
|         } | ||||
| 
 | ||||
|         assert_eq!(vec.data_ref(), [0, 1, 2, 3, 4, 5]); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn iter_rows() { | ||||
|         let vec = ByteGrid::load(2, 3, &[0, 1, 1, 2, 2, 3]); | ||||
|         for (y, row) in vec.iter_rows().enumerate() { | ||||
|             for (x, val) in row.enumerate() { | ||||
|                 assert_eq!(*val, (x + y) as u8); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn out_of_bounds_x() { | ||||
|         let mut vec = ByteGrid::load(2, 2, &[0, 1, 2, 3]); | ||||
|         vec.set(2, 1, 5); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn out_of_bounds_y() { | ||||
|         let vec = ByteGrid::load(2, 2, &[0, 1, 2, 3]); | ||||
|         vec.get(1, 2); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn ref_mut() { | ||||
|         let mut vec = ByteGrid::load(2, 2, &[0, 1, 2, 3]); | ||||
| 
 | ||||
|         let top_left = vec.get_ref_mut(0, 0); | ||||
|         *top_left += 5; | ||||
| 
 | ||||
|         assert_eq!(None, vec.get_ref_mut_optional(2, 2)); | ||||
|         assert_eq!(Some(&mut 5), vec.get_ref_mut_optional(0, 0)); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn optional() { | ||||
|         let mut grid = ByteGrid::load(2, 2, &[0, 1, 2, 3]); | ||||
|         grid.set_optional(0, 0, 5); | ||||
|         grid.set_optional(-1, 0, 8); | ||||
|         grid.set_optional(0, 8, 42); | ||||
|         assert_eq!(grid.data, [5, 1, 2, 3]); | ||||
| 
 | ||||
|         assert_eq!(grid.get_optional(0, 0), Some(5)); | ||||
|         assert_eq!(grid.get_optional(0, 8), None); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,9 @@ | |||
| use bitvec::prelude::BitVec; | ||||
| 
 | ||||
| use crate::command_code::CommandCode; | ||||
| use crate::compression::{into_compressed, into_decompressed}; | ||||
| use crate::{ | ||||
|     BitVec, ByteGrid, CompressionCode, Grid, Header, Packet, PixelGrid, | ||||
|     ByteGrid, CompressionCode, Grid, Header, Packet, PixelGrid, SpBitVec, | ||||
|     TILE_SIZE, | ||||
| }; | ||||
| 
 | ||||
|  | @ -43,16 +45,16 @@ pub enum Command { | |||
|     BitmapLegacy, | ||||
|     /// Set pixel data starting at the offset.
 | ||||
|     /// The contained `BitVec` is always uncompressed.
 | ||||
|     BitmapLinear(Offset, BitVec, CompressionCode), | ||||
|     BitmapLinear(Offset, SpBitVec, CompressionCode), | ||||
|     /// Set pixel data according to an and-mask starting at the offset.
 | ||||
|     /// The contained `BitVec` is always uncompressed.
 | ||||
|     BitmapLinearAnd(Offset, BitVec, CompressionCode), | ||||
|     BitmapLinearAnd(Offset, SpBitVec, CompressionCode), | ||||
|     /// Set pixel data according to an or-mask starting at the offset.
 | ||||
|     /// The contained `BitVec` is always uncompressed.
 | ||||
|     BitmapLinearOr(Offset, BitVec, CompressionCode), | ||||
|     BitmapLinearOr(Offset, SpBitVec, CompressionCode), | ||||
|     /// Set pixel data according to a xor-mask starting at the offset.
 | ||||
|     /// The contained `BitVec` is always uncompressed.
 | ||||
|     BitmapLinearXor(Offset, BitVec, CompressionCode), | ||||
|     BitmapLinearXor(Offset, SpBitVec, CompressionCode), | ||||
|     /// Show text on the screen. Note that the byte data has to be CP437 encoded.
 | ||||
|     Cp437Data(Origin, ByteGrid), | ||||
|     /// Sets a window of pixels to the specified values
 | ||||
|  | @ -366,7 +368,7 @@ impl Command { | |||
|     /// Helper method for Packets into `BitMapLinear*`-Commands
 | ||||
|     fn packet_into_linear_bitmap( | ||||
|         packet: Packet, | ||||
|     ) -> Result<(BitVec, CompressionCode), TryFromPacketError> { | ||||
|     ) -> Result<(SpBitVec, CompressionCode), TryFromPacketError> { | ||||
|         let Packet(Header(_, _, length, sub, reserved), payload) = packet; | ||||
|         if reserved != 0 { | ||||
|             return Err(TryFromPacketError::ExtraneousHeaderValues); | ||||
|  | @ -387,17 +389,18 @@ impl Command { | |||
|                 payload.len(), | ||||
|             )); | ||||
|         } | ||||
|         Ok((BitVec::from(&*payload), sub)) | ||||
|         Ok((BitVec::from_vec(payload), sub)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use bitvec::prelude::BitVec; | ||||
| 
 | ||||
|     use crate::command::TryFromPacketError; | ||||
|     use crate::command_code::CommandCode; | ||||
|     use crate::{ | ||||
|         BitVec, ByteGrid, Command, CompressionCode, Grid, Header, Origin, | ||||
|         Packet, PixelGrid, | ||||
|         ByteGrid, Command, CompressionCode, Header, Origin, Packet, PixelGrid, | ||||
|     }; | ||||
| 
 | ||||
|     fn round_trip(original: Command) { | ||||
|  | @ -462,20 +465,24 @@ mod tests { | |||
|     #[test] | ||||
|     fn round_trip_bitmap_linear() { | ||||
|         for compression in all_compressions().to_owned() { | ||||
|             round_trip(Command::BitmapLinear(23, BitVec::new(40), compression)); | ||||
|             round_trip(Command::BitmapLinear( | ||||
|                 23, | ||||
|                 BitVec::repeat(false, 40), | ||||
|                 compression, | ||||
|             )); | ||||
|             round_trip(Command::BitmapLinearAnd( | ||||
|                 23, | ||||
|                 BitVec::new(40), | ||||
|                 BitVec::repeat(false, 40), | ||||
|                 compression, | ||||
|             )); | ||||
|             round_trip(Command::BitmapLinearOr( | ||||
|                 23, | ||||
|                 BitVec::new(40), | ||||
|                 BitVec::repeat(false, 40), | ||||
|                 compression, | ||||
|             )); | ||||
|             round_trip(Command::BitmapLinearXor( | ||||
|                 23, | ||||
|                 BitVec::new(40), | ||||
|                 BitVec::repeat(false, 40), | ||||
|                 compression, | ||||
|             )); | ||||
|             round_trip(Command::BitmapLinearWin( | ||||
|  | @ -590,8 +597,12 @@ mod tests { | |||
|     #[test] | ||||
|     fn error_decompression_failed_and() { | ||||
|         for compression in all_compressions().to_owned() { | ||||
|             let p: Packet = | ||||
|                 Command::BitmapLinearAnd(0, BitVec::new(8), compression).into(); | ||||
|             let p: Packet = Command::BitmapLinearAnd( | ||||
|                 0, | ||||
|                 BitVec::repeat(false, 8), | ||||
|                 compression, | ||||
|             ) | ||||
|             .into(); | ||||
|             let Packet(header, mut payload) = p; | ||||
| 
 | ||||
|             // mangle it
 | ||||
|  | @ -633,7 +644,7 @@ mod tests { | |||
|     fn error_reserved_used() { | ||||
|         let Packet(header, payload) = Command::BitmapLinear( | ||||
|             0, | ||||
|             BitVec::new(8), | ||||
|             BitVec::repeat(false, 8), | ||||
|             CompressionCode::Uncompressed, | ||||
|         ) | ||||
|         .into(); | ||||
|  | @ -649,7 +660,7 @@ mod tests { | |||
|     fn error_invalid_compression() { | ||||
|         let Packet(header, payload) = Command::BitmapLinear( | ||||
|             0, | ||||
|             BitVec::new(8), | ||||
|             BitVec::repeat(false, 8), | ||||
|             CompressionCode::Uncompressed, | ||||
|         ) | ||||
|         .into(); | ||||
|  | @ -665,7 +676,7 @@ mod tests { | |||
|     fn error_unexpected_size() { | ||||
|         let Packet(header, payload) = Command::BitmapLinear( | ||||
|             0, | ||||
|             BitVec::new(8), | ||||
|             BitVec::repeat(false, 8), | ||||
|             CompressionCode::Uncompressed, | ||||
|         ) | ||||
|         .into(); | ||||
|  |  | |||
|  | @ -1,24 +1,58 @@ | |||
| /// A two-dimensional grid of `T`
 | ||||
| pub trait Grid<T> { | ||||
|     #[must_use] | ||||
|     /// Creates a new Grid with the specified dimensions.
 | ||||
|     /// Sets the value at the specified position
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// - width: size in x-direction
 | ||||
|     /// - height: size in y-direction
 | ||||
|     /// * `x` and `y`: position of the cell to read
 | ||||
|     ///
 | ||||
|     /// returns: Grid with all cells initialized to default state.
 | ||||
|     fn new(width: usize, height: usize) -> Self; | ||||
| 
 | ||||
|     /// Sets the value at the specified position
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// returns: the old value
 | ||||
|     fn set(&mut self, x: usize, y: usize, value: T) -> T; | ||||
|     /// When accessing `x` or `y` out of bounds.
 | ||||
|     fn set(&mut self, x: usize, y: usize, value: T); | ||||
| 
 | ||||
|     /// Get the current value at the specified position
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `x` and `y`: position of the cell to read
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// When accessing `x` or `y` out of bounds.
 | ||||
|     fn get(&self, x: usize, y: usize) -> T; | ||||
| 
 | ||||
|     /// Get the current value at the specified position if the position is inside of bounds
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `x` and `y`: position of the cell to read
 | ||||
|     ///
 | ||||
|     /// returns: Value at position or None
 | ||||
|     fn get_optional(&self, x: isize, y: isize) -> Option<T> { | ||||
|         if self.is_in_bounds(x, y) { | ||||
|             Some(self.get(x as usize, y as usize)) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Sets the value at the specified position if the position is inside of bounds
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `x` and `y`: position of the cell to read
 | ||||
|     ///
 | ||||
|     /// returns: the old value or None
 | ||||
|     fn set_optional(&mut self, x: isize, y: isize, value: T) -> bool { | ||||
|         if self.is_in_bounds(x, y) { | ||||
|             self.set(x as usize, y as usize, value); | ||||
|             true | ||||
|         } else { | ||||
|             false | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Sets all cells in the grid to the specified value
 | ||||
|     fn fill(&mut self, value: T); | ||||
| 
 | ||||
|  | @ -28,35 +62,29 @@ pub trait Grid<T> { | |||
|     /// the height in y-direction
 | ||||
|     fn height(&self) -> usize; | ||||
| 
 | ||||
|     /// Creates a new instance containing the specified window.
 | ||||
|     /// Checks whether the specified signed position is in grid bounds
 | ||||
|     fn is_in_bounds(&self, x: isize, y: isize) -> bool { | ||||
|         x >= 0 | ||||
|             && x < self.width() as isize | ||||
|             && y >= 0 | ||||
|             && y < self.height() as isize | ||||
|     } | ||||
| 
 | ||||
|     /// Asserts that the specified unsigned position is in grid bounds.
 | ||||
|     ///
 | ||||
|     /// Use concrete types to avoid boxing.
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `x`: column of the top left cell
 | ||||
|     /// * `y`: row of the top left cell
 | ||||
|     /// * `w`: size of window in x-direction
 | ||||
|     /// * `h`: size of window in y-direction
 | ||||
|     ///
 | ||||
|     /// returns: Self
 | ||||
|     ///
 | ||||
|     /// # Examples
 | ||||
|     /// To avoid boxing, this example is using the concrete type `ByteGrid`.
 | ||||
|     /// ```
 | ||||
|     /// use servicepoint::{ByteGrid, Grid};
 | ||||
|     /// fn split(grid: ByteGrid) -> (ByteGrid, ByteGrid) {
 | ||||
|     ///     assert!(grid.width() >= 2);
 | ||||
|     ///     let split_x = grid.width() / 2;
 | ||||
|     ///     let right_w = grid.width() - split_x;
 | ||||
|     ///
 | ||||
|     ///     let left = grid.window(0, 0, split_x, grid.height());
 | ||||
|     ///     let right = grid.window(split_x, 0, right_w, grid.height());
 | ||||
|     ///     (left, right)
 | ||||
|     /// }
 | ||||
|     ///
 | ||||
|     /// let (l, r) = split(ByteGrid::new(9, 5));
 | ||||
|     /// ```
 | ||||
|     #[must_use] | ||||
|     fn window(&self, x: usize, y: usize, w: usize, h: usize) -> Self; | ||||
|     /// When the specified position is out of bounds for this grid.
 | ||||
|     fn assert_in_bounds(&self, x: usize, y: usize) { | ||||
|         assert!( | ||||
|             x < self.width(), | ||||
|             "cannot access index [{x}, {y}] because x is outside of bounds 0..{}", | ||||
|             self.width() | ||||
|         ); | ||||
|         assert!( | ||||
|             y < self.height(), | ||||
|             "cannot access byte [{x}, {y}] because y is outside of bounds 0..{}", | ||||
|             self.height() | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -2,7 +2,9 @@ | |||
| 
 | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| pub use crate::bit_vec::BitVec; | ||||
| pub use bitvec; | ||||
| use bitvec::prelude::{BitVec, Msb0}; | ||||
| 
 | ||||
| pub use crate::byte_grid::ByteGrid; | ||||
| pub use crate::command::{Brightness, Command, Offset, Origin}; | ||||
| pub use crate::compression_code::CompressionCode; | ||||
|  | @ -12,7 +14,8 @@ pub use crate::grid::Grid; | |||
| pub use crate::packet::{Header, Packet, Payload}; | ||||
| pub use crate::pixel_grid::PixelGrid; | ||||
| 
 | ||||
| mod bit_vec; | ||||
| type SpBitVec = BitVec<u8, Msb0>; | ||||
| 
 | ||||
| mod byte_grid; | ||||
| mod command; | ||||
| mod command_code; | ||||
|  |  | |||
|  | @ -69,4 +69,10 @@ mod tests { | |||
|         let p = Packet::try_from(&*data).unwrap(); | ||||
|         assert_eq!(p, Packet(Header(0, 1, 2, 3, 4), vec![42u8; 23])); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn too_small() { | ||||
|         let data = vec![0u8; 4]; | ||||
|         assert_eq!(Packet::try_from(data.as_slice()), Err(())) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,14 +1,39 @@ | |||
| use crate::{BitVec, DataRef, Grid, PIXEL_HEIGHT, PIXEL_WIDTH}; | ||||
| use bitvec::order::Msb0; | ||||
| use bitvec::prelude::BitSlice; | ||||
| use bitvec::slice::IterMut; | ||||
| 
 | ||||
| use crate::{BitVec, DataRef, Grid, SpBitVec, PIXEL_HEIGHT, PIXEL_WIDTH}; | ||||
| 
 | ||||
| /// A grid of pixels stored in packed bytes.
 | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub struct PixelGrid { | ||||
|     width: usize, | ||||
|     height: usize, | ||||
|     bit_vec: BitVec, | ||||
|     bit_vec: SpBitVec, | ||||
| } | ||||
| 
 | ||||
| impl PixelGrid { | ||||
|     /// Creates a new `PixelGrid` with the specified dimensions.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `width`: size in pixels in x-direction
 | ||||
|     /// * `height`: size in pixels in y-direction
 | ||||
|     ///
 | ||||
|     /// returns: `PixelGrid` initialized to all pixels off
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// - when the width is not dividable by 8
 | ||||
|     pub fn new(width: usize, height: usize) -> Self { | ||||
|         assert_eq!(width % 8, 0); | ||||
|         Self { | ||||
|             width, | ||||
|             height, | ||||
|             bit_vec: BitVec::repeat(false, width * height), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Creates a new pixel grid with the size of the whole screen.
 | ||||
|     #[must_use] | ||||
|     pub fn max_sized() -> Self { | ||||
|  | @ -35,46 +60,63 @@ impl PixelGrid { | |||
|         Self { | ||||
|             width, | ||||
|             height, | ||||
|             bit_vec: BitVec::from(data), | ||||
|             bit_vec: BitVec::from_slice(data), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn check_indexes(&self, x: usize, y: usize) { | ||||
|         assert!( | ||||
|             x < self.width, | ||||
|             "cannot access pixel {x}-{y} because x is outside of bounds 0..{}", | ||||
|             self.width | ||||
|         ); | ||||
|         assert!( | ||||
|             y < self.height, | ||||
|             "cannot access pixel {x}-{y} because y is outside of bounds 0..{}", | ||||
|             self.height | ||||
|         ); | ||||
|     /// Iterate over all cells in `PixelGrid`.
 | ||||
|     ///
 | ||||
|     /// Order is equivalent to the following loop:
 | ||||
|     /// ```
 | ||||
|     /// # use servicepoint::{PixelGrid, Grid};
 | ||||
|     /// # let grid = PixelGrid::new(8,2);
 | ||||
|     /// for y in 0..grid.height() {
 | ||||
|     ///     for x in 0..grid.width() {
 | ||||
|     ///         grid.get(x, y);
 | ||||
|     ///     }
 | ||||
|     /// }
 | ||||
|     /// ```
 | ||||
|     pub fn iter(&self) -> impl Iterator<Item = &bool> { | ||||
|         self.bit_vec.iter().by_refs() | ||||
|     } | ||||
| 
 | ||||
|     /// Iterate over all cells in `PixelGrid` mutably.
 | ||||
|     ///
 | ||||
|     /// Order is equivalent to the following loop:
 | ||||
|     /// ```
 | ||||
|     /// # use servicepoint::{PixelGrid, Grid};
 | ||||
|     /// # let mut grid = PixelGrid::new(8,2);
 | ||||
|     /// # let value = false;
 | ||||
|     /// for y in 0..grid.height() {
 | ||||
|     ///     for x in 0..grid.width() {
 | ||||
|     ///         grid.set(x, y, value);
 | ||||
|     ///     }
 | ||||
|     /// }
 | ||||
|     /// ```
 | ||||
|     ///
 | ||||
|     /// # Example
 | ||||
|     /// ```
 | ||||
|     /// # use servicepoint::{PixelGrid, Grid};
 | ||||
|     /// # let mut grid = PixelGrid::new(8,2);
 | ||||
|     /// # let value = false;
 | ||||
|     /// for (index, mut pixel) in grid.iter_mut().enumerate() {
 | ||||
|     ///     pixel.set(index % 2 == 0)
 | ||||
|     /// }
 | ||||
|     /// ```
 | ||||
|     pub fn iter_mut(&mut self) -> IterMut<u8, Msb0> { | ||||
|         self.bit_vec.iter_mut() | ||||
|     } | ||||
| 
 | ||||
|     /// Iterate over all rows in `PixelGrid` top to bottom.
 | ||||
|     pub fn iter_rows(&self) -> IterRows { | ||||
|         IterRows { | ||||
|             pixel_grid: self, | ||||
|             row: 0, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Grid<bool> for PixelGrid { | ||||
|     /// Creates a new `PixelGrid` with the specified dimensions.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `width`: size in pixels in x-direction
 | ||||
|     /// * `height`: size in pixels in y-direction
 | ||||
|     ///
 | ||||
|     /// returns: `PixelGrid` initialized to all pixels off
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// - when the width is not dividable by 8
 | ||||
|     fn new(width: usize, height: usize) -> Self { | ||||
|         assert_eq!(width % 8, 0); | ||||
|         Self { | ||||
|             width, | ||||
|             height, | ||||
|             bit_vec: BitVec::new(width * height), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Sets the value of the specified position in the `PixelGrid`.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|  | @ -87,22 +129,14 @@ impl Grid<bool> for PixelGrid { | |||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// When accessing `x` or `y` out of bounds.
 | ||||
|     fn set(&mut self, x: usize, y: usize, value: bool) -> bool { | ||||
|         self.check_indexes(x, y); | ||||
|     fn set(&mut self, x: usize, y: usize, value: bool) { | ||||
|         self.assert_in_bounds(x, y); | ||||
|         self.bit_vec.set(x + y * self.width, value) | ||||
|     } | ||||
| 
 | ||||
|     /// Gets the current value at the specified position.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `x` and `y`: position of the cell to read
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     ///
 | ||||
|     /// When accessing `x` or `y` out of bounds.
 | ||||
|     fn get(&self, x: usize, y: usize) -> bool { | ||||
|         self.bit_vec.get(x + y * self.width) | ||||
|         self.assert_in_bounds(x, y); | ||||
|         self.bit_vec[x + y * self.width] | ||||
|     } | ||||
| 
 | ||||
|     /// Sets the state of all pixels in the `PixelGrid`.
 | ||||
|  | @ -122,28 +156,15 @@ impl Grid<bool> for PixelGrid { | |||
|     fn height(&self) -> usize { | ||||
|         self.height | ||||
|     } | ||||
| 
 | ||||
|     fn window(&self, x: usize, y: usize, w: usize, h: usize) -> Self { | ||||
|         // TODO: how to deduplicate?
 | ||||
|         // this cannot be moved into the trait because there, Self is not Sized
 | ||||
|         let mut win = Self::new(w, h); | ||||
|         for win_x in 0..w { | ||||
|             for win_y in 0..h { | ||||
|                 let value = self.get(x + win_x, y + win_y); | ||||
|                 win.set(win_x, win_y, value); | ||||
|             } | ||||
|         } | ||||
|         win | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl DataRef for PixelGrid { | ||||
|     fn data_ref_mut(&mut self) -> &mut [u8] { | ||||
|         self.bit_vec.data_ref_mut() | ||||
|         self.bit_vec.as_raw_mut_slice() | ||||
|     } | ||||
| 
 | ||||
|     fn data_ref(&self) -> &[u8] { | ||||
|         self.bit_vec.data_ref() | ||||
|         self.bit_vec.as_raw_slice() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -154,6 +175,26 @@ impl From<PixelGrid> for Vec<u8> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct IterRows<'t> { | ||||
|     pixel_grid: &'t PixelGrid, | ||||
|     row: usize, | ||||
| } | ||||
| 
 | ||||
| impl<'t> Iterator for IterRows<'t> { | ||||
|     type Item = &'t BitSlice<u8, Msb0>; | ||||
| 
 | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
|         if self.row >= self.pixel_grid.height { | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|         let start = self.row * self.pixel_grid.width; | ||||
|         let end = start + self.pixel_grid.width; | ||||
|         self.row += 1; | ||||
|         Some(&self.pixel_grid.bit_vec[start..end]) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::{DataRef, Grid, PixelGrid}; | ||||
|  | @ -201,4 +242,51 @@ mod tests { | |||
|         let grid = PixelGrid::load(8, 3, &data); | ||||
|         assert_eq!(grid.data_ref(), [0xAA, 0x55, 0xAA]); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn out_of_bounds_x() { | ||||
|         let vec = PixelGrid::new(8, 2); | ||||
|         vec.get(8, 1); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn out_of_bounds_y() { | ||||
|         let mut vec = PixelGrid::new(8, 2); | ||||
|         vec.set(1, 2, false); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn iter() { | ||||
|         let grid = PixelGrid::new(8, 2); | ||||
|         assert_eq!(16, grid.iter().count()) | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn iter_rows() { | ||||
|         let grid = PixelGrid::load(8, 2, &[0x04, 0x40]); | ||||
|         let mut iter = grid.iter_rows(); | ||||
| 
 | ||||
|         assert_eq!(iter.next().unwrap().count_ones(), 1); | ||||
|         assert_eq!(iter.next().unwrap().count_ones(), 1); | ||||
|         assert_eq!(None, iter.next()); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn iter_mut() { | ||||
|         let mut grid = PixelGrid::new(8, 2); | ||||
|         for (index, mut pixel) in grid.iter_mut().enumerate() { | ||||
|             pixel.set(index % 2 == 0); | ||||
|         } | ||||
|         assert_eq!(grid.data_ref(), [0xAA, 0xAA]); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn data_ref_mut() { | ||||
|         let mut grid = PixelGrid::new(8, 2); | ||||
|         let data = grid.data_ref_mut(); | ||||
|         data[1] = 0x0F; | ||||
|         assert!(grid.get(7, 1)); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -17,19 +17,19 @@ This crate contains C bindings for the `servicepoint` library, enabling users to | |||
| #include "servicepoint.h" | ||||
| 
 | ||||
| int main(void) { | ||||
|     sp2_Connection *connection = sp2_connection_open("localhost:2342"); | ||||
|     sp_Connection *connection = sp_connection_open("localhost:2342"); | ||||
|     if (connection == NULL) | ||||
|         return 1; | ||||
| 
 | ||||
|     sp2_PixelGrid *pixels = sp2_pixel_grid_new(sp2_PIXEL_WIDTH, sp2_PIXEL_HEIGHT); | ||||
|     sp2_pixel_grid_fill(pixels, true); | ||||
|     sp_PixelGrid *pixels = sp_pixel_grid_new(sp_PIXEL_WIDTH, sp_PIXEL_HEIGHT); | ||||
|     sp_pixel_grid_fill(pixels, true); | ||||
| 
 | ||||
|     sp2_Command *command = sp2_command_bitmap_linear_win(0, 0, pixels, Uncompressed); | ||||
|     sp2_Packet *packet = sp2_packet_from_command(command); | ||||
|     if (!sp2_connection_send(connection, packet)) | ||||
|     sp_Command *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed); | ||||
|     sp_Packet *packet = sp_packet_from_command(command); | ||||
|     if (!sp_connection_send(connection, packet)) | ||||
|         return 1; | ||||
| 
 | ||||
|     sp2_connection_dealloc(connection); | ||||
|     sp_connection_dealloc(connection); | ||||
|     return 0; | ||||
| } | ||||
| ``` | ||||
|  | @ -52,12 +52,12 @@ You have the choice of linking statically (recommended) or dynamically. | |||
| 
 | ||||
| ## Notes on differences to rust library | ||||
| 
 | ||||
| - function names are: `sp2_` \<struct_name\> \<rust name\>. | ||||
| - Use the rust documentation. | ||||
| - function names are: `sp_` \<struct_name\> \<rust name\>. | ||||
| - Instances get consumed in the same way they do when writing rust / C# code. Do not use an instance after an (implicit!) free. | ||||
| - Option<T> or Result<T, E> turn into nullable return values - check for NULL! | ||||
| - There are no specifics for C++ here yet. You might get a nicer header when generating directly for C++, but it should be usable. | ||||
| - Reading and writing to instances concurrently is not safe. Only reading concurrently is safe. | ||||
| - documentation is included in the header and available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/) | ||||
| 
 | ||||
| ## Everything else | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| const SP_INCLUDE: &str = "DEP_SERVICEPOINT_INCLUDE"; | ||||
| 
 | ||||
| fn main() { | ||||
|     println!("cargo:rerun-if-changed=src/main.c"); | ||||
|     println!("cargo:rerun-if-changed=build.rs"); | ||||
|     println!("cargo:rerun-if-env-changed={SP_INCLUDE}"); | ||||
|     println!("cargo::rerun-if-changed=src/main.c"); | ||||
|     println!("cargo::rerun-if-changed=build.rs"); | ||||
|     println!("cargo::rerun-if-env-changed={SP_INCLUDE}"); | ||||
| 
 | ||||
|     let sp_include = | ||||
|         std::env::var_os(SP_INCLUDE).unwrap().into_string().unwrap(); | ||||
|  |  | |||
|  | @ -2,9 +2,29 @@ | |||
| //!
 | ||||
| //! prefix `sp_bit_vec_`
 | ||||
| 
 | ||||
| use servicepoint::{BitVec, DataRef}; | ||||
| 
 | ||||
| use crate::c_slice::CByteSlice; | ||||
| use servicepoint::bitvec::prelude::{BitVec, Msb0}; | ||||
| 
 | ||||
| /// cbindgen:no-export
 | ||||
| type SpBitVec = BitVec<u8, Msb0>; | ||||
| 
 | ||||
| /// Opaque struct needed for correct code generation.
 | ||||
| #[derive(Clone)] | ||||
| pub struct CBitVec { | ||||
|     actual: SpBitVec, | ||||
| } | ||||
| 
 | ||||
| impl From<SpBitVec> for CBitVec { | ||||
|     fn from(actual: SpBitVec) -> Self { | ||||
|         Self { actual } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<CBitVec> for SpBitVec { | ||||
|     fn from(value: CBitVec) -> Self { | ||||
|         value.actual | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Creates a new `BitVec` instance.
 | ||||
| ///
 | ||||
|  | @ -25,8 +45,10 @@ use crate::c_slice::CByteSlice; | |||
| /// - the returned instance is freed in some way, either by using a consuming function or
 | ||||
| ///   by explicitly calling `sp_bit_vec_dealloc`.
 | ||||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_bit_vec_new(size: usize) -> *mut BitVec { | ||||
|     Box::into_raw(Box::new(BitVec::new(size))) | ||||
| pub unsafe extern "C" fn sp_bit_vec_new(size: usize) -> *mut CBitVec { | ||||
|     Box::into_raw(Box::new(CBitVec { | ||||
|         actual: SpBitVec::repeat(false, size), | ||||
|     })) | ||||
| } | ||||
| 
 | ||||
| /// Interpret the data as a series of bits and load then into a new `BitVec` instance.
 | ||||
|  | @ -43,9 +65,11 @@ pub unsafe extern "C" fn sp_bit_vec_new(size: usize) -> *mut BitVec { | |||
| pub unsafe extern "C" fn sp_bit_vec_load( | ||||
|     data: *const u8, | ||||
|     data_length: usize, | ||||
| ) -> *mut BitVec { | ||||
| ) -> *mut CBitVec { | ||||
|     let data = std::slice::from_raw_parts(data, data_length); | ||||
|     Box::into_raw(Box::new(BitVec::from(data))) | ||||
|     Box::into_raw(Box::new(CBitVec { | ||||
|         actual: SpBitVec::from_slice(data), | ||||
|     })) | ||||
| } | ||||
| 
 | ||||
| /// Clones a `BitVec`.
 | ||||
|  | @ -59,7 +83,9 @@ pub unsafe extern "C" fn sp_bit_vec_load( | |||
| /// - the returned instance is freed in some way, either by using a consuming function or
 | ||||
| ///   by explicitly calling `sp_bit_vec_dealloc`.
 | ||||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_bit_vec_clone(this: *const BitVec) -> *mut BitVec { | ||||
| pub unsafe extern "C" fn sp_bit_vec_clone( | ||||
|     this: *const CBitVec, | ||||
| ) -> *mut CBitVec { | ||||
|     Box::into_raw(Box::new((*this).clone())) | ||||
| } | ||||
| 
 | ||||
|  | @ -73,7 +99,7 @@ pub unsafe extern "C" fn sp_bit_vec_clone(this: *const BitVec) -> *mut BitVec { | |||
| /// - `this` is not used concurrently or after this call
 | ||||
| /// - `this` was not passed to another consuming function, e.g. to create a `Command`
 | ||||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_bit_vec_dealloc(this: *mut BitVec) { | ||||
| pub unsafe extern "C" fn sp_bit_vec_dealloc(this: *mut CBitVec) { | ||||
|     _ = Box::from_raw(this); | ||||
| } | ||||
| 
 | ||||
|  | @ -98,10 +124,10 @@ pub unsafe extern "C" fn sp_bit_vec_dealloc(this: *mut BitVec) { | |||
| /// - `this` is not written to concurrently
 | ||||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_bit_vec_get( | ||||
|     this: *const BitVec, | ||||
|     this: *const CBitVec, | ||||
|     index: usize, | ||||
| ) -> bool { | ||||
|     (*this).get(index) | ||||
|     *(*this).actual.get(index).unwrap() | ||||
| } | ||||
| 
 | ||||
| /// Sets the value of a bit in the `BitVec`.
 | ||||
|  | @ -126,11 +152,11 @@ pub unsafe extern "C" fn sp_bit_vec_get( | |||
| /// - `this` is not written to or read from concurrently
 | ||||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_bit_vec_set( | ||||
|     this: *mut BitVec, | ||||
|     this: *mut CBitVec, | ||||
|     index: usize, | ||||
|     value: bool, | ||||
| ) -> bool { | ||||
|     (*this).set(index, value) | ||||
| ) { | ||||
|     (*this).actual.set(index, value) | ||||
| } | ||||
| 
 | ||||
| /// Sets the value of all bits in the `BitVec`.
 | ||||
|  | @ -146,8 +172,8 @@ pub unsafe extern "C" fn sp_bit_vec_set( | |||
| /// - `this` points to a valid `BitVec`
 | ||||
| /// - `this` is not written to or read from concurrently
 | ||||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_bit_vec_fill(this: *mut BitVec, value: bool) { | ||||
|     (*this).fill(value) | ||||
| pub unsafe extern "C" fn sp_bit_vec_fill(this: *mut CBitVec, value: bool) { | ||||
|     (*this).actual.fill(value) | ||||
| } | ||||
| 
 | ||||
| /// Gets the length of the `BitVec` in bits.
 | ||||
|  | @ -158,8 +184,8 @@ pub unsafe extern "C" fn sp_bit_vec_fill(this: *mut BitVec, value: bool) { | |||
| ///
 | ||||
| /// - `this` points to a valid `BitVec`
 | ||||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_bit_vec_len(this: *const BitVec) -> usize { | ||||
|     (*this).len() | ||||
| pub unsafe extern "C" fn sp_bit_vec_len(this: *const CBitVec) -> usize { | ||||
|     (*this).actual.len() | ||||
| } | ||||
| 
 | ||||
| /// Returns true if length is 0.
 | ||||
|  | @ -170,8 +196,8 @@ pub unsafe extern "C" fn sp_bit_vec_len(this: *const BitVec) -> usize { | |||
| ///
 | ||||
| /// - `this` points to a valid `BitVec`
 | ||||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_bit_vec_is_empty(this: *const BitVec) -> bool { | ||||
|     (*this).is_empty() | ||||
| pub unsafe extern "C" fn sp_bit_vec_is_empty(this: *const CBitVec) -> bool { | ||||
|     (*this).actual.is_empty() | ||||
| } | ||||
| 
 | ||||
| /// Gets an unsafe reference to the data of the `BitVec` instance.
 | ||||
|  | @ -185,9 +211,9 @@ pub unsafe extern "C" fn sp_bit_vec_is_empty(this: *const BitVec) -> bool { | |||
| /// - the returned memory range is never accessed concurrently, either via the `BitVec` or directly
 | ||||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_bit_vec_unsafe_data_ref( | ||||
|     this: *mut BitVec, | ||||
|     this: *mut CBitVec, | ||||
| ) -> CByteSlice { | ||||
|     let data = (*this).data_ref_mut(); | ||||
|     let data = (*this).actual.as_raw_mut_slice(); | ||||
|     CByteSlice { | ||||
|         start: data.as_mut_ptr_range().start, | ||||
|         length: data.len(), | ||||
|  |  | |||
|  | @ -1,3 +1,5 @@ | |||
| //! FFI slice helper
 | ||||
| 
 | ||||
| #[repr(C)] | ||||
| /// Represents a span of memory (`&mut [u8]` ) as a struct usable by C code.
 | ||||
| ///
 | ||||
|  |  | |||
|  | @ -5,10 +5,12 @@ | |||
| use std::ptr::null_mut; | ||||
| 
 | ||||
| use servicepoint::{ | ||||
|     BitVec, Brightness, ByteGrid, Command, CompressionCode, Offset, Origin, | ||||
|     Packet, PixelGrid, | ||||
|     Brightness, ByteGrid, Command, CompressionCode, Offset, Origin, Packet, | ||||
|     PixelGrid, | ||||
| }; | ||||
| 
 | ||||
| use crate::bit_vec::CBitVec; | ||||
| 
 | ||||
| /// Tries to turn a `Packet` into a `Command`. The packet is deallocated in the process.
 | ||||
| ///
 | ||||
| /// Returns: pointer to new `Command` instance or NULL
 | ||||
|  | @ -140,13 +142,13 @@ pub unsafe extern "C" fn sp_command_char_brightness( | |||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_command_bitmap_linear( | ||||
|     offset: Offset, | ||||
|     bit_vec: *mut BitVec, | ||||
|     bit_vec: *mut CBitVec, | ||||
|     compression: CompressionCode, | ||||
| ) -> *mut Command { | ||||
|     let bit_vec = *Box::from_raw(bit_vec); | ||||
|     Box::into_raw(Box::new(Command::BitmapLinear( | ||||
|         offset, | ||||
|         bit_vec, | ||||
|         bit_vec.into(), | ||||
|         compression, | ||||
|     ))) | ||||
| } | ||||
|  | @ -166,13 +168,13 @@ pub unsafe extern "C" fn sp_command_bitmap_linear( | |||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_command_bitmap_linear_and( | ||||
|     offset: Offset, | ||||
|     bit_vec: *mut BitVec, | ||||
|     bit_vec: *mut CBitVec, | ||||
|     compression: CompressionCode, | ||||
| ) -> *mut Command { | ||||
|     let bit_vec = *Box::from_raw(bit_vec); | ||||
|     Box::into_raw(Box::new(Command::BitmapLinearAnd( | ||||
|         offset, | ||||
|         bit_vec, | ||||
|         bit_vec.into(), | ||||
|         compression, | ||||
|     ))) | ||||
| } | ||||
|  | @ -192,13 +194,13 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_and( | |||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_command_bitmap_linear_or( | ||||
|     offset: Offset, | ||||
|     bit_vec: *mut BitVec, | ||||
|     bit_vec: *mut CBitVec, | ||||
|     compression: CompressionCode, | ||||
| ) -> *mut Command { | ||||
|     let bit_vec = *Box::from_raw(bit_vec); | ||||
|     Box::into_raw(Box::new(Command::BitmapLinearOr( | ||||
|         offset, | ||||
|         bit_vec, | ||||
|         bit_vec.into(), | ||||
|         compression, | ||||
|     ))) | ||||
| } | ||||
|  | @ -218,13 +220,13 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_or( | |||
| #[no_mangle] | ||||
| pub unsafe extern "C" fn sp_command_bitmap_linear_xor( | ||||
|     offset: Offset, | ||||
|     bit_vec: *mut BitVec, | ||||
|     bit_vec: *mut CBitVec, | ||||
|     compression: CompressionCode, | ||||
| ) -> *mut Command { | ||||
|     let bit_vec = *Box::from_raw(bit_vec); | ||||
|     Box::into_raw(Box::new(Command::BitmapLinearXor( | ||||
|         offset, | ||||
|         bit_vec, | ||||
|         bit_vec.into(), | ||||
|         compression, | ||||
|     ))) | ||||
| } | ||||
|  |  | |||
|  | @ -22,4 +22,4 @@ pub mod pixel_grid; | |||
| /// The minimum time needed for the display to refresh the screen in ms.
 | ||||
| pub const FRAME_PACING_MS: u32 = servicepoint::FRAME_PACING.as_millis() as u32; | ||||
| 
 | ||||
| mod c_slice; | ||||
| pub mod c_slice; | ||||
|  |  | |||
|  | @ -4,6 +4,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePoint", "ServicePoin | |||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "lang-cs", "examples/lang_cs/lang_cs.csproj", "{DA3B8B6E-993A-47DA-844B-F92AF520FF59}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{C2F8EC4A-2426-4DC3-990F-C43810B183F5}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		..\..\shell.nix = ..\..\shell.nix | ||||
| 		..\..\.envrc = ..\..\.envrc | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
|  |  | |||
|  | @ -21,46 +21,45 @@ namespace ServicePoint.BindGen | |||
| 
 | ||||
|         /// <summary>Creates a new `BitVec` instance.  # Arguments  * `size`: size in bits.  returns: `BitVec` with all bits set to false.  # Panics  When `size` is not divisible by 8.  # Safety  The caller has to make sure that:  - the returned instance is freed in some way, either by using a consuming function or by explicitly calling `sp_bit_vec_dealloc`.</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_bit_vec_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern BitVec* sp_bit_vec_new(nuint size); | ||||
|         public static extern CBitVec* sp_bit_vec_new(nuint size); | ||||
| 
 | ||||
|         /// <summary>Interpret the data as a series of bits and load then into a new `BitVec` instance.  # Safety  The caller has to make sure that:  - `data` points to a valid memory location of at least `data_length` bytes in size. - the returned instance is freed in some way, either by using a consuming function or by explicitly calling `sp_bit_vec_dealloc`.</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_bit_vec_load", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern BitVec* sp_bit_vec_load(byte* data, nuint data_length); | ||||
|         public static extern CBitVec* sp_bit_vec_load(byte* data, nuint data_length); | ||||
| 
 | ||||
|         /// <summary>Clones a `BitVec`.  # Safety  The caller has to make sure that:  - `this` points to a valid `BitVec` - `this` is not written to concurrently - the returned instance is freed in some way, either by using a consuming function or by explicitly calling `sp_bit_vec_dealloc`.</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_bit_vec_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern BitVec* sp_bit_vec_clone(BitVec* @this); | ||||
|         public static extern CBitVec* sp_bit_vec_clone(CBitVec* @this); | ||||
| 
 | ||||
|         /// <summary>Deallocates a `BitVec`.  # Safety  The caller has to make sure that:  - `this` points to a valid `BitVec` - `this` is not used concurrently or after this call - `this` was not passed to another consuming function, e.g. to create a `Command`</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_bit_vec_dealloc", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern void sp_bit_vec_dealloc(BitVec* @this); | ||||
|         public static extern void sp_bit_vec_dealloc(CBitVec* @this); | ||||
| 
 | ||||
|         /// <summary>Gets the value of a bit from the `BitVec`.  # Arguments  * `this`: instance to read from * `index`: the bit index to read  returns: value of the bit  # Panics  When accessing `index` out of bounds.  # Safety  The caller has to make sure that:  - `this` points to a valid `BitVec` - `this` is not written to concurrently</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_bit_vec_get", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         [return: MarshalAs(UnmanagedType.U1)] | ||||
|         public static extern bool sp_bit_vec_get(BitVec* @this, nuint index); | ||||
|         public static extern bool sp_bit_vec_get(CBitVec* @this, nuint index); | ||||
| 
 | ||||
|         /// <summary>Sets the value of a bit in the `BitVec`.  # Arguments  * `this`: instance to write to * `index`: the bit index to edit * `value`: the value to set the bit to  returns: old value of the bit  # Panics  When accessing `index` out of bounds.  # Safety  The caller has to make sure that:  - `this` points to a valid `BitVec` - `this` is not written to or read from concurrently</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_bit_vec_set", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         [return: MarshalAs(UnmanagedType.U1)] | ||||
|         public static extern bool sp_bit_vec_set(BitVec* @this, nuint index, [MarshalAs(UnmanagedType.U1)] bool value); | ||||
|         public static extern void sp_bit_vec_set(CBitVec* @this, nuint index, [MarshalAs(UnmanagedType.U1)] bool value); | ||||
| 
 | ||||
|         /// <summary>Sets the value of all bits in the `BitVec`.  # Arguments  * `value`: the value to set all bits to  # Safety  The caller has to make sure that:  - `this` points to a valid `BitVec` - `this` is not written to or read from concurrently</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_bit_vec_fill", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern void sp_bit_vec_fill(BitVec* @this, [MarshalAs(UnmanagedType.U1)] bool value); | ||||
|         public static extern void sp_bit_vec_fill(CBitVec* @this, [MarshalAs(UnmanagedType.U1)] bool value); | ||||
| 
 | ||||
|         /// <summary>Gets the length of the `BitVec` in bits.  # Safety  The caller has to make sure that:  - `this` points to a valid `BitVec`</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_bit_vec_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern nuint sp_bit_vec_len(BitVec* @this); | ||||
|         public static extern nuint sp_bit_vec_len(CBitVec* @this); | ||||
| 
 | ||||
|         /// <summary>Returns true if length is 0.  # Safety  The caller has to make sure that:  - `this` points to a valid `BitVec`</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_bit_vec_is_empty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         [return: MarshalAs(UnmanagedType.U1)] | ||||
|         public static extern bool sp_bit_vec_is_empty(BitVec* @this); | ||||
|         public static extern bool sp_bit_vec_is_empty(CBitVec* @this); | ||||
| 
 | ||||
|         /// <summary>Gets an unsafe reference to the data of the `BitVec` instance.  ## Safety  The caller has to make sure that:  - `this` points to a valid `BitVec` - the returned memory range is never accessed after the passed `BitVec` has been freed - the returned memory range is never accessed concurrently, either via the `BitVec` or directly</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_bit_vec_unsafe_data_ref", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern CByteSlice sp_bit_vec_unsafe_data_ref(BitVec* @this); | ||||
|         public static extern CByteSlice sp_bit_vec_unsafe_data_ref(CBitVec* @this); | ||||
| 
 | ||||
|         /// <summary>Creates a new `ByteGrid` with the specified dimensions.  returns: `ByteGrid` initialized to 0.  # Safety  The caller has to make sure that:  - the returned instance is freed in some way, either by using a consuming function or by explicitly calling `sp_byte_grid_dealloc`.</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_byte_grid_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|  | @ -132,19 +131,19 @@ namespace ServicePoint.BindGen | |||
| 
 | ||||
|         /// <summary>Allocates a new `Command::BitmapLinear` instance. The passed `BitVec` gets consumed.  # Safety  The caller has to make sure that:  - `bit_vec` points to a valid instance of `BitVec` - `bit_vec` is not used concurrently or after this call - `compression` matches one of the allowed enum values - the returned `Command` instance is freed in some way, either by using a consuming function or by explicitly calling `sp_command_dealloc`.</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_command_bitmap_linear", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern Command* sp_command_bitmap_linear(nuint offset, BitVec* bit_vec, CompressionCode compression); | ||||
|         public static extern Command* sp_command_bitmap_linear(nuint offset, CBitVec* bit_vec, CompressionCode compression); | ||||
| 
 | ||||
|         /// <summary>Allocates a new `Command::BitmapLinearAnd` instance. The passed `BitVec` gets consumed.  # Safety  The caller has to make sure that:  - `bit_vec` points to a valid instance of `BitVec` - `bit_vec` is not used concurrently or after this call - `compression` matches one of the allowed enum values - the returned `Command` instance is freed in some way, either by using a consuming function or by explicitly calling `sp_command_dealloc`.</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_command_bitmap_linear_and", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern Command* sp_command_bitmap_linear_and(nuint offset, BitVec* bit_vec, CompressionCode compression); | ||||
|         public static extern Command* sp_command_bitmap_linear_and(nuint offset, CBitVec* bit_vec, CompressionCode compression); | ||||
| 
 | ||||
|         /// <summary>Allocates a new `Command::BitmapLinearOr` instance. The passed `BitVec` gets consumed.  # Safety  The caller has to make sure that:  - `bit_vec` points to a valid instance of `BitVec` - `bit_vec` is not used concurrently or after this call - `compression` matches one of the allowed enum values - the returned `Command` instance is freed in some way, either by using a consuming function or by explicitly calling `sp_command_dealloc`.</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_command_bitmap_linear_or", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern Command* sp_command_bitmap_linear_or(nuint offset, BitVec* bit_vec, CompressionCode compression); | ||||
|         public static extern Command* sp_command_bitmap_linear_or(nuint offset, CBitVec* bit_vec, CompressionCode compression); | ||||
| 
 | ||||
|         /// <summary>Allocates a new `Command::BitmapLinearXor` instance. The passed `BitVec` gets consumed.  # Safety  The caller has to make sure that:  - `bit_vec` points to a valid instance of `BitVec` - `bit_vec` is not used concurrently or after this call - `compression` matches one of the allowed enum values - the returned `Command` instance is freed in some way, either by using a consuming function or by explicitly calling `sp_command_dealloc`.</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_command_bitmap_linear_xor", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|         public static extern Command* sp_command_bitmap_linear_xor(nuint offset, BitVec* bit_vec, CompressionCode compression); | ||||
|         public static extern Command* sp_command_bitmap_linear_xor(nuint offset, CBitVec* bit_vec, CompressionCode compression); | ||||
| 
 | ||||
|         /// <summary>Allocates a new `Command::Cp437Data` instance. The passed `ByteGrid` gets consumed.  # Safety  The caller has to make sure that:  - `byte_grid` points to a valid instance of `ByteGrid` - `byte_grid` is not used concurrently or after this call - the returned `Command` instance is freed in some way, either by using a consuming function or by explicitly calling `sp_command_dealloc`.</summary> | ||||
|         [DllImport(__DllName, EntryPoint = "sp_command_cp437_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] | ||||
|  | @ -227,6 +226,11 @@ namespace ServicePoint.BindGen | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     [StructLayout(LayoutKind.Sequential)] | ||||
|     public unsafe partial struct CBitVec | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [StructLayout(LayoutKind.Sequential)] | ||||
|     public unsafe partial struct CByteSlice | ||||
|     { | ||||
|  | @ -234,11 +238,6 @@ namespace ServicePoint.BindGen | |||
|         public nuint length; | ||||
|     } | ||||
| 
 | ||||
|     [StructLayout(LayoutKind.Sequential)] | ||||
|     public unsafe partial struct BitVec | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [StructLayout(LayoutKind.Sequential)] | ||||
|     public unsafe partial struct ByteGrid | ||||
|     { | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ using ServicePoint.BindGen; | |||
| 
 | ||||
| namespace ServicePoint; | ||||
| 
 | ||||
| public sealed class BitVec : SpNativeInstance<BindGen.BitVec> | ||||
| public sealed class BitVec : SpNativeInstance<BindGen.CBitVec> | ||||
| { | ||||
|     public static BitVec New(int size) | ||||
|     { | ||||
|  | @ -80,7 +80,7 @@ public sealed class BitVec : SpNativeInstance<BindGen.BitVec> | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private unsafe BitVec(BindGen.BitVec* instance) : base(instance) | ||||
|     private unsafe BitVec(BindGen.CBitVec* instance) : base(instance) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,21 +27,15 @@ | |||
|     </PropertyGroup> | ||||
| 
 | ||||
|     <!-- generate C# bindings --> | ||||
|     <Target Name="BuildBindings" Condition="'$(Configuration)'=='Release'" BeforeTargets="Build"> | ||||
|     <Target Name="BuildBindings" Condition="'$(Configuration)'=='Release'" BeforeTargets="PrepareForBuild"> | ||||
|         <Exec Command="cargo build --release"/> | ||||
|         <Exec Command="cargo build --manifest-path ../../../crates/servicepoint_binding_c/Cargo.toml --release"/> | ||||
|     </Target> | ||||
|     <Target Name="BuildBindings" Condition="'$(Configuration)'=='Debug'" BeforeTargets="Build"> | ||||
|     <Target Name="BuildBindings" Condition="'$(Configuration)'=='Debug'" BeforeTargets="PrepareForBuild"> | ||||
|         <Exec Command="cargo build"/> | ||||
|         <Exec Command="cargo build --manifest-path ../../../crates/servicepoint_binding_c/Cargo.toml"/> | ||||
|     </Target> | ||||
| 
 | ||||
|     <!-- build native library to include in output --> | ||||
|     <Target Name="BuildLibrary" Condition="'$(Configuration)'=='Release'" BeforeTargets="Build"> | ||||
|         <Exec Command="cargo build --manifest-path ../../../crates/servicepoint/Cargo.toml --release"/> | ||||
|     </Target> | ||||
|     <Target Name="BuildLibrary" Condition="'$(Configuration)'=='Debug'" BeforeTargets="Build"> | ||||
|         <Exec Command="cargo build --manifest-path ../../../crates/servicepoint/Cargo.toml"/> | ||||
|     </Target> | ||||
| 
 | ||||
|      | ||||
|     <!-- include native binary in output --> | ||||
|     <ItemGroup Condition="'$(Configuration)'=='Debug'"> | ||||
|         <Content Include="../../../target/debug/libservicepoint_binding_c.so" CopyToOutputDirectory="Always"> | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| //! Build script generating the C# code needed to call methods from the `servicepoint` C library.
 | ||||
| 
 | ||||
| fn main() { | ||||
|     println!("cargo:rerun-if-changed=../servicepoint_binding_c/src"); | ||||
|     println!("cargo:rerun-if-changed=build.rs"); | ||||
|     println!("cargo::rerun-if-changed=../servicepoint_binding_c/src"); | ||||
|     println!("cargo::rerun-if-changed=build.rs"); | ||||
|     csbindgen::Builder::default() | ||||
|         .input_extern_file("../servicepoint_binding_c/src/bit_vec.rs") | ||||
|         .input_extern_file("../servicepoint_binding_c/src/byte_grid.rs") | ||||
|  | @ -12,7 +12,6 @@ fn main() { | |||
|         .input_extern_file("../servicepoint_binding_c/src/lib.rs") | ||||
|         .input_extern_file("../servicepoint_binding_c/src/c_slice.rs") | ||||
|         .input_extern_file("../servicepoint_binding_c/src/packet.rs") | ||||
|         .input_extern_file("../servicepoint/src/bit_vec.rs") | ||||
|         .input_extern_file("../servicepoint/src/byte_grid.rs") | ||||
|         .input_extern_file("../servicepoint/src/command.rs") | ||||
|         .input_extern_file("../servicepoint/src/connection.rs") | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter