rbx_mesh 0.8.0

Rust parser for Roblox mesh files.
Documentation
use super::bit_buffer::{BitBuffer, Cache};

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum BitCounterError {
	NotEnoughBytes,
	InvalidBytesLen,
	NotEnoughBits,
}
impl core::fmt::Display for BitCounterError {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		write!(f, "{self:?}")
	}
}
impl core::error::Error for BitCounterError {}

const CHUNK_SIZE: usize = size_of::<Cache>();

/// Read bits in the same inconsistent manner as Roblox.
#[derive(Debug, Clone)]
pub struct RobloxBitReader<'a> {
	chunks: core::slice::Iter<'a, [u8; CHUNK_SIZE]>,
	cache: BitBuffer,
	bit_count: u32,
}
impl<'a> RobloxBitReader<'a> {
	pub fn new(bytes: &'a [u8], bit_count_limit: u32) -> Result<Self, BitCounterError> {
		if (bytes.len() as u32 * u8::BITS) < bit_count_limit {
			return Err(BitCounterError::NotEnoughBytes);
		}

		let (chunks, rem) = bytes.as_chunks();
		if !rem.is_empty() {
			return Err(BitCounterError::InvalidBytesLen);
		}

		Ok(Self {
			chunks: chunks.iter(),
			cache: BitBuffer::empty(),
			bit_count: bit_count_limit,
		})
	}
	pub const fn remaining_bits(&self) -> u32 {
		self.bit_count + self.cache.bits()
	}
	pub fn read(&mut self, bits: u32) -> Result<Cache, BitCounterError> {
		debug_assert!(bits <= Cache::BITS);

		// replace cache if we need more bits
		let mut value = if self.cache.bits() < bits {
			let draw_bits = self.bit_count.min(BitBuffer::CAPACITY);
			self.bit_count -= draw_bits;
			// bits are lsb-aligned
			let new_cache_bits = self.chunks.next().copied().map_or(0, Cache::from_le_bytes);
			let new_cache = BitBuffer::new(new_cache_bits, draw_bits);
			core::mem::replace(&mut self.cache, new_cache)
		} else {
			BitBuffer::empty()
		};

		let draw_bits = bits - value.bits();
		if self.cache.bits() < draw_bits {
			return Err(BitCounterError::NotEnoughBits);
		}

		// populate value with cached bits
		value.push_lsb(draw_bits, self.cache.pop_msb(draw_bits));
		Ok(value.value())
	}
}