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}
28
29impl DynamicCall {
30    /// Prepare a reflection call to the named function on `obj`.
31    pub fn new(obj: &UObjectRef<impl UeClass>, func_name: &str) -> RustealResult<Self> {
32        let h = obj.checked()?.raw();
33        let func = unsafe {
34            ffi_dispatch::reflection_find_function(h, func_name.as_ptr(), func_name.len() as u32)
35        };
36        if func.is_null() {
37            return Err(RustealError::FunctionNotFound(func_name.to_string()));
38        }
39        let params = unsafe { ffi_dispatch::reflection_alloc_params(func) };
40        Ok(DynamicCall {
41            obj: h,
42            func,
43            params,
44        })
45    }
46
47    /// Write a parameter value into the params buffer.
48    ///
49    /// # Safety contract
50    /// `T` must match the actual UE property type at the named parameter.
51    /// Using the wrong type leads to undefined behavior at runtime. This is
52    /// inherently less safe than the codegen direct-call path.
53    pub fn set<T: Copy>(&mut self, name: &str, value: T) -> RustealResult<()> {
54        let (prop, offset) = self.find_param(name)?;
55        let _ = prop; // used only for lookup
56        // SAFETY: The offset is provided by UE reflection and the caller
57        // guarantees T matches the property type.
58        unsafe {
59            ffi_dispatch::native_mem_write(self.params, offset as usize, value);
60        }
61        Ok(())
62    }
63
64    /// Copy a struct into a parameter with the struct's own copy semantics.
65    pub fn set_struct<T: crate::traits::UeStruct>(
66        &mut self,
67        name: &str,
68        value: &crate::containers::OwnedStruct<T>,
69    ) -> RustealResult<()> {
70        let (_, offset) = self.find_param(name)?;
71        check_ffi(unsafe {
72            ffi_dispatch::reflection_copy_struct(
73                T::static_struct(),
74                self.params.add(offset as usize),
75                value.as_bytes().as_ptr(),
76            )
77        })
78    }
79
80    /// Invoke the function via ProcessEvent. Consumes this builder and returns
81    /// a `DynamicCallResult` for reading output/return values.
82    pub fn call(mut self) -> RustealResult<DynamicCallResult> {
83        let code =
84            unsafe { ffi_dispatch::reflection_call_function(self.obj, self.func, self.params) };
85        check_ffi(code)?;
86        // Transfer params ownership to DynamicCallResult.
87        let result = DynamicCallResult {
88            func: self.func,
89            params: self.params,
90        };
91        // Prevent Drop from double-freeing.
92        self.params = NATIVE_PTR_NULL;
93        Ok(result)
94    }
95
96    /// Look up a named parameter and return its property handle + offset.
97    fn find_param(&self, name: &str) -> RustealResult<(FPropertyHandle, u32)> {
98        let prop = unsafe {
99            ffi_dispatch::reflection_get_function_param(self.func, name.as_ptr(), name.len() as u32)
100        };
101        if prop.is_null() {
102            return Err(RustealError::PropertyNotFound(name.to_string()));
103        }
104        let offset = unsafe { ffi_dispatch::reflection_get_property_offset(prop) };
105        Ok((prop, offset))
106    }
107}
108
109impl Drop for DynamicCall {
110    fn drop(&mut self) {
111        if !native_ptr_is_null(self.params) {
112            unsafe { ffi_dispatch::reflection_free_params(self.func, self.params) };
113        }
114    }
115}
116
117/// Holds the params buffer after a successful `DynamicCall::call()`.
118/// Use `get()` to read output parameters and return values.
119pub struct DynamicCallResult {
120    func: UFunctionHandle,
121    params: NativePtr,
122}
123
124impl DynamicCallResult {
125    /// Read an output parameter or return value from the params buffer.
126    ///
127    /// # Safety contract
128    /// `T` must match the actual UE property type at the named parameter.
129    pub fn get<T: Copy>(&self, name: &str) -> RustealResult<T> {
130        let prop = unsafe {
131            ffi_dispatch::reflection_get_function_param(self.func, name.as_ptr(), name.len() as u32)
132        };
133        if prop.is_null() {
134            return Err(RustealError::PropertyNotFound(name.to_string()));
135        }
136        let offset = unsafe { ffi_dispatch::reflection_get_property_offset(prop) };
137        // SAFETY: The offset is provided by UE reflection and the caller
138        // guarantees T matches the property type.
139        let value = unsafe { ffi_dispatch::native_mem_read(self.params, offset as usize) };
140        Ok(value)
141    }
142}
143
144impl Drop for DynamicCallResult {
145    fn drop(&mut self) {
146        if !native_ptr_is_null(self.params) {
147            unsafe { ffi_dispatch::reflection_free_params(self.func, self.params) };
148        }
149    }
150}