Skip to main content

uika_runtime/
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 uika_ffi::{FPropertyHandle, UFunctionHandle, UObjectHandle};
8
9use crate::api::api;
10use crate::error::{check_ffi, UikaError, UikaResult};
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: *mut u8,
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) -> UikaResult<Self> {
32        let h = obj.checked()?.raw();
33        let func = unsafe {
34            ((*api().reflection).find_function)(h, func_name.as_ptr(), func_name.len() as u32)
35        };
36        if func.0.is_null() {
37            return Err(UikaError::FunctionNotFound(func_name.to_string()));
38        }
39        let params = unsafe { ((*api().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) -> UikaResult<()> {
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            std::ptr::write_unaligned(self.params.add(offset as usize) as *mut T, value);
60        }
61        Ok(())
62    }
63
64    /// Invoke the function via ProcessEvent. Consumes this builder and returns
65    /// a `DynamicCallResult` for reading output/return values.
66    pub fn call(mut self) -> UikaResult<DynamicCallResult> {
67        let code =
68            unsafe { ((*api().reflection).call_function)(self.obj, self.func, self.params) };
69        check_ffi(code)?;
70        // Transfer params ownership to DynamicCallResult.
71        let result = DynamicCallResult {
72            func: self.func,
73            params: self.params,
74        };
75        // Prevent Drop from double-freeing.
76        self.params = std::ptr::null_mut();
77        Ok(result)
78    }
79
80    /// Look up a named parameter and return its property handle + offset.
81    fn find_param(&self, name: &str) -> UikaResult<(FPropertyHandle, u32)> {
82        let prop = unsafe {
83            ((*api().reflection).get_function_param)(self.func, name.as_ptr(), name.len() as u32)
84        };
85        if prop.0.is_null() {
86            return Err(UikaError::PropertyNotFound(name.to_string()));
87        }
88        let offset = unsafe { ((*api().reflection).get_property_offset)(prop) };
89        Ok((prop, offset))
90    }
91}
92
93impl Drop for DynamicCall {
94    fn drop(&mut self) {
95        if !self.params.is_null() {
96            unsafe { ((*api().reflection).free_params)(self.func, self.params) };
97        }
98    }
99}
100
101/// Holds the params buffer after a successful `DynamicCall::call()`.
102/// Use `get()` to read output parameters and return values.
103pub struct DynamicCallResult {
104    func: UFunctionHandle,
105    params: *mut u8,
106}
107
108impl DynamicCallResult {
109    /// Read an output parameter or return value from the params buffer.
110    ///
111    /// # Safety contract
112    /// `T` must match the actual UE property type at the named parameter.
113    pub fn get<T: Copy>(&self, name: &str) -> UikaResult<T> {
114        let prop = unsafe {
115            ((*api().reflection).get_function_param)(self.func, name.as_ptr(), name.len() as u32)
116        };
117        if prop.0.is_null() {
118            return Err(UikaError::PropertyNotFound(name.to_string()));
119        }
120        let offset = unsafe { ((*api().reflection).get_property_offset)(prop) };
121        // SAFETY: The offset is provided by UE reflection and the caller
122        // guarantees T matches the property type.
123        let value = unsafe { std::ptr::read_unaligned(self.params.add(offset as usize) as *const T) };
124        Ok(value)
125    }
126}
127
128impl Drop for DynamicCallResult {
129    fn drop(&mut self) {
130        if !self.params.is_null() {
131            unsafe { ((*api().reflection).free_params)(self.func, self.params) };
132        }
133    }
134}