Skip to main content

rusteal_core/
dynamic_call.rs

1use std::any::Any;
2
3use rusteal_ffi::{FNameHandle, FPropertyHandle, UFunctionHandle, UObjectHandle};
4
5use crate::error::{RustealError, RustealResult, check_ffi};
6use crate::ffi_dispatch::{self, NATIVE_PTR_NULL, NativePtr, native_ptr_is_null};
7use crate::fname::FName;
8use crate::object_ref::UObjectRef;
9use crate::traits::UeClass;
10
11pub struct DynamicCall {
12    obj: UObjectHandle,
13    func: UFunctionHandle,
14    params: NativePtr,
15    delegate: FPropertyHandle,
16}
17
18impl DynamicCall {
19    #[track_caller]
20    pub fn new(obj: &UObjectRef<impl UeClass>, func_name: &str) -> RustealResult<Self> {
21        Self::on_object(obj.checked()?.raw(), func_name)
22    }
23
24    #[track_caller]
25    pub(crate) fn on_object(h: UObjectHandle, func_name: &str) -> RustealResult<Self> {
26        let func = unsafe {
27            ffi_dispatch::reflection_find_function(h, func_name.as_ptr(), func_name.len() as u32)
28        };
29
30        if func.is_null() {
31            return Err(RustealError::function_not_found(func_name.to_string()).on_handle(h));
32        }
33
34        let params = unsafe { ffi_dispatch::reflection_alloc_params(func) };
35
36        Ok(DynamicCall {
37            obj: h,
38            func,
39            params,
40            delegate: FPropertyHandle::null(),
41        })
42    }
43
44    #[track_caller]
45    pub fn for_delegate(
46        obj: &UObjectRef<impl UeClass>,
47        delegate: FPropertyHandle,
48    ) -> RustealResult<Self> {
49        let h = obj.checked()?.raw();
50
51        if delegate.is_null() {
52            return Err(RustealError::property_not_found("delegate").on_handle(h));
53        }
54
55        let func = unsafe { ffi_dispatch::reflection_get_delegate_signature(delegate) };
56
57        if func.is_null() {
58            return Err(RustealError::type_mismatch()
59                .context("the property is not a delegate")
60                .on_handle(h));
61        }
62
63        let params = unsafe { ffi_dispatch::reflection_alloc_params(func) };
64
65        Ok(DynamicCall {
66            obj: h,
67            func,
68            params,
69            delegate,
70        })
71    }
72
73    #[track_caller]
74    pub fn set<T: Copy + 'static>(&mut self, name: &str, value: T) -> RustealResult<()> {
75        let (prop, offset) = self.find_param(name)?;
76
77        if let Some(handle) = as_fname_handle(&value) {
78            return check_ffi(unsafe {
79                ffi_dispatch::property_set_fname(params_as_container(self.params), prop, handle)
80            });
81        }
82
83        unsafe {
84            ffi_dispatch::native_mem_write(self.params, offset as usize, value);
85        }
86
87        Ok(())
88    }
89
90    #[track_caller]
91    pub fn set_struct<T: crate::traits::UeStruct>(
92        &mut self,
93        name: &str,
94        value: &crate::containers::OwnedStruct<T>,
95    ) -> RustealResult<()> {
96        let (_, offset) = self.find_param(name)?;
97
98        check_ffi(unsafe {
99            ffi_dispatch::reflection_copy_struct(
100                T::static_struct(),
101                self.params.add(offset as usize),
102                value.as_bytes().as_ptr(),
103            )
104        })
105    }
106
107    #[track_caller]
108    pub fn set_struct_ref<T: crate::traits::UeStruct>(
109        &mut self,
110        name: &str,
111        value: &crate::struct_ref::UStructRef<T>,
112    ) -> RustealResult<()> {
113        let (_, offset) = self.find_param(name)?;
114
115        check_ffi(unsafe {
116            ffi_dispatch::reflection_copy_struct(
117                T::static_struct(),
118                self.params.add(offset as usize),
119                value.as_ptr().0 as *const u8,
120            )
121        })
122    }
123
124    #[track_caller]
125    pub fn broadcast(self) -> RustealResult<()> {
126        if self.delegate.is_null() {
127            return Err(RustealError::invalid_operation("not a delegate call"));
128        }
129
130        check_ffi(unsafe {
131            ffi_dispatch::delegate_broadcast_multicast(self.obj, self.delegate, self.params)
132        })
133    }
134
135    #[track_caller]
136    pub fn call(mut self) -> RustealResult<DynamicCallResult> {
137        let code =
138            unsafe { ffi_dispatch::reflection_call_function(self.obj, self.func, self.params) };
139
140        check_ffi(code).map_err(|e| {
141            e.context(format!("calling {}", function_name(self.func)))
142                .on_handle(self.obj)
143        })?;
144
145        let result = DynamicCallResult {
146            func: self.func,
147            params: self.params,
148        };
149
150        self.params = NATIVE_PTR_NULL;
151
152        Ok(result)
153    }
154
155    #[track_caller]
156    fn find_param(&self, name: &str) -> RustealResult<(FPropertyHandle, u32)> {
157        let prop = unsafe {
158            ffi_dispatch::reflection_get_function_param(self.func, name.as_ptr(), name.len() as u32)
159        };
160
161        if prop.is_null() {
162            return Err(missing_param(self.func, name).on_handle(self.obj));
163        }
164
165        let offset = unsafe { ffi_dispatch::reflection_get_property_offset(prop) };
166
167        Ok((prop, offset))
168    }
169}
170
171impl Drop for DynamicCall {
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}
178
179pub struct DynamicCallResult {
180    func: UFunctionHandle,
181    params: NativePtr,
182}
183
184impl DynamicCallResult {
185    #[track_caller]
186    pub fn get<T: Copy + 'static>(&self, name: &str) -> RustealResult<T> {
187        let prop = unsafe {
188            ffi_dispatch::reflection_get_function_param(self.func, name.as_ptr(), name.len() as u32)
189        };
190
191        if prop.is_null() {
192            return Err(missing_param(self.func, name));
193        }
194
195        if is_fname::<T>() {
196            let mut handle = FNameHandle::default();
197
198            check_ffi(unsafe {
199                ffi_dispatch::property_get_fname(
200                    params_as_container(self.params),
201                    prop,
202                    &mut handle,
203                )
204            })?;
205
206            let name = FName(handle);
207
208            return (&name as &dyn Any)
209                .downcast_ref::<T>()
210                .or_else(|| (&handle as &dyn Any).downcast_ref::<T>())
211                .copied()
212                .ok_or_else(|| RustealError::type_mismatch());
213        }
214
215        let offset = unsafe { ffi_dispatch::reflection_get_property_offset(prop) };
216
217        let value = unsafe { ffi_dispatch::native_mem_read(self.params, offset as usize) };
218
219        Ok(value)
220    }
221}
222
223fn function_name(func: UFunctionHandle) -> String {
224    crate::error::object_name(UObjectHandle(func.0)).unwrap_or_else(|| "a function".to_owned())
225}
226
227#[track_caller]
228fn missing_param(func: UFunctionHandle, name: &str) -> RustealError {
229    RustealError::property_not_found(format!("parameter {name} of {}", function_name(func)))
230}
231
232fn is_fname<T: 'static>() -> bool {
233    let id = std::any::TypeId::of::<T>();
234
235    id == std::any::TypeId::of::<FNameHandle>() || id == std::any::TypeId::of::<FName>()
236}
237
238fn as_fname_handle<T: 'static>(value: &T) -> Option<FNameHandle> {
239    let value = value as &dyn Any;
240
241    value
242        .downcast_ref::<FNameHandle>()
243        .copied()
244        .or_else(|| value.downcast_ref::<FName>().map(FName::handle))
245}
246
247fn params_as_container(params: NativePtr) -> UObjectHandle {
248    UObjectHandle(params as *mut std::ffi::c_void)
249}
250
251impl Drop for DynamicCallResult {
252    fn drop(&mut self) {
253        if !native_ptr_is_null(self.params) {
254            unsafe { ffi_dispatch::reflection_free_params(self.func, self.params) };
255        }
256    }
257}