mod render;
pub use render::{NamedPath, Nameless, NamelessKind};
#[cfg(test)]
mod tests;
use std::{
fmt,
hash::{Hash, Hasher},
iter::FusedIterator,
slice, vec,
};
use ltk_hash::{BinHash, HashValue, WadHash};
use ltk_primitives::Color;
use crate::{
property::{values, Kind},
walk::{Leaf, TreeValue as _},
Error, PropertyValueEnum,
};
#[derive(Clone, Debug, Default)]
pub struct ValuePath {
segments: Vec<ValueSegment>,
classes: Vec<BinHash>,
}
impl ValuePath {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn segments(&self) -> &[ValueSegment] {
&self.segments
}
#[must_use]
pub fn len(&self) -> usize {
self.segments.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}
pub fn push_field(&mut self, field: BinHash, class: BinHash) {
self.segments.push(ValueSegment::Field(field));
self.classes.push(class);
}
pub fn push_index(&mut self, index: usize) {
self.segments.push(ValueSegment::Index(index));
}
pub fn push_key(&mut self, key: MapKey) {
self.segments.push(ValueSegment::Key(key));
}
pub fn push(&mut self, segment: ValueSegment) {
match segment {
ValueSegment::Field(field) => self.push_field(field, BinHash(0)),
segment => self.segments.push(segment),
}
}
pub fn pop(&mut self) -> Option<ValueSegment> {
let segment = self.segments.pop()?;
if let ValueSegment::Field(_) = segment {
self.classes.pop();
}
Some(segment)
}
pub fn fields(&self) -> Fields<'_> {
Fields {
segments: self.segments.iter(),
classes: self.classes.iter(),
}
}
}
impl PartialEq for ValuePath {
fn eq(&self, other: &Self) -> bool {
self.segments == other.segments
}
}
impl Eq for ValuePath {}
impl Hash for ValuePath {
fn hash<H: Hasher>(&self, state: &mut H) {
self.segments.hash(state);
}
}
impl fmt::Display for ValuePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, segment) in self.segments.iter().enumerate() {
if i > 0 && matches!(segment, ValueSegment::Field(_)) {
f.write_str(".")?;
}
write!(f, "{segment}")?;
}
Ok(())
}
}
impl FromIterator<ValueSegment> for ValuePath {
fn from_iter<I: IntoIterator<Item = ValueSegment>>(iter: I) -> Self {
let mut path = Self::new();
path.extend(iter);
path
}
}
impl Extend<ValueSegment> for ValuePath {
fn extend<I: IntoIterator<Item = ValueSegment>>(&mut self, iter: I) {
for segment in iter {
self.push(segment);
}
}
}
impl<'a> IntoIterator for &'a ValuePath {
type Item = &'a ValueSegment;
type IntoIter = slice::Iter<'a, ValueSegment>;
fn into_iter(self) -> Self::IntoIter {
self.segments.iter()
}
}
impl IntoIterator for ValuePath {
type Item = ValueSegment;
type IntoIter = vec::IntoIter<ValueSegment>;
fn into_iter(self) -> Self::IntoIter {
self.segments.into_iter()
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[derive(Clone, Debug)]
pub struct Fields<'a> {
segments: slice::Iter<'a, ValueSegment>,
classes: slice::Iter<'a, BinHash>,
}
impl Iterator for Fields<'_> {
type Item = (BinHash, Option<BinHash>);
fn next(&mut self) -> Option<Self::Item> {
let field = self.segments.find_map(|segment| match segment {
ValueSegment::Field(field) => Some(*field),
_ => None,
})?;
let class = self.classes.next().copied().filter(|class| **class != 0);
Some((field, class))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.classes.len(), Some(self.classes.len()))
}
}
impl ExactSizeIterator for Fields<'_> {}
impl FusedIterator for Fields<'_> {}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ValueSegment {
Field(BinHash),
Index(usize),
Key(MapKey),
}
impl fmt::Display for ValueSegment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Field(field) => write!(f, "{field:08x}"),
Self::Index(index) => write!(f, "[{index}]"),
Self::Key(key) => write!(f, "{{{key}}}"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MapKey {
None,
Bool(bool),
I8(i8),
U8(u8),
I16(i16),
U16(u16),
I32(i32),
U32(u32),
I64(i64),
U64(u64),
F32(FloatBits),
Vector2([FloatBits; 2]),
Vector3([FloatBits; 3]),
Vector4([FloatBits; 4]),
Matrix44([FloatBits; 16]),
Color(Color<u8>),
String(String),
Hash(HashValue),
File(WadHash),
}
impl fmt::Display for MapKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_leaf().write_key(f)
}
}
impl MapKey {
#[must_use]
pub fn kind(&self) -> Kind {
self.as_leaf().kind()
}
#[must_use]
pub fn from_leaf(leaf: Leaf<'_>) -> Option<Self> {
Some(match leaf {
Leaf::None => Self::None,
Leaf::Bool(v) => Self::Bool(v),
Leaf::I8(v) => Self::I8(v),
Leaf::U8(v) => Self::U8(v),
Leaf::I16(v) => Self::I16(v),
Leaf::U16(v) => Self::U16(v),
Leaf::I32(v) => Self::I32(v),
Leaf::U32(v) => Self::U32(v),
Leaf::I64(v) => Self::I64(v),
Leaf::U64(v) => Self::U64(v),
Leaf::F32(v) => Self::F32(FloatBits::new(v)),
Leaf::Vector2(v) => Self::Vector2(v.to_array().map(FloatBits::new)),
Leaf::Vector3(v) => Self::Vector3(v.to_array().map(FloatBits::new)),
Leaf::Vector4(v) => Self::Vector4(v.to_array().map(FloatBits::new)),
Leaf::Matrix44(v) => Self::Matrix44(v.transpose().to_cols_array().map(FloatBits::new)),
Leaf::Color(v) => Self::Color(v),
Leaf::String(v) => Self::String(v.to_owned()),
Leaf::Hash(v) => Self::Hash(v),
Leaf::File(v) => Self::File(v),
Leaf::Link(_) | Leaf::Flag(_) => return None,
})
}
#[must_use]
pub fn to_value(&self) -> PropertyValueEnum {
match self.as_leaf() {
Leaf::None => values::None::default().into(),
Leaf::Bool(v) => values::Bool::new(v).into(),
Leaf::I8(v) => values::I8::new(v).into(),
Leaf::U8(v) => values::U8::new(v).into(),
Leaf::I16(v) => values::I16::new(v).into(),
Leaf::U16(v) => values::U16::new(v).into(),
Leaf::I32(v) => values::I32::new(v).into(),
Leaf::U32(v) => values::U32::new(v).into(),
Leaf::I64(v) => values::I64::new(v).into(),
Leaf::U64(v) => values::U64::new(v).into(),
Leaf::F32(v) => values::F32::new(v).into(),
Leaf::Vector2(v) => values::Vector2::new(v).into(),
Leaf::Vector3(v) => values::Vector3::new(v).into(),
Leaf::Vector4(v) => values::Vector4::new(v).into(),
Leaf::Matrix44(v) => values::Matrix44::new(v).into(),
Leaf::Color(v) => values::Color::new(v).into(),
Leaf::String(v) => values::String::new(v.to_owned()).into(),
Leaf::Hash(v) => values::Hash::new(v).into(),
Leaf::File(v) => values::WadChunkLink::new(v).into(),
Leaf::Link(_) | Leaf::Flag(_) => {
unreachable!("a MapKey is never a link or a flag")
}
}
}
pub(crate) fn as_leaf(&self) -> Leaf<'_> {
match self {
Self::None => Leaf::None,
Self::Bool(v) => Leaf::Bool(*v),
Self::I8(v) => Leaf::I8(*v),
Self::U8(v) => Leaf::U8(*v),
Self::I16(v) => Leaf::I16(*v),
Self::U16(v) => Leaf::U16(*v),
Self::I32(v) => Leaf::I32(*v),
Self::U32(v) => Leaf::U32(*v),
Self::I64(v) => Leaf::I64(*v),
Self::U64(v) => Leaf::U64(*v),
Self::F32(v) => Leaf::F32(v.get()),
Self::Vector2(v) => Leaf::Vector2(glam::Vec2::from_array(floats(v))),
Self::Vector3(v) => Leaf::Vector3(glam::Vec3::from_array(floats(v))),
Self::Vector4(v) => Leaf::Vector4(glam::Vec4::from_array(floats(v))),
Self::Matrix44(v) => {
Leaf::Matrix44(glam::Mat4::from_cols_array(&floats(v)).transpose())
}
Self::Color(v) => Leaf::Color(*v),
Self::String(v) => Leaf::String(v),
Self::Hash(v) => Leaf::Hash(*v),
Self::File(v) => Leaf::File(*v),
}
}
}
impl TryFrom<&PropertyValueEnum> for MapKey {
type Error = Error;
fn try_from(value: &PropertyValueEnum) -> Result<Self, Error> {
value.map_key()
}
}
fn floats<const N: usize>(bits: &[FloatBits; N]) -> [f32; N] {
bits.map(FloatBits::get)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FloatBits(u32);
impl FloatBits {
#[must_use]
pub fn new(value: f32) -> Self {
Self(value.to_bits())
}
#[must_use]
pub fn get(self) -> f32 {
f32::from_bits(self.0)
}
}
impl From<f32> for FloatBits {
fn from(value: f32) -> Self {
Self::new(value)
}
}