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/// A typed reference the engine passes as a `UObject*`: an object
139/// (`UObjectRef<T>`) or a class (`SubclassOf<T>`, a `UClass*`). The generated
140/// bindings convert through it, so the declared type decides which one.
141pub trait ObjectPointer: Sized {
142    /// Wrap a pointer the engine returned.
143    ///
144    /// # Safety
145    /// The handle must be null or an object the type admits: an instance of
146    /// `T` for `UObjectRef<T>`, `T`'s class or a subclass for `SubclassOf<T>`.
147    unsafe fn from_object_handle(handle: UObjectHandle) -> Self;
148
149    /// The pointer to hand to the engine.
150    fn object_handle(&self) -> UObjectHandle;
151}
152
153impl<T: UeClass> ObjectPointer for UObjectRef<T> {
154    #[inline]
155    unsafe fn from_object_handle(handle: UObjectHandle) -> Self {
156        unsafe { UObjectRef::from_raw(handle) }
157    }
158
159    #[inline]
160    fn object_handle(&self) -> UObjectHandle {
161        self.handle
162    }
163}
164
165/// Blanket Deref: `UObjectRef<Child>` auto-derefs to `UObjectRef<Parent>`.
166/// Safe because `UObjectRef<T>` is `#[repr(transparent)]` over `UObjectHandle`.
167impl<T: HasParent> Deref for UObjectRef<T> {
168    type Target = UObjectRef<T::Parent>;
169    #[inline]
170    fn deref(&self) -> &UObjectRef<T::Parent> {
171        unsafe { &*(self as *const _ as *const UObjectRef<T::Parent>) }
172    }
173}
174
175impl<T: UeClass> UeHandle for UObjectRef<T> {
176    #[inline]
177    fn checked_handle(&self) -> RustealResult<UObjectHandle> {
178        self.checked().map(|c| c.raw())
179    }
180
181    #[inline]
182    fn raw_handle(&self) -> UObjectHandle {
183        self.raw()
184    }
185}
186
187impl<T: UeClass> std::fmt::Debug for UObjectRef<T> {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        f.debug_struct("UObjectRef")
190            .field("handle", &self.handle)
191            .field("valid", &self.is_valid())
192            .finish()
193    }
194}
195
196// ---------------------------------------------------------------------------
197// Checked<T>
198// ---------------------------------------------------------------------------
199
200/// A pre-validated handle to a UObject. Proves that the object was alive
201/// at the time of validation. Used as the receiver for codegen extension
202/// trait methods, which can then skip per-call validity checks.
203///
204/// Obtain via [`UObjectRef::checked()`] or [`Pinned::as_checked()`].
205#[derive(Clone, Copy, PartialEq, Eq, Hash)]
206#[repr(transparent)]
207pub struct Checked<T: UeClass> {
208    handle: UObjectHandle,
209    _marker: PhantomData<*const T>,
210}
211
212unsafe impl<T: UeClass> Send for Checked<T> {}
213
214impl<T: UeClass> Checked<T> {
215    /// Create a `Checked` handle without validation.
216    /// Used internally by `Pinned::as_checked()`.
217    #[inline]
218    pub(crate) fn new_unchecked(handle: UObjectHandle) -> Self {
219        Checked {
220            handle,
221            _marker: PhantomData,
222        }
223    }
224
225    /// Get the underlying raw handle.
226    #[inline]
227    pub fn raw(&self) -> UObjectHandle {
228        self.handle
229    }
230
231    /// Downgrade back to a `UObjectRef<T>`.
232    #[inline]
233    pub fn as_ref(&self) -> UObjectRef<T> {
234        unsafe { UObjectRef::from_raw(self.handle) }
235    }
236
237    /// Check whether this object is an instance of `U` (or a subclass of `U`).
238    /// No validity check needed — already validated at `Checked` construction.
239    #[inline]
240    pub fn is_a<U: UeClass>(&self) -> bool {
241        unsafe { ffi_dispatch::core_is_a(self.handle, U::static_class()) }
242    }
243
244    /// Cast to a different UClass type. Fails if not an instance of `U`.
245    pub fn cast<U: UeClass>(self) -> RustealResult<Checked<U>> {
246        if self.is_a::<U>() {
247            Ok(Checked::new_unchecked(self.handle))
248        } else {
249            Err(RustealError::InvalidCast)
250        }
251    }
252}
253
254impl<T: HasParent> Checked<T> {
255    /// Infallible upcast to the parent class. Zero-cost (same handle).
256    #[inline]
257    pub fn upcast(self) -> Checked<T::Parent> {
258        Checked::new_unchecked(self.handle)
259    }
260}
261
262/// Blanket Deref: `Checked<Child>` auto-derefs to `Checked<Parent>`.
263/// Safe because `Checked<T>` is `#[repr(transparent)]` over `UObjectHandle`.
264impl<T: HasParent> Deref for Checked<T> {
265    type Target = Checked<T::Parent>;
266    #[inline]
267    fn deref(&self) -> &Checked<T::Parent> {
268        unsafe { &*(self as *const _ as *const Checked<T::Parent>) }
269    }
270}
271
272impl<T: UeClass> ValidHandle for Checked<T> {
273    #[inline]
274    fn handle(&self) -> UObjectHandle {
275        self.handle
276    }
277}
278
279impl<T: UeClass> std::fmt::Debug for Checked<T> {
280    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281        f.debug_struct("Checked")
282            .field("handle", &self.handle)
283            .finish()
284    }
285}