Skip to main content

goud_engine/ecs/schedule/
system_label.rs

1//! System label traits and type-erased identifiers.
2
3use std::any::TypeId;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6
7use super::stage_label::DynHasherWrapper;
8
9/// Trait for types that can be used as system labels.
10///
11/// System labels provide a way to reference systems by name rather than ID,
12/// enabling more flexible ordering constraints.
13///
14/// # Example
15///
16/// ```ignore
17/// use goud_engine::ecs::schedule::SystemLabel;
18/// use std::any::TypeId;
19///
20/// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21/// struct PhysicsSystem;
22///
23/// impl SystemLabel for PhysicsSystem {
24///     fn label_id(&self) -> TypeId { TypeId::of::<Self>() }
25///     fn label_name(&self) -> &'static str { "PhysicsSystem" }
26/// }
27/// ```
28pub trait SystemLabel: Send + Sync + 'static {
29    /// Returns a unique identifier for this label type.
30    fn label_id(&self) -> TypeId;
31
32    /// Returns a human-readable name for this label.
33    fn label_name(&self) -> &'static str;
34
35    /// Clones this label into a boxed trait object.
36    fn dyn_clone(&self) -> Box<dyn SystemLabel>;
37
38    /// Compares this label with another for equality.
39    fn dyn_eq(&self, other: &dyn SystemLabel) -> bool {
40        self.label_id() == other.label_id()
41    }
42
43    /// Computes a hash of this label.
44    fn dyn_hash(&self, state: &mut dyn Hasher) {
45        self.label_id().hash(&mut DynHasherWrapper(state));
46    }
47}
48
49impl Clone for Box<dyn SystemLabel> {
50    fn clone(&self) -> Self {
51        self.dyn_clone()
52    }
53}
54
55impl PartialEq for dyn SystemLabel {
56    fn eq(&self, other: &Self) -> bool {
57        self.dyn_eq(other)
58    }
59}
60
61impl Eq for dyn SystemLabel {}
62
63impl Hash for dyn SystemLabel {
64    fn hash<H: Hasher>(&self, state: &mut H) {
65        self.label_id().hash(state);
66    }
67}
68
69impl fmt::Debug for dyn SystemLabel {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        write!(f, "SystemLabel({})", self.label_name())
72    }
73}
74
75// ============================================================================
76// SystemLabelId - Type-erased System Label
77// ============================================================================
78
79/// A type-erased system label identifier.
80///
81/// This wraps a `Box<dyn SystemLabel>` and provides the necessary trait
82/// implementations for use as a map key.
83///
84/// # Example
85///
86/// ```
87/// use goud_engine::ecs::schedule::{SystemLabelId, SystemLabel, CoreSystemLabel};
88///
89/// let id = SystemLabelId::of(CoreSystemLabel::Input);
90/// assert_eq!(id.name(), "Input");
91/// ```
92#[derive(Clone)]
93pub struct SystemLabelId(Box<dyn SystemLabel>);
94
95impl SystemLabelId {
96    /// Creates a new `SystemLabelId` from a label.
97    pub fn of<L: SystemLabel + Clone>(label: L) -> Self {
98        Self(Box::new(label))
99    }
100
101    /// Returns the label's human-readable name.
102    #[inline]
103    pub fn name(&self) -> &'static str {
104        self.0.label_name()
105    }
106
107    /// Returns the label's type ID.
108    #[inline]
109    pub fn type_id(&self) -> TypeId {
110        self.0.label_id()
111    }
112
113    /// Returns a reference to the inner label.
114    #[inline]
115    pub fn inner(&self) -> &dyn SystemLabel {
116        &*self.0
117    }
118}
119
120impl PartialEq for SystemLabelId {
121    fn eq(&self, other: &Self) -> bool {
122        self.0.label_id() == other.0.label_id()
123    }
124}
125
126impl Eq for SystemLabelId {}
127
128impl Hash for SystemLabelId {
129    fn hash<H: Hasher>(&self, state: &mut H) {
130        self.0.label_id().hash(state);
131    }
132}
133
134impl fmt::Debug for SystemLabelId {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(f, "SystemLabelId({})", self.0.label_name())
137    }
138}
139
140impl fmt::Display for SystemLabelId {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        write!(f, "{}", self.0.label_name())
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::ecs::schedule::CoreSystemLabel;
150    use std::collections::HashMap;
151
152    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
153    struct CustomLabel;
154
155    impl SystemLabel for CustomLabel {
156        fn label_id(&self) -> TypeId {
157            TypeId::of::<Self>()
158        }
159        fn label_name(&self) -> &'static str {
160            "CustomLabel"
161        }
162        fn dyn_clone(&self) -> Box<dyn SystemLabel> {
163            Box::new(*self)
164        }
165    }
166
167    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
168    struct AnotherLabel;
169
170    impl SystemLabel for AnotherLabel {
171        fn label_id(&self) -> TypeId {
172            TypeId::of::<Self>()
173        }
174        fn label_name(&self) -> &'static str {
175            "AnotherLabel"
176        }
177        fn dyn_clone(&self) -> Box<dyn SystemLabel> {
178            Box::new(*self)
179        }
180    }
181
182    // SystemLabel trait tests
183
184    #[test]
185    fn test_custom_label_name() {
186        assert_eq!(CustomLabel.label_name(), "CustomLabel");
187    }
188
189    #[test]
190    fn test_custom_label_id() {
191        assert_eq!(CustomLabel.label_id(), TypeId::of::<CustomLabel>());
192    }
193
194    #[test]
195    fn test_custom_label_dyn_clone() {
196        let cloned = CustomLabel.dyn_clone();
197        assert_eq!(cloned.label_name(), "CustomLabel");
198    }
199
200    #[test]
201    fn test_dyn_eq_same() {
202        assert!(CustomLabel.dyn_eq(&CustomLabel));
203    }
204
205    #[test]
206    fn test_dyn_eq_different() {
207        assert!(!CustomLabel.dyn_eq(&AnotherLabel));
208    }
209
210    #[test]
211    fn test_box_clone() {
212        let label: Box<dyn SystemLabel> = Box::new(CustomLabel);
213        let cloned = label.clone();
214        assert_eq!(label.label_name(), cloned.label_name());
215    }
216
217    #[test]
218    fn test_dyn_partial_eq() {
219        let a: &dyn SystemLabel = &CustomLabel;
220        let b: &dyn SystemLabel = &CustomLabel;
221        assert!(a == b);
222    }
223
224    #[test]
225    fn test_dyn_hash() {
226        use std::collections::hash_map::DefaultHasher;
227        let a: &dyn SystemLabel = &CustomLabel;
228        let b: &dyn SystemLabel = &CustomLabel;
229        let mut hasher_a = DefaultHasher::new();
230        let mut hasher_b = DefaultHasher::new();
231        a.hash(&mut hasher_a);
232        b.hash(&mut hasher_b);
233        assert_eq!(hasher_a.finish(), hasher_b.finish());
234    }
235
236    #[test]
237    fn test_dyn_debug() {
238        let label: &dyn SystemLabel = &CustomLabel;
239        let debug = format!("{:?}", label);
240        assert!(debug.contains("CustomLabel"));
241    }
242
243    #[test]
244    fn test_system_label_send_sync() {
245        fn assert_send<T: Send>() {}
246        fn assert_sync<T: Sync>() {}
247        assert_send::<CustomLabel>();
248        assert_sync::<CustomLabel>();
249    }
250
251    // SystemLabelId tests
252
253    #[test]
254    fn test_of() {
255        let id = SystemLabelId::of(CustomLabel);
256        assert_eq!(id.name(), "CustomLabel");
257    }
258
259    #[test]
260    fn test_name() {
261        let id = SystemLabelId::of(CoreSystemLabel::Physics);
262        assert_eq!(id.name(), "Physics");
263    }
264
265    #[test]
266    fn test_type_id() {
267        let id = SystemLabelId::of(CustomLabel);
268        assert_eq!(id.type_id(), TypeId::of::<CustomLabel>());
269    }
270
271    #[test]
272    fn test_inner() {
273        let id = SystemLabelId::of(CoreSystemLabel::Input);
274        assert_eq!(id.inner().label_name(), "Input");
275    }
276
277    #[test]
278    fn test_equality_same() {
279        let a = SystemLabelId::of(CustomLabel);
280        let b = SystemLabelId::of(CustomLabel);
281        assert_eq!(a, b);
282    }
283
284    #[test]
285    fn test_equality_different() {
286        let a = SystemLabelId::of(CustomLabel);
287        let b = SystemLabelId::of(CoreSystemLabel::Physics);
288        assert_ne!(a, b);
289    }
290
291    #[test]
292    fn test_hash() {
293        use std::collections::hash_map::DefaultHasher;
294        let a = SystemLabelId::of(CustomLabel);
295        let b = SystemLabelId::of(CustomLabel);
296        let mut hasher_a = DefaultHasher::new();
297        let mut hasher_b = DefaultHasher::new();
298        a.hash(&mut hasher_a);
299        b.hash(&mut hasher_b);
300        assert_eq!(hasher_a.finish(), hasher_b.finish());
301    }
302
303    #[test]
304    fn test_debug() {
305        let id = SystemLabelId::of(CustomLabel);
306        assert!(format!("{:?}", id).contains("CustomLabel"));
307    }
308
309    #[test]
310    fn test_display() {
311        let id = SystemLabelId::of(CoreSystemLabel::Audio);
312        assert_eq!(format!("{}", id), "Audio");
313    }
314
315    #[test]
316    fn test_clone() {
317        let id = SystemLabelId::of(CustomLabel);
318        let cloned = id.clone();
319        assert_eq!(id, cloned);
320    }
321
322    #[test]
323    fn test_in_hashmap() {
324        let mut map = HashMap::new();
325        let id = SystemLabelId::of(CustomLabel);
326        map.insert(id.clone(), "value");
327        assert_eq!(map.get(&id), Some(&"value"));
328    }
329}