Skip to main content

uika_runtime/
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;
8
9use uika_ffi::{UClassHandle, UObjectHandle};
10
11use crate::api::api;
12use crate::error::{check_ffi, UikaError, UikaResult};
13use crate::pinned::Pinned;
14use crate::traits::{UeClass, UeHandle, ValidHandle};
15
16/// A typed, non-owning reference to a UObject.
17///
18/// - `Copy` + `Send` — can be freely cloned and sent across threads.
19/// - `!Sync` — must only be *used* on the game thread.
20/// - Does not prevent garbage collection; call [`is_valid`](Self::is_valid)
21///   before use, or upgrade to [`Pinned<T>`] via [`pin`](Self::pin).
22#[derive(Clone, Copy, PartialEq, Eq, Hash)]
23#[repr(transparent)]
24pub struct UObjectRef<T: UeClass> {
25    handle: UObjectHandle,
26    _marker: PhantomData<*const T>, // *const T makes it !Sync
27}
28
29// Send: handles are raw identifiers safe to move between threads.
30// !Sync: enforced by PhantomData<*const T> — no shared references across threads.
31unsafe impl<T: UeClass> Send for UObjectRef<T> {}
32
33impl<T: UeClass> UObjectRef<T> {
34    /// Create from a raw FFI handle.
35    ///
36    /// # Safety
37    /// The caller must ensure the handle points to an object whose UClass
38    /// is `T` or a subclass of `T`.
39    #[inline]
40    pub unsafe fn from_raw(handle: UObjectHandle) -> Self {
41        UObjectRef {
42            handle,
43            _marker: PhantomData,
44        }
45    }
46
47    /// Get the underlying raw handle.
48    #[inline]
49    pub fn raw(&self) -> UObjectHandle {
50        self.handle
51    }
52
53    /// Check whether the underlying UObject is still alive.
54    #[inline]
55    pub fn is_valid(&self) -> bool {
56        unsafe { ((*api().core).is_valid)(self.handle) }
57    }
58
59    /// Validate that the object is still alive, returning a `Checked<T>`
60    /// handle that provides infallible access to extension trait methods.
61    #[inline]
62    pub fn checked(&self) -> UikaResult<Checked<T>> {
63        if self.is_valid() {
64            Ok(Checked {
65                handle: self.handle,
66                _marker: PhantomData,
67            })
68        } else {
69            Err(UikaError::ObjectDestroyed)
70        }
71    }
72
73    /// Cast to a different UClass type. Fails if the object is destroyed
74    /// or is not an instance of `U`.
75    pub fn cast<U: UeClass>(self) -> UikaResult<UObjectRef<U>> {
76        let h = self.checked()?.raw();
77        let target = U::static_class();
78        if unsafe { ((*api().core).is_a)(h, target) } {
79            Ok(UObjectRef {
80                handle: self.handle,
81                _marker: PhantomData,
82            })
83        } else {
84            Err(UikaError::InvalidCast)
85        }
86    }
87
88    /// Upgrade to a `Pinned<T>`, adding a GC root to keep the object alive.
89    pub fn pin(self) -> UikaResult<Pinned<T>> {
90        Pinned::new(self)
91    }
92
93    /// Get the object's FName as a String.
94    pub fn get_name(&self) -> UikaResult<String> {
95        let h = self.checked()?.raw();
96        // Stack buffer — 256 bytes is enough for virtually all UObject names.
97        let mut buf = [0u8; 256];
98        let mut out_len: u32 = 0;
99        let code = unsafe {
100            ((*api().core).get_name)(h, buf.as_mut_ptr(), buf.len() as u32, &mut out_len)
101        };
102        check_ffi(code)?;
103        // C++ writes valid UTF-8 (converted from TCHAR).
104        std::str::from_utf8(&buf[..out_len as usize])
105            .map(|s| s.to_owned())
106            .map_err(|_| UikaError::Internal("name is not valid UTF-8".into()))
107    }
108
109    /// Get the object's UClass handle.
110    pub fn get_class(&self) -> UikaResult<UClassHandle> {
111        let h = self.checked()?.raw();
112        Ok(unsafe { ((*api().core).get_class)(h) })
113    }
114
115    /// Get the object's Outer.
116    pub fn get_outer(&self) -> UikaResult<UObjectHandle> {
117        let h = self.checked()?.raw();
118        Ok(unsafe { ((*api().core).get_outer)(h) })
119    }
120}
121
122impl<T: UeClass> UeHandle for UObjectRef<T> {
123    #[inline]
124    fn checked_handle(&self) -> UikaResult<UObjectHandle> {
125        self.checked().map(|c| c.raw())
126    }
127
128    #[inline]
129    fn raw_handle(&self) -> UObjectHandle {
130        self.raw()
131    }
132}
133
134impl<T: UeClass> std::fmt::Debug for UObjectRef<T> {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        f.debug_struct("UObjectRef")
137            .field("handle", &self.handle)
138            .field("valid", &self.is_valid())
139            .finish()
140    }
141}
142
143// ---------------------------------------------------------------------------
144// Checked<T>
145// ---------------------------------------------------------------------------
146
147/// A pre-validated handle to a UObject. Proves that the object was alive
148/// at the time of validation. Used as the receiver for codegen extension
149/// trait methods, which can then skip per-call validity checks.
150///
151/// Obtain via [`UObjectRef::checked()`] or [`Pinned::as_checked()`].
152#[derive(Clone, Copy, PartialEq, Eq, Hash)]
153#[repr(transparent)]
154pub struct Checked<T: UeClass> {
155    handle: UObjectHandle,
156    _marker: PhantomData<*const T>,
157}
158
159unsafe impl<T: UeClass> Send for Checked<T> {}
160
161impl<T: UeClass> Checked<T> {
162    /// Create a `Checked` handle without validation.
163    /// Used internally by `Pinned::as_checked()`.
164    #[inline]
165    pub(crate) fn new_unchecked(handle: UObjectHandle) -> Self {
166        Checked {
167            handle,
168            _marker: PhantomData,
169        }
170    }
171
172    /// Get the underlying raw handle.
173    #[inline]
174    pub fn raw(&self) -> UObjectHandle {
175        self.handle
176    }
177
178    /// Downgrade back to a `UObjectRef<T>`.
179    #[inline]
180    pub fn as_ref(&self) -> UObjectRef<T> {
181        unsafe { UObjectRef::from_raw(self.handle) }
182    }
183}
184
185impl<T: UeClass> ValidHandle for Checked<T> {
186    #[inline]
187    fn handle(&self) -> UObjectHandle {
188        self.handle
189    }
190}
191
192impl<T: UeClass> std::fmt::Debug for Checked<T> {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        f.debug_struct("Checked")
195            .field("handle", &self.handle)
196            .finish()
197    }
198}