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#[derive(Clone, Copy, PartialEq, Eq, Hash)]
24#[repr(transparent)]
25pub struct UObjectRef<T: UeClass> {
26    handle: UObjectHandle,
27    _marker: PhantomData<*const T>, // *const T makes it !Sync
28}
29
30// Send: handles are raw identifiers safe to move between threads.
31// !Sync: enforced by PhantomData<*const T> — no shared references across threads.
32unsafe impl<T: UeClass> Send for UObjectRef<T> {}
33
34impl<T: UeClass> UObjectRef<T> {
35    /// Create from a raw FFI handle.
36    ///
37    /// # Safety
38    /// The caller must ensure the handle points to an object whose UClass
39    /// is `T` or a subclass of `T`.
40    #[inline]
41    pub unsafe fn from_raw(handle: UObjectHandle) -> Self {
42        UObjectRef {
43            handle,
44            _marker: PhantomData,
45        }
46    }
47
48    /// Get the underlying raw handle.
49    #[inline]
50    pub fn raw(&self) -> UObjectHandle {
51        self.handle
52    }
53
54    /// Check whether the underlying UObject is still alive.
55    #[inline]
56    pub fn is_valid(&self) -> bool {
57        unsafe { ffi_dispatch::core_is_valid(self.handle) }
58    }
59
60    /// Validate that the object is still alive, returning a `Checked<T>`
61    /// handle that provides infallible access to extension trait methods.
62    #[inline]
63    pub fn checked(&self) -> RustealResult<Checked<T>> {
64        if self.is_valid() {
65            Ok(Checked {
66                handle: self.handle,
67                _marker: PhantomData,
68            })
69        } else {
70            Err(RustealError::ObjectDestroyed)
71        }
72    }
73
74    /// Cast to a different UClass type. Fails if the object is destroyed
75    /// or is not an instance of `U`.
76    pub fn cast<U: UeClass>(self) -> RustealResult<UObjectRef<U>> {
77        let h = self.checked()?.raw();
78        let target = U::static_class();
79        if unsafe { ffi_dispatch::core_is_a(h, target) } {
80            Ok(UObjectRef {
81                handle: self.handle,
82                _marker: PhantomData,
83            })
84        } else {
85            Err(RustealError::InvalidCast)
86        }
87    }
88
89    /// Upgrade to a `Pinned<T>`, adding a GC root to keep the object alive.
90    pub fn pin(self) -> RustealResult<Pinned<T>> {
91        Pinned::new(self)
92    }
93
94    /// Get the object's FName as a String.
95    pub fn get_name(&self) -> RustealResult<String> {
96        let h = self.checked()?.raw();
97        // Stack buffer — 256 bytes is enough for virtually all UObject names.
98        let mut buf = [0u8; 256];
99        let mut out_len: u32 = 0;
100        let code = unsafe {
101            ffi_dispatch::core_get_name(h, buf.as_mut_ptr(), buf.len() as u32, &mut out_len)
102        };
103        check_ffi(code)?;
104        // C++ writes valid UTF-8 (converted from TCHAR).
105        std::str::from_utf8(&buf[..out_len as usize])
106            .map(|s| s.to_owned())
107            .map_err(|_| RustealError::Internal("name is not valid UTF-8".into()))
108    }
109
110    /// Get the object's UClass handle.
111    pub fn get_class(&self) -> RustealResult<UClassHandle> {
112        let h = self.checked()?.raw();
113        Ok(unsafe { ffi_dispatch::core_get_class(h) })
114    }
115
116    /// Get the object's Outer.
117    pub fn get_outer(&self) -> RustealResult<UObjectHandle> {
118        let h = self.checked()?.raw();
119        Ok(unsafe { ffi_dispatch::core_get_outer(h) })
120    }
121
122    /// Check whether this object is an instance of `U` (or a subclass of `U`).
123    /// Returns `false` if the object has been destroyed.
124    #[inline]
125    pub fn is_a<U: UeClass>(&self) -> bool {
126        self.is_valid() && unsafe { ffi_dispatch::core_is_a(self.handle, U::static_class()) }
127    }
128}
129
130impl<T: HasParent> UObjectRef<T> {
131    /// Infallible upcast to the parent class. Zero-cost (same handle).
132    #[inline]
133    pub fn upcast(self) -> UObjectRef<T::Parent> {
134        unsafe { UObjectRef::from_raw(self.handle) }
135    }
136}
137
138/// Blanket Deref: `UObjectRef<Child>` auto-derefs to `UObjectRef<Parent>`.
139/// Safe because `UObjectRef<T>` is `#[repr(transparent)]` over `UObjectHandle`.
140impl<T: HasParent> Deref for UObjectRef<T> {
141    type Target = UObjectRef<T::Parent>;
142    #[inline]
143    fn deref(&self) -> &UObjectRef<T::Parent> {
144        unsafe { &*(self as *const _ as *const UObjectRef<T::Parent>) }
145    }
146}
147
148impl<T: UeClass> UeHandle for UObjectRef<T> {
149    #[inline]
150    fn checked_handle(&self) -> RustealResult<UObjectHandle> {
151        self.checked().map(|c| c.raw())
152    }
153
154    #[inline]
155    fn raw_handle(&self) -> UObjectHandle {
156        self.raw()
157    }
158}
159
160impl<T: UeClass> std::fmt::Debug for UObjectRef<T> {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        f.debug_struct("UObjectRef")
163            .field("handle", &self.handle)
164            .field("valid", &self.is_valid())
165            .finish()
166    }
167}
168
169// ---------------------------------------------------------------------------
170// Checked<T>
171// ---------------------------------------------------------------------------
172
173/// A pre-validated handle to a UObject. Proves that the object was alive
174/// at the time of validation. Used as the receiver for codegen extension
175/// trait methods, which can then skip per-call validity checks.
176///
177/// Obtain via [`UObjectRef::checked()`] or [`Pinned::as_checked()`].
178#[derive(Clone, Copy, PartialEq, Eq, Hash)]
179#[repr(transparent)]
180pub struct Checked<T: UeClass> {
181    handle: UObjectHandle,
182    _marker: PhantomData<*const T>,
183}
184
185unsafe impl<T: UeClass> Send for Checked<T> {}
186
187impl<T: UeClass> Checked<T> {
188    /// Create a `Checked` handle without validation.
189    /// Used internally by `Pinned::as_checked()`.
190    #[inline]
191    pub(crate) fn new_unchecked(handle: UObjectHandle) -> Self {
192        Checked {
193            handle,
194            _marker: PhantomData,
195        }
196    }
197
198    /// Get the underlying raw handle.
199    #[inline]
200    pub fn raw(&self) -> UObjectHandle {
201        self.handle
202    }
203
204    /// Downgrade back to a `UObjectRef<T>`.
205    #[inline]
206    pub fn as_ref(&self) -> UObjectRef<T> {
207        unsafe { UObjectRef::from_raw(self.handle) }
208    }
209
210    /// Check whether this object is an instance of `U` (or a subclass of `U`).
211    /// No validity check needed — already validated at `Checked` construction.
212    #[inline]
213    pub fn is_a<U: UeClass>(&self) -> bool {
214        unsafe { ffi_dispatch::core_is_a(self.handle, U::static_class()) }
215    }
216
217    /// Cast to a different UClass type. Fails if not an instance of `U`.
218    pub fn cast<U: UeClass>(self) -> RustealResult<Checked<U>> {
219        if self.is_a::<U>() {
220            Ok(Checked::new_unchecked(self.handle))
221        } else {
222            Err(RustealError::InvalidCast)
223        }
224    }
225}
226
227impl<T: HasParent> Checked<T> {
228    /// Infallible upcast to the parent class. Zero-cost (same handle).
229    #[inline]
230    pub fn upcast(self) -> Checked<T::Parent> {
231        Checked::new_unchecked(self.handle)
232    }
233}
234
235/// Blanket Deref: `Checked<Child>` auto-derefs to `Checked<Parent>`.
236/// Safe because `Checked<T>` is `#[repr(transparent)]` over `UObjectHandle`.
237impl<T: HasParent> Deref for Checked<T> {
238    type Target = Checked<T::Parent>;
239    #[inline]
240    fn deref(&self) -> &Checked<T::Parent> {
241        unsafe { &*(self as *const _ as *const Checked<T::Parent>) }
242    }
243}
244
245impl<T: UeClass> ValidHandle for Checked<T> {
246    #[inline]
247    fn handle(&self) -> UObjectHandle {
248        self.handle
249    }
250}
251
252impl<T: UeClass> std::fmt::Debug for Checked<T> {
253    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254        f.debug_struct("Checked")
255            .field("handle", &self.handle)
256            .finish()
257    }
258}