unity_asset_core/
unity_class.rs

1//! Unity class system
2//!
3//! This module implements Unity's dynamic class system, allowing for
4//! runtime creation and manipulation of Unity objects.
5
6use crate::dynamic_access::{DynamicAccess, DynamicValue};
7use crate::error::Result;
8use crate::unity_value::UnityValue;
9use indexmap::IndexMap;
10use std::collections::HashMap;
11use std::fmt;
12
13/// A Unity class instance
14#[derive(Debug, Clone)]
15pub struct UnityClass {
16    /// Class ID (numeric identifier)
17    pub class_id: i32,
18    /// Class name (string identifier)
19    pub class_name: String,
20    /// YAML anchor for this object
21    pub anchor: String,
22    /// Extra data after the anchor line
23    pub extra_anchor_data: String,
24    /// Object properties
25    properties: IndexMap<String, UnityValue>,
26}
27
28impl UnityClass {
29    /// Create a new Unity class instance
30    pub fn new(class_id: i32, class_name: String, anchor: String) -> Self {
31        Self {
32            class_id,
33            class_name,
34            anchor,
35            extra_anchor_data: String::new(),
36            properties: IndexMap::new(),
37        }
38    }
39
40    /// Get a property value
41    pub fn get(&self, key: &str) -> Option<&UnityValue> {
42        self.properties.get(key)
43    }
44
45    /// Get a mutable property value
46    pub fn get_mut(&mut self, key: &str) -> Option<&mut UnityValue> {
47        self.properties.get_mut(key)
48    }
49
50    /// Set a property value
51    pub fn set<V: Into<UnityValue>>(&mut self, key: String, value: V) {
52        self.properties.insert(key, value.into());
53    }
54
55    /// Check if a property exists
56    pub fn has_property(&self, key: &str) -> bool {
57        self.properties.contains_key(key)
58    }
59
60    /// Get all property names
61    pub fn property_names(&self) -> impl Iterator<Item = &String> {
62        self.properties.keys()
63    }
64
65    /// Get all properties
66    pub fn properties(&self) -> &IndexMap<String, UnityValue> {
67        &self.properties
68    }
69
70    /// Get mutable properties
71    pub fn properties_mut(&mut self) -> &mut IndexMap<String, UnityValue> {
72        &mut self.properties
73    }
74
75    /// Update properties from another map
76    pub fn update_properties(&mut self, other: IndexMap<String, UnityValue>) {
77        for (key, value) in other {
78            self.properties.insert(key, value);
79        }
80    }
81
82    /// Get serialized properties (excluding anchor and metadata)
83    pub fn serialized_properties(&self) -> IndexMap<String, UnityValue> {
84        self.properties.clone()
85    }
86
87    /// Get the object name (m_Name property if it exists)
88    pub fn name(&self) -> Option<&str> {
89        self.get("m_Name").and_then(|v| v.as_str())
90    }
91}
92
93impl fmt::Display for UnityClass {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "{}({})", self.class_name, self.class_id)
96    }
97}
98
99/// Implementation of dynamic property access for UnityClass
100impl DynamicAccess for UnityClass {
101    fn get_dynamic(&self, key: &str) -> Option<DynamicValue> {
102        self.properties.get(key).map(DynamicValue::from_unity_value)
103    }
104
105    fn set_dynamic(&mut self, key: &str, value: DynamicValue) -> Result<()> {
106        self.properties
107            .insert(key.to_string(), value.to_unity_value());
108        Ok(())
109    }
110
111    fn has_dynamic(&self, key: &str) -> bool {
112        self.properties.contains_key(key)
113    }
114
115    fn keys_dynamic(&self) -> Vec<String> {
116        self.properties.keys().cloned().collect()
117    }
118}
119
120/// Registry for Unity class types
121#[derive(Debug, Default)]
122pub struct UnityClassRegistry {
123    /// Map from "class_id-class_name" to class constructor
124    classes: HashMap<String, fn(i32, String, String) -> UnityClass>,
125}
126
127impl UnityClassRegistry {
128    /// Create a new registry
129    pub fn new() -> Self {
130        Self::default()
131    }
132
133    /// Register a class type
134    pub fn register_class<F>(&mut self, class_id: i32, class_name: &str, _constructor: F)
135    where
136        F: Fn(i32, String, String) -> UnityClass + 'static,
137    {
138        let key = format!("{}-{}", class_id, class_name);
139        // For now, we'll use a simple constructor that ignores the custom function
140        self.classes.insert(key, UnityClass::new);
141    }
142
143    /// Get or create a class instance
144    pub fn get_or_create_class(
145        &self,
146        class_id: i32,
147        class_name: &str,
148        anchor: String,
149    ) -> UnityClass {
150        let key = format!("{}-{}", class_id, class_name);
151
152        if let Some(constructor) = self.classes.get(&key) {
153            constructor(class_id, class_name.to_string(), anchor)
154        } else {
155            // Default constructor
156            UnityClass::new(class_id, class_name.to_string(), anchor)
157        }
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn test_unity_class_creation() {
167        let mut class = UnityClass::new(1, "GameObject".to_string(), "123".to_string());
168        class.set("m_Name".to_string(), "TestObject");
169
170        assert_eq!(class.class_name, "GameObject");
171        assert_eq!(class.name(), Some("TestObject"));
172    }
173
174    #[test]
175    fn test_unity_class_registry() {
176        let registry = UnityClassRegistry::new();
177        let class = registry.get_or_create_class(1, "GameObject", "123".to_string());
178
179        assert_eq!(class.class_id, 1);
180        assert_eq!(class.class_name, "GameObject");
181        assert_eq!(class.anchor, "123");
182    }
183
184    #[test]
185    fn test_dynamic_access() {
186        let mut class = UnityClass::new(1, "GameObject".to_string(), "123".to_string());
187
188        // Test setting and getting dynamic values
189        let value = DynamicValue::String("TestName".to_string());
190        class.set_dynamic("m_Name", value).unwrap();
191
192        let retrieved = class.get_dynamic("m_Name").unwrap();
193        assert_eq!(retrieved.as_string(), Some("TestName"));
194
195        // Test has_dynamic
196        assert!(class.has_dynamic("m_Name"));
197        assert!(!class.has_dynamic("nonexistent"));
198
199        // Test keys_dynamic
200        let keys = class.keys_dynamic();
201        assert!(keys.contains(&"m_Name".to_string()));
202    }
203}