diff --git a/Cargo.lock b/Cargo.lock index 0b18f51..c0e062c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/crates/servicepoint/Cargo.toml b/crates/servicepoint/Cargo.toml index 5e748e3..ac2698f 100644 --- a/crates/servicepoint/Cargo.toml +++ b/crates/servicepoint/Cargo.toml @@ -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 } diff --git a/crates/servicepoint/examples/brightness_tester.rs b/crates/servicepoint/examples/brightness_tester.rs new file mode 100644 index 0000000..b1b1ac3 --- /dev/null +++ b/crates/servicepoint/examples/brightness_tester.rs @@ -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"); +} diff --git a/crates/servicepoint/examples/wiping_clear.rs b/crates/servicepoint/examples/wiping_clear.rs index 1295fc4..fdf84e3 100644 --- a/crates/servicepoint/examples/wiping_clear.rs +++ b/crates/servicepoint/examples/wiping_clear.rs @@ -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 = 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)) diff --git a/crates/servicepoint/src/bit_vec.rs b/crates/servicepoint/src/bit_vec.rs deleted file mode 100644 index 7b2d5c1..0000000 --- a/crates/servicepoint/src/bit_vec.rs +++ /dev/null @@ -1,228 +0,0 @@ -use crate::DataRef; - -/// A fixed-size vector of bits -#[derive(Debug, Clone, PartialEq)] -pub struct BitVec { - size: usize, - data: Vec, -} - -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 for Vec { - /// Turns the `BitVec` into the underlying `Vec` - 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 = 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:?}"); - } -} diff --git a/crates/servicepoint/src/byte_grid.rs b/crates/servicepoint/src/byte_grid.rs index d9b515c..74b9e3c 100644 --- a/crates/servicepoint/src/byte_grid.rs +++ b/crates/servicepoint/src/byte_grid.rs @@ -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 { + 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 { + 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 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 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 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 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 for Vec { } } +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 { + 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); + } } diff --git a/crates/servicepoint/src/command.rs b/crates/servicepoint/src/command.rs index 4e653eb..b488426 100644 --- a/crates/servicepoint/src/command.rs +++ b/crates/servicepoint/src/command.rs @@ -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(); diff --git a/crates/servicepoint/src/grid.rs b/crates/servicepoint/src/grid.rs index 1a53f77..7e71532 100644 --- a/crates/servicepoint/src/grid.rs +++ b/crates/servicepoint/src/grid.rs @@ -1,24 +1,58 @@ /// A two-dimensional grid of `T` pub trait Grid { - #[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 { + 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 { /// 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() + ); + } } diff --git a/crates/servicepoint/src/lib.rs b/crates/servicepoint/src/lib.rs index d68801c..dcb2755 100644 --- a/crates/servicepoint/src/lib.rs +++ b/crates/servicepoint/src/lib.rs @@ -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; + mod byte_grid; mod command; mod command_code; diff --git a/crates/servicepoint/src/packet.rs b/crates/servicepoint/src/packet.rs index d6cb78b..5ad80f1 100644 --- a/crates/servicepoint/src/packet.rs +++ b/crates/servicepoint/src/packet.rs @@ -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(())) + } } diff --git a/crates/servicepoint/src/pixel_grid.rs b/crates/servicepoint/src/pixel_grid.rs index 2b7ec20..f8c34b4 100644 --- a/crates/servicepoint/src/pixel_grid.rs +++ b/crates/servicepoint/src/pixel_grid.rs @@ -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 { + 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 { + 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 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 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 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 for Vec { } } +pub struct IterRows<'t> { + pixel_grid: &'t PixelGrid, + row: usize, +} + +impl<'t> Iterator for IterRows<'t> { + type Item = &'t BitSlice; + + fn next(&mut self) -> Option { + 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)); + } } diff --git a/crates/servicepoint_binding_c/README.md b/crates/servicepoint_binding_c/README.md index fb2c70c..eacd65d 100644 --- a/crates/servicepoint_binding_c/README.md +++ b/crates/servicepoint_binding_c/README.md @@ -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_` \ \. -- Use the rust documentation. +- function names are: `sp_` \ \. - Instances get consumed in the same way they do when writing rust / C# code. Do not use an instance after an (implicit!) free. - Option or Result 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 diff --git a/crates/servicepoint_binding_c/examples/lang_c/build.rs b/crates/servicepoint_binding_c/examples/lang_c/build.rs index 279439f..db090d4 100644 --- a/crates/servicepoint_binding_c/examples/lang_c/build.rs +++ b/crates/servicepoint_binding_c/examples/lang_c/build.rs @@ -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(); diff --git a/crates/servicepoint_binding_c/src/bit_vec.rs b/crates/servicepoint_binding_c/src/bit_vec.rs index ba4233d..2592aea 100644 --- a/crates/servicepoint_binding_c/src/bit_vec.rs +++ b/crates/servicepoint_binding_c/src/bit_vec.rs @@ -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; + +/// Opaque struct needed for correct code generation. +#[derive(Clone)] +pub struct CBitVec { + actual: SpBitVec, +} + +impl From for CBitVec { + fn from(actual: SpBitVec) -> Self { + Self { actual } + } +} + +impl From 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(), diff --git a/crates/servicepoint_binding_c/src/c_slice.rs b/crates/servicepoint_binding_c/src/c_slice.rs index b255c1b..566213d 100644 --- a/crates/servicepoint_binding_c/src/c_slice.rs +++ b/crates/servicepoint_binding_c/src/c_slice.rs @@ -1,3 +1,5 @@ +//! FFI slice helper + #[repr(C)] /// Represents a span of memory (`&mut [u8]` ) as a struct usable by C code. /// diff --git a/crates/servicepoint_binding_c/src/command.rs b/crates/servicepoint_binding_c/src/command.rs index 0c981f6..400a6ee 100644 --- a/crates/servicepoint_binding_c/src/command.rs +++ b/crates/servicepoint_binding_c/src/command.rs @@ -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, ))) } diff --git a/crates/servicepoint_binding_c/src/lib.rs b/crates/servicepoint_binding_c/src/lib.rs index 47ce5cb..7aaa92e 100644 --- a/crates/servicepoint_binding_c/src/lib.rs +++ b/crates/servicepoint_binding_c/src/lib.rs @@ -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; diff --git a/crates/servicepoint_binding_cs/ServicePoint.sln b/crates/servicepoint_binding_cs/ServicePoint.sln index 3d278f9..c4d9165 100644 --- a/crates/servicepoint_binding_cs/ServicePoint.sln +++ b/crates/servicepoint_binding_cs/ServicePoint.sln @@ -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 diff --git a/crates/servicepoint_binding_cs/ServicePoint/BindGen/ServicePoint.g.cs b/crates/servicepoint_binding_cs/ServicePoint/BindGen/ServicePoint.g.cs index 98fe381..5e958ad 100644 --- a/crates/servicepoint_binding_cs/ServicePoint/BindGen/ServicePoint.g.cs +++ b/crates/servicepoint_binding_cs/ServicePoint/BindGen/ServicePoint.g.cs @@ -21,46 +21,45 @@ namespace ServicePoint.BindGen /// 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`. [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); /// 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`. [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); /// 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`. [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); /// 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` [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); /// 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 [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); /// 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 [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); /// 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 [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); /// Gets the length of the `BitVec` in bits. # Safety The caller has to make sure that: - `this` points to a valid `BitVec` [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); /// Returns true if length is 0. # Safety The caller has to make sure that: - `this` points to a valid `BitVec` [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); /// 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 [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); /// 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`. [DllImport(__DllName, EntryPoint = "sp_byte_grid_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] @@ -132,19 +131,19 @@ namespace ServicePoint.BindGen /// 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`. [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); /// 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`. [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); /// 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`. [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); /// 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`. [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); /// 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`. [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 { diff --git a/crates/servicepoint_binding_cs/ServicePoint/BitVec.cs b/crates/servicepoint_binding_cs/ServicePoint/BitVec.cs index 13b2200..53ee65c 100644 --- a/crates/servicepoint_binding_cs/ServicePoint/BitVec.cs +++ b/crates/servicepoint_binding_cs/ServicePoint/BitVec.cs @@ -2,7 +2,7 @@ using ServicePoint.BindGen; namespace ServicePoint; -public sealed class BitVec : SpNativeInstance +public sealed class BitVec : SpNativeInstance { public static BitVec New(int size) { @@ -80,7 +80,7 @@ public sealed class BitVec : SpNativeInstance } } - private unsafe BitVec(BindGen.BitVec* instance) : base(instance) + private unsafe BitVec(BindGen.CBitVec* instance) : base(instance) { } diff --git a/crates/servicepoint_binding_cs/ServicePoint/ServicePoint.csproj b/crates/servicepoint_binding_cs/ServicePoint/ServicePoint.csproj index 94691e7..103497f 100644 --- a/crates/servicepoint_binding_cs/ServicePoint/ServicePoint.csproj +++ b/crates/servicepoint_binding_cs/ServicePoint/ServicePoint.csproj @@ -27,21 +27,15 @@ - + + - + + - - - - - - - - - + diff --git a/crates/servicepoint_binding_cs/build.rs b/crates/servicepoint_binding_cs/build.rs index 7f64dcf..49747b2 100644 --- a/crates/servicepoint_binding_cs/build.rs +++ b/crates/servicepoint_binding_cs/build.rs @@ -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")