Skip to main content

murk_core/
entity.rs

1//! Entity manifest configuration.
2
3use crate::PropertyIndex;
4
5/// Declares the homogeneous property schema for entities in a world.
6///
7/// Every entity in a world has the same property layout. The `alive_property`
8/// is the single source of truth for liveness: stores stamp it to `1.0` on
9/// spawn and `0.0` on despawn.
10#[derive(Clone, Debug, PartialEq)]
11pub struct EntityManifest {
12    /// Human-readable property names.
13    pub property_names: Vec<String>,
14    /// Default values applied to each property at spawn.
15    pub property_defaults: Vec<f32>,
16    /// Property index used as the alive flag.
17    pub alive_property: PropertyIndex,
18}
19
20impl EntityManifest {
21    /// Number of properties in the manifest.
22    #[must_use]
23    pub fn property_count(&self) -> usize {
24        self.property_defaults.len()
25    }
26
27    /// Validate the manifest shape.
28    pub fn validate(&self) -> Result<(), EntityManifestError> {
29        if self.property_names.len() != self.property_defaults.len() {
30            return Err(EntityManifestError::PropertyLengthMismatch {
31                names: self.property_names.len(),
32                defaults: self.property_defaults.len(),
33            });
34        }
35        if self.property_defaults.is_empty() {
36            return Err(EntityManifestError::EmptyProperties);
37        }
38        if self.alive_property.0 as usize >= self.property_defaults.len() {
39            return Err(EntityManifestError::AlivePropertyOutOfRange {
40                alive_property: self.alive_property,
41                property_count: self.property_defaults.len(),
42            });
43        }
44        Ok(())
45    }
46}
47
48/// Validation errors for [`EntityManifest`].
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum EntityManifestError {
51    /// Property name/default vectors have different lengths.
52    PropertyLengthMismatch {
53        /// Number of property names.
54        names: usize,
55        /// Number of default values.
56        defaults: usize,
57    },
58    /// The manifest declares no properties.
59    EmptyProperties,
60    /// The alive property index points outside the property array.
61    AlivePropertyOutOfRange {
62        /// Configured alive property index.
63        alive_property: PropertyIndex,
64        /// Number of declared properties.
65        property_count: usize,
66    },
67}
68
69impl std::fmt::Display for EntityManifestError {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        match self {
72            Self::PropertyLengthMismatch { names, defaults } => write!(
73                f,
74                "entity property names/defaults length mismatch: {names} names, {defaults} defaults"
75            ),
76            Self::EmptyProperties => {
77                write!(f, "entity manifest must declare at least one property")
78            }
79            Self::AlivePropertyOutOfRange {
80                alive_property,
81                property_count,
82            } => write!(
83                f,
84                "alive property {alive_property} is out of range for {property_count} properties"
85            ),
86        }
87    }
88}
89
90impl std::error::Error for EntityManifestError {}
91
92#[cfg(test)]
93mod tests {
94    use crate::{EntityManifest, PropertyIndex};
95
96    #[test]
97    fn property_count_matches_defaults() {
98        let manifest = EntityManifest {
99            property_names: vec!["alive".into(), "hp".into()],
100            property_defaults: vec![1.0, 100.0],
101            alive_property: PropertyIndex(0),
102        };
103
104        assert_eq!(manifest.property_count(), 2);
105        assert_eq!(manifest.validate(), Ok(()));
106    }
107
108    #[test]
109    fn validate_rejects_length_mismatch() {
110        let manifest = EntityManifest {
111            property_names: vec!["alive".into()],
112            property_defaults: vec![1.0, 100.0],
113            alive_property: PropertyIndex(0),
114        };
115
116        assert!(manifest.validate().is_err());
117    }
118
119    #[test]
120    fn validate_rejects_alive_property_out_of_range() {
121        let manifest = EntityManifest {
122            property_names: vec!["alive".into()],
123            property_defaults: vec![1.0],
124            alive_property: PropertyIndex(2),
125        };
126
127        assert!(manifest.validate().is_err());
128    }
129}