use std::{marker::PhantomData, str::FromStr};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use qbsp_macros::BspVariableValue;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use crate::{
BspParseError, BspParseResultDoingJobExt, BspResult,
reader::{BspByteReader, BspParseContext, BspValue},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NoField;
impl BspValue for NoField {
fn bsp_parse(_reader: &mut BspByteReader) -> BspResult<Self> {
Ok(NoField)
}
fn bsp_struct_size(_ctx: &BspParseContext) -> usize {
0
}
}
mod no_field_impls {
use crate::{
LumpEntry,
data::{
models::PerSizeHulls,
nodes::{BspAmbience, BspLeafBrushes},
texture::{BspTexQ2Info, Palette},
},
};
use super::NoField;
macro_rules! impl_from_no_field_for_option {
($opt_inner:ty) => {
impl From<NoField> for Option<$opt_inner> {
fn from(_: NoField) -> Self {
None
}
}
};
}
impl_from_no_field_for_option!(u32);
impl_from_no_field_for_option!(u16);
impl_from_no_field_for_option!(BspTexQ2Info);
impl_from_no_field_for_option!(BspAmbience);
impl_from_no_field_for_option!(BspLeafBrushes);
impl_from_no_field_for_option!(LumpEntry);
impl_from_no_field_for_option!(Palette);
impl_from_no_field_for_option!(PerSizeHulls);
}
impl<T, N> From<NoField> for Option<BspVariableArray<T, N>> {
fn from(_: NoField) -> Self {
None
}
}
impl<T, const N: usize> From<NoField> for Option<[T; N]> {
fn from(_: NoField) -> Self {
None
}
}
#[derive(BspVariableValue, Hash, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[bsp29(u16)]
#[bsp2(u32)]
#[bsp30(u16)]
#[bsp38(u16)]
#[qbism(u32)]
pub struct UBspValue(pub u32);
#[derive(BspVariableValue, Hash, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[bsp29(i16)]
#[bsp2(i32)]
#[bsp30(i16)]
#[bsp38(i16)]
#[qbism(i32)]
pub struct IBspValue(pub i32);
#[derive(Debug, Clone, Default, derive_more::Deref, derive_more::DerefMut, derive_more::IntoIterator)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BspVariableArray<T, N> {
#[deref]
#[deref_mut]
#[into_iterator(owned, ref, ref_mut)]
pub inner: Vec<T>,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
_marker: PhantomData<N>,
}
impl<T: BspValue, N: BspValue + TryInto<usize, Error: std::fmt::Debug>> BspValue for BspVariableArray<T, N> {
#[track_caller] fn bsp_parse(reader: &mut BspByteReader) -> BspResult<Self> {
let count: usize = reader.read::<N>().job("count")?.try_into().unwrap();
let mut inner = Vec::with_capacity(count);
for _ in 0..count {
inner.push(reader.read().job(std::any::type_name::<T>())?);
}
Ok(Self { inner, _marker: PhantomData })
}
fn bsp_struct_size(_ctx: &BspParseContext) -> usize {
unimplemented!("{} is of variable size", std::any::type_name::<Self>());
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct FixedStr<const N: usize> {
data: [u8; N],
}
impl<const N: usize> BspValue for FixedStr<N> {
fn bsp_parse(reader: &mut BspByteReader) -> BspResult<Self> {
let data = reader.read()?;
Self::new(data).map_err(BspParseError::map_utf8_error(&data))
}
#[inline]
fn bsp_struct_size(_ctx: &BspParseContext) -> usize {
N
}
}
impl<const N: usize> FixedStr<N> {
pub const EMPTY: Self = Self { data: [0; N] };
pub const MAX_LEN: usize = N;
pub fn new(mut data: [u8; N]) -> Result<Self, std::str::Utf8Error> {
if let Some(index) = data.iter().position(|b| *b == 0) {
data[index..].fill(0);
}
std::str::from_utf8(&data)?;
Ok(Self { data })
}
pub fn len(&self) -> usize {
for i in 0..N {
if self.data[i] == 0 {
return i;
}
}
N
}
pub fn is_empty(&self) -> bool {
self == &Self::EMPTY
}
pub fn as_str(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(&self.data) }.trim_end_matches('\0')
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn to_bytes(self) -> [u8; N] {
self.data
}
pub fn extend<const NEW_N: usize>(&self) -> FixedStr<NEW_N> {
assert!(NEW_N >= N);
let mut data = [0; NEW_N];
data[..N].copy_from_slice(&self.data);
FixedStr { data }
}
pub fn truncate<const NEW_N: usize>(&self) -> Option<FixedStr<NEW_N>> {
if self.len() > NEW_N {
return None;
}
let mut data = [0; NEW_N];
data.copy_from_slice(&self.data[..NEW_N]);
Some(FixedStr { data })
}
}
impl<const N: usize> std::fmt::Debug for FixedStr<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl<const N: usize> std::fmt::Display for FixedStr<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl<const N: usize> Default for FixedStr<N> {
fn default() -> Self {
Self { data: [0; N] }
}
}
impl<const N: usize> FromStr for FixedStr<N> {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() > N {
return Err(());
}
let mut data = [0; N];
#[expect(clippy::manual_memcpy)]
for i in 0..s.len() {
data[i] = s.as_bytes()[i];
}
Ok(Self { data })
}
}
#[cfg(feature = "serde")]
impl<const N: usize> Serialize for FixedStr<N> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "serde")]
impl<'de, const N: usize> Deserialize<'de> for FixedStr<N> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct DataVisitor<const N: usize>;
impl<const N: usize> de::Visitor<'_> for DataVisitor<N> {
type Value = [u8; N];
fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "Fixed string of len {N}")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.as_bytes()
.try_into()
.map_err(|_| E::custom(format_args!("string was of len {}, when max len is {N}", v.len())))
}
}
Ok(Self {
data: deserializer.deserialize_seq(DataVisitor::<N>)?,
})
}
}
#[cfg(test)]
mod fixed_str_tests {
use std::str::FromStr;
use super::FixedStr;
#[test]
fn from_str() {
assert!(FixedStr::<8>::from_str("12345678").is_ok());
assert!(FixedStr::<8>::from_str("123456789").is_err());
}
#[test]
fn from_null_garbage() {
assert!(FixedStr::<8>::new([b'+', b's', b'k', b'y', 0, b'+', b'v', 189]).is_ok());
}
#[test]
fn calculate_len() {
assert_eq!(FixedStr::<8>::from_str("").unwrap().len(), 0);
assert_eq!(FixedStr::<8>::from_str("foo").unwrap().len(), 3);
assert_eq!(FixedStr::<8>::from_str("12345678").unwrap().len(), 8);
}
#[test]
fn extend() {
assert_eq!(
FixedStr::<3>::from_str("foo").unwrap().extend::<32>(),
FixedStr::<32>::from_str("foo").unwrap()
);
assert_eq!(
FixedStr::<4>::from_str("foo").unwrap().extend::<8>(),
FixedStr::<8>::from_str("foo").unwrap()
);
}
#[test]
fn truncate() {
assert_eq!(
FixedStr::<8>::from_str("foo").unwrap().truncate::<4>(),
Some(FixedStr::<4>::from_str("foo").unwrap())
);
assert_eq!(
FixedStr::<8>::from_str("foo").unwrap().truncate::<3>(),
Some(FixedStr::<3>::from_str("foo").unwrap())
);
assert!(FixedStr::<8>::from_str("foo").unwrap().truncate::<2>().is_none());
}
}