byte-engine-utils 0.1.0

Shared allocation, async, synchronization, collection, and geometry utilities for Byte-Engine.
#[derive(Debug, Clone, Copy)]
pub struct BitArray<const N: usize>
where
	[u8; N.div_ceil(8)]:,
{
	data: [u8; N.div_ceil(8)],
}

impl<const N: usize> Default for BitArray<N>
where
	[u8; N.div_ceil(8)]:,
{
	fn default() -> Self {
		Self::new()
	}
}

impl<const N: usize> BitArray<N>
where
	[u8; N.div_ceil(8)]:,
{
	pub fn new() -> Self {
		Self {
			data: [0; N.div_ceil(8)],
		}
	}

	pub fn set(&mut self, index: usize, value: bool) {
		assert!(
			index < N,
			"Bit index is out of bounds. The most likely cause is an index greater than or equal to the bit-array length."
		);
		let byte_index = index / 8;
		let bit_index = index % 8;
		let mask = 1 << bit_index;
		if value {
			self.data[byte_index] |= mask;
		} else {
			self.data[byte_index] &= !mask;
		}
	}

	pub fn get(&self, index: usize) -> bool {
		assert!(
			index < N,
			"Bit index is out of bounds. The most likely cause is an index greater than or equal to the bit-array length."
		);
		let byte_index = index / 8;
		let bit_index = index % 8;
		let mask = 1 << bit_index;
		(self.data[byte_index] & mask) != 0
	}
}

#[cfg(test)]
mod tests {
	use super::BitArray;

	#[test]
	fn bits_are_independent_across_byte_boundaries() {
		let mut bits = BitArray::<17>::new();

		for index in 0..17 {
			assert!(!bits.get(index));
		}

		for index in [0, 7, 8, 15, 16] {
			bits.set(index, true);
		}

		for index in 0..17 {
			assert_eq!(bits.get(index), matches!(index, 0 | 7 | 8 | 15 | 16));
		}

		bits.set(8, false);
		assert!(!bits.get(8));
		assert!(bits.get(7));
		assert!(bits.get(15));
	}

	#[test]
	fn non_byte_aligned_lengths_store_the_last_declared_bit() {
		let mut bits = BitArray::<10>::default();
		bits.set(9, true);

		assert!(bits.get(9));
		assert!(!bits.get(8));
	}

	#[test]
	#[should_panic(expected = "Bit index is out of bounds")]
	fn rejects_index_at_declared_length() {
		let _ = BitArray::<10>::new().get(10);
	}
}