Skip to main content

plasma_prp/core/
key.rs

1//! plKey — reference-counted handle to a keyed object.
2//!
3//! In C++, plKey is a smart pointer to plKeyData (plKeyImp), which holds:
4//!   - A plUoid identifying the object
5//!   - An optional pointer to the loaded object
6//!   - File position (start_pos, data_len) for demand loading
7//!   - Active reference count
8//!
9//! In Rust, we represent this as Arc<RwLock<KeyData>>.
10//!
11//! C++ ref: pnKeyedObject/plKey.h, plKeyImp.h/.cpp
12
13use std::fmt;
14use std::hash::{Hash, Hasher};
15use std::sync::{Arc, RwLock};
16
17use super::uoid::Uoid;
18
19/// The inner data of a key — holds identity and optional object reference.
20pub struct KeyData {
21    pub uoid: Uoid,
22    /// File offset where the object data starts (for demand loading).
23    pub start_pos: u32,
24    /// Length of the object data in the file.
25    pub data_len: u32,
26    /// Number of active references to this key's object.
27    pub active_refs: u16,
28    // Object pointer will be added when we implement KeyedObject trait in Step 4.
29    // For now, this is just the identity + file position.
30}
31
32impl KeyData {
33    pub fn name(&self) -> &str {
34        &self.uoid.object_name
35    }
36
37    pub fn class_type(&self) -> u16 {
38        self.uoid.class_type
39    }
40}
41
42impl fmt::Debug for KeyData {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        write!(
45            f,
46            "KeyData({:?}, pos={}, len={})",
47            self.uoid, self.start_pos, self.data_len
48        )
49    }
50}
51
52/// A reference-counted handle to a keyed object.
53///
54/// This is the primary way objects reference each other in the Plasma engine.
55/// A null key (None) represents a nil reference.
56#[derive(Clone)]
57pub struct Key(Option<Arc<RwLock<KeyData>>>);
58
59impl Key {
60    /// Create a null (nil) key.
61    pub fn null() -> Self {
62        Key(None)
63    }
64
65    /// Create a key from a Uoid (no file position).
66    pub fn from_uoid(uoid: Uoid) -> Self {
67        Key(Some(Arc::new(RwLock::new(KeyData {
68            uoid,
69            start_pos: 0,
70            data_len: 0,
71            active_refs: 0,
72        }))))
73    }
74
75    /// Create a key with file position info (for demand loading).
76    pub fn from_uoid_with_pos(uoid: Uoid, start_pos: u32, data_len: u32) -> Self {
77        Key(Some(Arc::new(RwLock::new(KeyData {
78            uoid,
79            start_pos,
80            data_len,
81            active_refs: 0,
82        }))))
83    }
84
85    /// Returns true if this is a null (nil) key.
86    pub fn is_null(&self) -> bool {
87        self.0.is_none()
88    }
89
90    /// Returns true if this key is non-null.
91    pub fn is_some(&self) -> bool {
92        self.0.is_some()
93    }
94
95    /// Get the inner Arc for this key. Returns None for null keys.
96    pub fn inner(&self) -> Option<&Arc<RwLock<KeyData>>> {
97        self.0.as_ref()
98    }
99
100    /// Get the Uoid of this key. Panics on null keys.
101    pub fn uoid(&self) -> Uoid {
102        self.0
103            .as_ref()
104            .expect("uoid() called on null key")
105            .read()
106            .unwrap()
107            .uoid
108            .clone()
109    }
110
111    /// Get the name of the referenced object. Panics on null keys.
112    pub fn name(&self) -> String {
113        self.0
114            .as_ref()
115            .expect("name() called on null key")
116            .read()
117            .unwrap()
118            .uoid
119            .object_name
120            .clone()
121    }
122
123    /// Get the class type of the referenced object. Panics on null keys.
124    pub fn class_type(&self) -> u16 {
125        self.0
126            .as_ref()
127            .expect("class_type() called on null key")
128            .read()
129            .unwrap()
130            .uoid
131            .class_type
132    }
133
134    /// Try to get the name, returning None for null keys.
135    pub fn try_name(&self) -> Option<String> {
136        self.0
137            .as_ref()
138            .map(|arc| arc.read().unwrap().uoid.object_name.clone())
139    }
140}
141
142impl Default for Key {
143    fn default() -> Self {
144        Key::null()
145    }
146}
147
148impl PartialEq for Key {
149    fn eq(&self, other: &Self) -> bool {
150        match (&self.0, &other.0) {
151            (None, None) => true,
152            (Some(a), Some(b)) => Arc::ptr_eq(a, b),
153            _ => false,
154        }
155    }
156}
157
158impl Eq for Key {}
159
160impl Hash for Key {
161    fn hash<H: Hasher>(&self, state: &mut H) {
162        match &self.0 {
163            None => 0u8.hash(state),
164            Some(arc) => {
165                1u8.hash(state);
166                Arc::as_ptr(arc).hash(state);
167            }
168        }
169    }
170}
171
172impl PartialOrd for Key {
173    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
174        Some(self.cmp(other))
175    }
176}
177
178impl Ord for Key {
179    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
180        match (&self.0, &other.0) {
181            (None, None) => std::cmp::Ordering::Equal,
182            (None, Some(_)) => std::cmp::Ordering::Less,
183            (Some(_), None) => std::cmp::Ordering::Greater,
184            (Some(a), Some(b)) => Arc::as_ptr(a).cmp(&Arc::as_ptr(b)),
185        }
186    }
187}
188
189impl fmt::Debug for Key {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        match &self.0 {
192            None => write!(f, "Key(nil)"),
193            Some(arc) => {
194                let data = arc.read().unwrap();
195                write!(f, "Key({:?})", data.uoid)
196            }
197        }
198    }
199}
200
201impl fmt::Display for Key {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match &self.0 {
204            None => write!(f, "(nil)"),
205            Some(arc) => {
206                let data = arc.read().unwrap();
207                write!(f, "{}", data.uoid)
208            }
209        }
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use super::super::location::Location;
217
218    #[test]
219    fn test_null_key() {
220        let k = Key::null();
221        assert!(k.is_null());
222        assert!(!k.is_some());
223    }
224
225    #[test]
226    fn test_key_from_uoid() {
227        let uoid = Uoid::new(Location::new(100, 0), 0x004C, "TestDrawable".into());
228        let k = Key::from_uoid(uoid);
229        assert!(!k.is_null());
230        assert_eq!(k.name(), "TestDrawable");
231        assert_eq!(k.class_type(), 0x004C);
232    }
233
234    #[test]
235    fn test_key_clone_shares_data() {
236        let uoid = Uoid::new(Location::new(100, 0), 0x0001, "Object".into());
237        let k1 = Key::from_uoid(uoid);
238        let k2 = k1.clone();
239        assert_eq!(k1, k2);
240    }
241
242    #[test]
243    fn test_key_with_pos() {
244        let uoid = Uoid::new(Location::new(100, 0), 0x0004, "Texture".into());
245        let k = Key::from_uoid_with_pos(uoid, 1000, 500);
246        let arc = k.inner().unwrap();
247        let data = arc.read().unwrap();
248        assert_eq!(data.start_pos, 1000);
249        assert_eq!(data.data_len, 500);
250    }
251
252    #[test]
253    fn test_null_keys_equal() {
254        assert_eq!(Key::null(), Key::null());
255    }
256
257    #[test]
258    fn test_different_keys_not_equal() {
259        let k1 = Key::from_uoid(Uoid::new(Location::new(1, 0), 1, "A".into()));
260        let k2 = Key::from_uoid(Uoid::new(Location::new(1, 0), 1, "A".into()));
261        // Different Arc instances, so not equal by pointer
262        assert_ne!(k1, k2);
263    }
264}