Skip to main content

rusteal_core/
object_ref.rs

1// UObjectRef<T>: lightweight 8-byte Copy handle to a UObject.
2//
3// Does NOT prevent garbage collection — the referenced object may become
4// invalid at any time between GC sweeps. Use `Pinned<T>` when you need
5// to guarantee liveness.
6
7use std::marker::PhantomData;
8use std::ops::Deref;
9
10use rusteal_ffi::{UClassHandle, UObjectHandle};
11
12use crate::error::{check_ffi, RustealError, RustealResult};
13use crate::ffi_dispatch;
14use crate::pinned::Pinned;
15use crate::traits::{HasParent, UeClass, UeHandle, ValidHandle};
16
17/// A typed, non-owning reference to a UObject.
18///
19/// - `Copy` + `Send` — can be freely cloned and sent across threads.
20/// - `!Sync` — must only be *used* on the game thread.
21/// - Does not prevent garbage collection; call [`is_valid`](Self::is_valid)
22///   before use, or upgrade to [`Pinned<T>`] via [`pin`](Self::pin).
23#[repr(transparent)]
24pub struct UObjectRef<T: UeClass> {
25    handle: UObjectHandle,
26    _marker: PhantomData<*const T>, // *const T makes it !Sync
27}
28
29// By hand: derives would require the same traits of `T`, which classes lack,
30// and leave `UObjectRef<Actor>` neither Copy nor comparable.
31impl<T: UeClass> Clone for UObjectRef<T> {
32    fn clone(&self) -> Self {
33        *self
34    }
35}
36
37impl<T: UeClass> Copy for UObjectRef<T> {}
38
39impl<T: UeClass> PartialEq for UObjectRef<T> {
40    fn eq(&self, other: &Self) -> bool {
41        self.handle == other.handle
42    }
43}
44
45impl<T: UeClass> Eq for UObjectRef<T> {}
46
47impl<T: UeClass> std::hash::Hash for UObjectRef<T> {
48    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
49        self.handle.hash(state);
50    }
51}
52
53// Send: handles are raw identifiers safe to move between threads.
54// !Sync: enforced by PhantomData<*const T> — no shared references across threads.
55unsafe impl<T: UeClass> Send for UObjectRef<T> {}
56
57impl<T: UeClass> Default for UObjectRef<T> {
58    /// No object: `nullptr`.
59    fn default() -> Self {
60        Self::null()
61    }
62}
63
64impl<T: UeClass> UObjectRef<T> {
65    /// No object: what C++ returns as `nullptr`. Never valid.
66    #[inline]
67    pub fn null() -> Self {
68        UObjectRef {
69            handle: UObjectHandle::null(),
70            _marker: PhantomData,
71        }
72    }
73
74    /// Create from a raw FFI handle.
75    ///
76    /// # Safety
77    /// The caller must ensure the handle points to an object whose UClass
78    /// is `T` or a subclass of `T`.
79    #[inline]
80    pub unsafe fn from_raw(handle: UObjectHandle) -> Self {
81        UObjectRef {
82            handle,
83            _marker: PhantomData,
84        }
85    }
86
87    /// Get the underlying raw handle.
88    #[inline]
89    pub fn raw(&self) -> UObjectHandle {
90        self.handle
91    }
92
93    /// Check whether the underlying UObject is still alive.
94    #[inline]
95    pub fn is_valid(&self) -> bool {
96        unsafe { ffi_dispatch::core_is_valid(self.handle) }
97    }
98
99    /// Validate that the object is still alive, returning a `Checked<T>`
100    /// handle that provides infallible access to extension trait methods.
101    #[inline]
102    pub fn checked(&self) -> RustealResult<Checked<T>> {
103        if self.is_valid() {
104            Ok(Checked {
105                handle: self.handle,
106                _marker: PhantomData,
107            })
108        } else {
109            Err(RustealError::ObjectDestroyed)
110        }
111    }
112
113    /// Cast to a different UClass type. Fails if the object is destroyed
114    /// or is not an instance of `U`.
115    pub fn cast<U: UeClass>(self) -> RustealResult<UObjectRef<U>> {
116        let h = self.checked()?.raw();
117        let target = U::static_class();
118        if unsafe { ffi_dispatch::core_is_a(h, target) } {
119            Ok(UObjectRef {
120                handle: self.handle,
121                _marker: PhantomData,
122            })
123        } else {
124            Err(RustealError::InvalidCast)
125        }
126    }
127
128    /// Upgrade to a `Pinned<T>`, adding a GC root to keep the object alive.
129    pub fn pin(self) -> RustealResult<Pinned<T>> {
130        Pinned::new(self)
131    }
132
133    /// Get the object's FName as a String.
134    pub fn get_name(&self) -> RustealResult<String> {
135        let h = self.checked()?.raw();
136        // Stack buffer — 256 bytes is enough for virtually all UObject names.
137        let mut buf = [0u8; 256];
138        let mut out_len: u32 = 0;
139        let code = unsafe {
140            ffi_dispatch::core_get_name(h, buf.as_mut_ptr(), buf.len() as u32, &mut out_len)
141        };
142        check_ffi(code)?;
143        // C++ writes valid UTF-8 (converted from TCHAR).
144        std::str::from_utf8(&buf[..out_len as usize])
145            .map(|s| s.to_owned())
146            .map_err(|_| RustealError::Internal("name is not valid UTF-8".into()))
147    }
148
149    /// Get the object's UClass handle.
150    pub fn get_class(&self) -> RustealResult<UClassHandle> {
151        let h = self.checked()?.raw();
152        Ok(unsafe { ffi_dispatch::core_get_class(h) })
153    }
154
155    /// Get the object's Outer.
156    pub fn get_outer(&self) -> RustealResult<UObjectHandle> {
157        let h = self.checked()?.raw();
158        Ok(unsafe { ffi_dispatch::core_get_outer(h) })
159    }
160
161    /// Check whether this object is an instance of `U` (or a subclass of `U`).
162    /// Returns `false` if the object has been destroyed.
163    #[inline]
164    pub fn is_a<U: UeClass>(&self) -> bool {
165        self.is_valid() && unsafe { ffi_dispatch::core_is_a(self.handle, U::static_class()) }
166    }
167}
168
169impl<T: HasParent> UObjectRef<T> {
170    /// Infallible upcast to the parent class. Zero-cost (same handle).
171    #[inline]
172    pub fn upcast(self) -> UObjectRef<T::Parent> {
173        unsafe { UObjectRef::from_raw(self.handle) }
174    }
175}
176
177impl<T: UeClass> UObjectRef<T> {
178    /// Infallible upcast to any ancestor class, checked at compile time
179    /// (`character.upcast_to::<Object>()` for a world context). Zero-cost.
180    #[inline]
181    pub fn upcast_to<U: UeClass>(self) -> UObjectRef<U>
182    where
183        T: crate::traits::Inherits<U>,
184    {
185        unsafe { UObjectRef::from_raw(self.handle) }
186    }
187}
188
189/// A typed reference the engine passes as a `UObject*`: an object
190/// (`UObjectRef<T>`) or a class (`SubclassOf<T>`, a `UClass*`). The generated
191/// bindings convert through it, so the declared type decides which one.
192pub trait ObjectPointer: Sized {
193    /// Wrap a pointer the engine returned.
194    ///
195    /// # Safety
196    /// The handle must be null or an object the type admits: an instance of
197    /// `T` for `UObjectRef<T>`, `T`'s class or a subclass for `SubclassOf<T>`.
198    unsafe fn from_object_handle(handle: UObjectHandle) -> Self;
199
200    /// The pointer to hand to the engine.
201    fn object_handle(&self) -> UObjectHandle;
202}
203
204impl<T: UeClass> ObjectPointer for UObjectRef<T> {
205    #[inline]
206    unsafe fn from_object_handle(handle: UObjectHandle) -> Self {
207        unsafe { UObjectRef::from_raw(handle) }
208    }
209
210    #[inline]
211    fn object_handle(&self) -> UObjectHandle {
212        self.handle
213    }
214}
215
216/// Blanket Deref: `UObjectRef<Child>` auto-derefs to `UObjectRef<Parent>`.
217/// Safe because `UObjectRef<T>` is `#[repr(transparent)]` over `UObjectHandle`.
218impl<T: HasParent> Deref for UObjectRef<T> {
219    type Target = UObjectRef<T::Parent>;
220    #[inline]
221    fn deref(&self) -> &UObjectRef<T::Parent> {
222        unsafe { &*(self as *const _ as *const UObjectRef<T::Parent>) }
223    }
224}
225
226impl<T: UeClass> UeHandle for UObjectRef<T> {
227    #[inline]
228    fn checked_handle(&self) -> RustealResult<UObjectHandle> {
229        self.checked().map(|c| c.raw())
230    }
231
232    #[inline]
233    fn raw_handle(&self) -> UObjectHandle {
234        self.raw()
235    }
236}
237
238impl<T: UeClass> std::fmt::Debug for UObjectRef<T> {
239    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240        f.debug_struct("UObjectRef")
241            .field("handle", &self.handle)
242            .field("valid", &self.is_valid())
243            .finish()
244    }
245}
246
247// ---------------------------------------------------------------------------
248// Checked<T>
249// ---------------------------------------------------------------------------
250
251/// A pre-validated handle to a UObject. Proves that the object was alive
252/// at the time of validation. Used as the receiver for codegen extension
253/// trait methods, which can then skip per-call validity checks.
254///
255/// Obtain via [`UObjectRef::checked()`] or [`Pinned::as_checked()`].
256#[derive(Clone, Copy, PartialEq, Eq, Hash)]
257#[repr(transparent)]
258pub struct Checked<T: UeClass> {
259    handle: UObjectHandle,
260    _marker: PhantomData<*const T>,
261}
262
263unsafe impl<T: UeClass> Send for Checked<T> {}
264
265impl<T: UeClass> Checked<T> {
266    /// Create a `Checked` handle without validation.
267    /// Used internally by `Pinned::as_checked()`.
268    #[inline]
269    pub(crate) fn new_unchecked(handle: UObjectHandle) -> Self {
270        Checked {
271            handle,
272            _marker: PhantomData,
273        }
274    }
275
276    /// Get the underlying raw handle.
277    #[inline]
278    pub fn raw(&self) -> UObjectHandle {
279        self.handle
280    }
281
282    /// Downgrade back to a `UObjectRef<T>`.
283    #[inline]
284    pub fn as_ref(&self) -> UObjectRef<T> {
285        unsafe { UObjectRef::from_raw(self.handle) }
286    }
287
288    /// Check whether this object is an instance of `U` (or a subclass of `U`).
289    /// No validity check needed — already validated at `Checked` construction.
290    #[inline]
291    pub fn is_a<U: UeClass>(&self) -> bool {
292        unsafe { ffi_dispatch::core_is_a(self.handle, U::static_class()) }
293    }
294
295    /// Cast to a different UClass type. Fails if not an instance of `U`.
296    pub fn cast<U: UeClass>(self) -> RustealResult<Checked<U>> {
297        if self.is_a::<U>() {
298            Ok(Checked::new_unchecked(self.handle))
299        } else {
300            Err(RustealError::InvalidCast)
301        }
302    }
303}
304
305impl<T: HasParent> Checked<T> {
306    /// Infallible upcast to the parent class. Zero-cost (same handle).
307    #[inline]
308    pub fn upcast(self) -> Checked<T::Parent> {
309        Checked::new_unchecked(self.handle)
310    }
311}
312
313/// Blanket Deref: `Checked<Child>` auto-derefs to `Checked<Parent>`.
314/// Safe because `Checked<T>` is `#[repr(transparent)]` over `UObjectHandle`.
315impl<T: HasParent> Deref for Checked<T> {
316    type Target = Checked<T::Parent>;
317    #[inline]
318    fn deref(&self) -> &Checked<T::Parent> {
319        unsafe { &*(self as *const _ as *const Checked<T::Parent>) }
320    }
321}
322
323impl<T: UeClass> ValidHandle for Checked<T> {
324    #[inline]
325    fn handle(&self) -> UObjectHandle {
326        self.handle
327    }
328}
329
330impl<T: UeClass> std::fmt::Debug for Checked<T> {
331    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332        f.debug_struct("Checked")
333            .field("handle", &self.handle)
334            .finish()
335    }
336}