Skip to main content

rusteal_core/
dynamic_call.rs

1// DynamicCall: reflection-based function invocation via ProcessEvent.
2//
3// This is the "safety net" fallback for functions not covered by codegen's
4// direct call path. It uses UE's reflection system to find functions, allocate
5// parameter buffers, set/get parameter values, and invoke via ProcessEvent.
6
7use rusteal_ffi::{FPropertyHandle, UFunctionHandle, UObjectHandle};
8
9use crate::error::{check_ffi, RustealError, RustealResult};
10use crate::ffi_dispatch::{self, NativePtr, NATIVE_PTR_NULL, native_ptr_is_null};
11use crate::object_ref::UObjectRef;
12use crate::traits::UeClass;
13
14/// Builder for a reflection-based function call.
15///
16/// Typical usage:
17/// ```ignore
18/// let mut call = DynamicCall::new(&obj, "DoSomething")?;
19/// call.set::<i32>("Param1", 42)?;
20/// let result = call.call()?;
21/// let ret: f32 = result.get::<f32>("ReturnValue")?;
22/// ```
23pub struct DynamicCall {
24    obj: UObjectHandle,
25    func: UFunctionHandle,
26    params: NativePtr,
27    /// The delegate property a broadcast fires; null for a function call.
28    delegate: FPropertyHandle,
29}
30
31impl DynamicCall {
32    /// Prepare a reflection call to the named function on `obj`.
33    pub fn new(obj: &UObjectRef<impl UeClass>, func_name: &str) -> RustealResult<Self> {
34        let h = obj.checked()?.raw();
35        let func = unsafe {
36            ffi_dispatch::reflection_find_function(h, func_name.as_ptr(), func_name.len() as u32)
37        };
38        if func.is_null() {
39            return Err(RustealError::FunctionNotFound(func_name.to_string()));
40        }
41        let params = unsafe { ffi_dispatch::reflection_alloc_params(func) };
42        Ok(DynamicCall {
43            obj: h,
44            func,
45            params,
46            delegate: FPropertyHandle::null(),
47        })
48    }
49
50    /// Write a parameter value into the params buffer.
51    ///
52    /// # Safety contract
53    /// `T` must match the actual UE property type at the named parameter.
54    /// Using the wrong type leads to undefined behavior at runtime. This is
55    /// inherently less safe than the codegen direct-call path.
56    /// Parameters for broadcasting the multicast delegate `delegate` (a
57    /// delegate property) on `obj`: set them, then [`DynamicCall::broadcast`].
58    pub fn for_delegate(obj: &UObjectRef<impl UeClass>, delegate: FPropertyHandle) -> RustealResult<Self> {
59        let h = obj.checked()?.raw();
60        if delegate.is_null() {
61            return Err(RustealError::PropertyNotFound("delegate".to_string()));
62        }
63        let func = unsafe { ffi_dispatch::reflection_get_delegate_signature(delegate) };
64        if func.is_null() {
65            return Err(RustealError::TypeMismatch);
66        }
67        let params = unsafe { ffi_dispatch::reflection_alloc_params(func) };
68        Ok(DynamicCall { obj: h, func, params, delegate })
69    }
70
71    pub fn set<T: Copy>(&mut self, name: &str, value: T) -> RustealResult<()> {
72        let (prop, offset) = self.find_param(name)?;
73        let _ = prop; // used only for lookup
74        // SAFETY: The offset is provided by UE reflection and the caller
75        // guarantees T matches the property type.
76        unsafe {
77            ffi_dispatch::native_mem_write(self.params, offset as usize, value);
78        }
79        Ok(())
80    }
81
82    /// Copy a struct into a parameter with the struct's own copy semantics.
83    pub fn set_struct<T: crate::traits::UeStruct>(
84        &mut self,
85        name: &str,
86        value: &crate::containers::OwnedStruct<T>,
87    ) -> RustealResult<()> {
88        let (_, offset) = self.find_param(name)?;
89        check_ffi(unsafe {
90            ffi_dispatch::reflection_copy_struct(
91                T::static_struct(),
92                self.params.add(offset as usize),
93                value.as_bytes().as_ptr(),
94            )
95        })
96    }
97
98    /// Invoke the function via ProcessEvent. Consumes this builder and returns
99    /// a `DynamicCallResult` for reading output/return values.
100    /// Fire every function bound to the delegate this call was made
101    /// [`for`](DynamicCall::for_delegate), as C++'s `Broadcast` does.
102    pub fn broadcast(self) -> RustealResult<()> {
103        if self.delegate.is_null() {
104            return Err(RustealError::InvalidOperation("not a delegate call".into()));
105        }
106        check_ffi(unsafe { ffi_dispatch::delegate_broadcast_multicast(self.obj, self.delegate, self.params) })
107    }
108
109    pub fn call(mut self) -> RustealResult<DynamicCallResult> {
110        let code =
111            unsafe { ffi_dispatch::reflection_call_function(self.obj, self.func, self.params) };
112        check_ffi(code)?;
113        // Transfer params ownership to DynamicCallResult.
114        let result = DynamicCallResult {
115            func: self.func,
116            params: self.params,
117        };
118        // Prevent Drop from double-freeing.
119        self.params = NATIVE_PTR_NULL;
120        Ok(result)
121    }
122
123    /// Look up a named parameter and return its property handle + offset.
124    fn find_param(&self, name: &str) -> RustealResult<(FPropertyHandle, u32)> {
125        let prop = unsafe {
126            ffi_dispatch::reflection_get_function_param(self.func, name.as_ptr(), name.len() as u32)
127        };
128        if prop.is_null() {
129            return Err(RustealError::PropertyNotFound(name.to_string()));
130        }
131        let offset = unsafe { ffi_dispatch::reflection_get_property_offset(prop) };
132        Ok((prop, offset))
133    }
134}
135
136impl Drop for DynamicCall {
137    fn drop(&mut self) {
138        if !native_ptr_is_null(self.params) {
139            unsafe { ffi_dispatch::reflection_free_params(self.func, self.params) };
140        }
141    }
142}
143
144/// Holds the params buffer after a successful `DynamicCall::call()`.
145/// Use `get()` to read output parameters and return values.
146pub struct DynamicCallResult {
147    func: UFunctionHandle,
148    params: NativePtr,
149}
150
151impl DynamicCallResult {
152    /// Read an output parameter or return value from the params buffer.
153    ///
154    /// # Safety contract
155    /// `T` must match the actual UE property type at the named parameter.
156    pub fn get<T: Copy>(&self, name: &str) -> RustealResult<T> {
157        let prop = unsafe {
158            ffi_dispatch::reflection_get_function_param(self.func, name.as_ptr(), name.len() as u32)
159        };
160        if prop.is_null() {
161            return Err(RustealError::PropertyNotFound(name.to_string()));
162        }
163        let offset = unsafe { ffi_dispatch::reflection_get_property_offset(prop) };
164        // SAFETY: The offset is provided by UE reflection and the caller
165        // guarantees T matches the property type.
166        let value = unsafe { ffi_dispatch::native_mem_read(self.params, offset as usize) };
167        Ok(value)
168    }
169}
170
171impl Drop for DynamicCallResult {
172    fn drop(&mut self) {
173        if !native_ptr_is_null(self.params) {
174            unsafe { ffi_dispatch::reflection_free_params(self.func, self.params) };
175        }
176    }
177}