Skip to main content

rusteal_codegen/rust_gen/
delegates.rs

1use crate::context::CodegenContext;
2use crate::naming::to_snake_case;
3use crate::schema::PropertyInfo;
4use crate::type_map::{self, ConversionKind};
5
6pub struct DelegateInfo<'a> {
7    pub prop: &'a PropertyInfo,
8    pub class_name: &'a str,
9    pub rust_name: String,
10    pub struct_name: String,
11    pub is_multicast: bool,
12    pub params: Vec<DelegateParam>,
13}
14
15pub struct DelegateParam {
16    pub name: String,
17    pub rust_type: String,
18    pub conversion: ParamConversion,
19}
20
21pub enum ParamConversion {
22    Primitive(String),
23    ObjectRef(String),
24    Enum {
25        rust_type: String,
26        repr: String,
27    },
28    FName,
29    String,
30    Struct {
31        struct_name: String,
32        cpp_name: String,
33    },
34}
35
36pub fn collect_delegate_props<'a>(
37    props: &'a [PropertyInfo],
38    class_name: &'a str,
39    ctx: &CodegenContext,
40) -> Vec<DelegateInfo<'a>> {
41    let mut result = Vec::new();
42
43    for prop in props {
44        let mapped = type_map::map_property_type(
45            &prop.prop_type,
46            prop.class_name.as_deref(),
47            prop.struct_name.as_deref(),
48            prop.enum_name.as_deref(),
49            prop.enum_underlying_type.as_deref(),
50            prop.meta_class_name.as_deref(),
51            prop.interface_name.as_deref(),
52        );
53
54        if !matches!(
55            mapped.rust_to_ffi,
56            ConversionKind::Delegate | ConversionKind::MulticastDelegate
57        ) {
58            continue;
59        }
60
61        let is_multicast = matches!(mapped.rust_to_ffi, ConversionKind::MulticastDelegate);
62
63        let func_info = match &prop.func_info {
64            Some(fi) => fi,
65            None => continue,
66        };
67
68        let params_json = match func_info.get("params").and_then(|p| p.as_array()) {
69            Some(params) => params,
70            None => &Vec::new() as &Vec<serde_json::Value>,
71        };
72
73        let mut params = Vec::new();
74        let mut all_supported = true;
75
76        for param_value in params_json {
77            let param_name = param_value
78                .get("name")
79                .and_then(|n| n.as_str())
80                .unwrap_or("unknown");
81
82            let param_type = match param_value.get("type").and_then(|t| t.as_str()) {
83                Some(t) => t,
84                None => {
85                    all_supported = false;
86                    break;
87                }
88            };
89
90            match resolve_delegate_param(param_name, param_type, param_value, ctx) {
91                Some(dp) => params.push(dp),
92                None => {
93                    all_supported = false;
94                    break;
95                }
96            }
97        }
98
99        if !all_supported {
100            continue;
101        }
102
103        let rust_name = to_snake_case(&prop.name);
104        let struct_name = format!("{}{}Delegate", class_name, prop.name);
105
106        result.push(DelegateInfo {
107            prop,
108            class_name,
109            rust_name,
110            struct_name,
111            is_multicast,
112            params,
113        });
114    }
115
116    result
117}
118
119fn resolve_delegate_param(
120    name: &str,
121    prop_type: &str,
122    value: &serde_json::Value,
123    ctx: &CodegenContext,
124) -> Option<DelegateParam> {
125    let param_name = to_snake_case(name);
126
127    match prop_type {
128        "BoolProperty" => Some(DelegateParam {
129            name: param_name,
130            rust_type: "bool".into(),
131            conversion: ParamConversion::Primitive("bool".into()),
132        }),
133        "Int8Property" => Some(DelegateParam {
134            name: param_name,
135            rust_type: "i8".into(),
136            conversion: ParamConversion::Primitive("i8".into()),
137        }),
138        "ByteProperty" => {
139            if let Some(en) = value.get("enum_name").and_then(|v| v.as_str()) {
140                if ctx.enums.contains_key(en) {
141                    Some(DelegateParam {
142                        name: param_name,
143                        rust_type: en.to_string(),
144                        conversion: ParamConversion::Enum {
145                            rust_type: en.to_string(),
146                            repr: ctx.enum_actual_repr(en).unwrap_or("u8").to_string(),
147                        },
148                    })
149                } else {
150                    None
151                }
152            } else {
153                Some(DelegateParam {
154                    name: param_name,
155                    rust_type: "u8".into(),
156                    conversion: ParamConversion::Primitive("u8".into()),
157                })
158            }
159        }
160        "Int16Property" => prim_param(param_name, "i16"),
161        "UInt16Property" => prim_param(param_name, "u16"),
162        "IntProperty" => prim_param(param_name, "i32"),
163        "UInt32Property" => prim_param(param_name, "u32"),
164        "Int64Property" => prim_param(param_name, "i64"),
165        "UInt64Property" => prim_param(param_name, "u64"),
166        "FloatProperty" => prim_param(param_name, "f32"),
167        "DoubleProperty" => prim_param(param_name, "f64"),
168        "NameProperty" => Some(DelegateParam {
169            name: param_name,
170            rust_type: "rusteal_core::FNameHandle".into(),
171            conversion: ParamConversion::FName,
172        }),
173        "StrProperty" | "TextProperty" => Some(DelegateParam {
174            name: param_name,
175            rust_type: "String".into(),
176            conversion: ParamConversion::String,
177        }),
178        "ClassProperty"
179            if value
180                .get("meta_class_name")
181                .and_then(|v| v.as_str())
182                .is_some_and(|meta| ctx.classes.contains_key(meta)) =>
183        {
184            let meta = value["meta_class_name"].as_str().unwrap_or_default();
185
186            Some(DelegateParam {
187                name: param_name,
188                rust_type: format!("rusteal_core::SubclassOf<{meta}>"),
189                conversion: ParamConversion::ObjectRef(meta.to_string()),
190            })
191        }
192        "ObjectProperty" | "ClassProperty" => {
193            let cls = value.get("class_name").and_then(|v| v.as_str());
194
195            if let Some(cls) = cls {
196                if ctx.classes.contains_key(cls) {
197                    Some(DelegateParam {
198                        name: param_name,
199                        rust_type: format!("rusteal_core::UObjectRef<{cls}>"),
200                        conversion: ParamConversion::ObjectRef(cls.to_string()),
201                    })
202                } else {
203                    None
204                }
205            } else {
206                Some(DelegateParam {
207                    name: param_name,
208                    rust_type: "rusteal_core::UObjectHandle".into(),
209                    conversion: ParamConversion::Primitive("rusteal_core::UObjectHandle".into()),
210                })
211            }
212        }
213        "EnumProperty" => {
214            let en = value.get("enum_name").and_then(|v| v.as_str())?;
215
216            if !ctx.enums.contains_key(en) {
217                return None;
218            }
219
220            Some(DelegateParam {
221                name: param_name,
222                rust_type: en.to_string(),
223                conversion: ParamConversion::Enum {
224                    rust_type: en.to_string(),
225                    repr: ctx.enum_actual_repr(en).unwrap_or("u8").to_string(),
226                },
227            })
228        }
229        "StructProperty" => {
230            let sn = value.get("struct_name").and_then(|v| v.as_str())?;
231            let si = ctx.structs.get(sn)?;
232
233            if !si.has_static_struct {
234                return None;
235            }
236
237            Some(DelegateParam {
238                name: param_name,
239                rust_type: format!("rusteal_core::OwnedStruct<{}>", si.cpp_name),
240                conversion: ParamConversion::Struct {
241                    struct_name: sn.to_string(),
242                    cpp_name: si.cpp_name.clone(),
243                },
244            })
245        }
246        _ => None,
247    }
248}
249
250fn prim_param(name: String, ty: &str) -> Option<DelegateParam> {
251    Some(DelegateParam {
252        name,
253        rust_type: ty.into(),
254        conversion: ParamConversion::Primitive(ty.into()),
255    })
256}
257
258pub fn generate_delegate_impls(out: &mut String, delegates: &[DelegateInfo]) {
259    for d in delegates {
260        let rust_name = &d.rust_name;
261        let struct_name = &d.struct_name;
262        let class_name = d.class_name;
263        let prop_name = &d.prop.name;
264        let prop_name_len = prop_name.len();
265        let byte_lit = format!("b\"{prop_name}\\0\"");
266
267        out.push_str(&format!(
268            "    fn {rust_name}(&self) -> {struct_name} {{\n\
269             \x20       static PROP: std::sync::OnceLock<rusteal_core::FPropertyHandle> = std::sync::OnceLock::new();\n\
270             \x20       let prop = *PROP.get_or_init(|| unsafe {{\n\
271             \x20           rusteal_core::ffi_dispatch::reflection_find_property(\n\
272             \x20               {class_name}::static_class(), {byte_lit}.as_ptr(), {prop_name_len}\n\
273             \x20           )\n\
274             \x20       }});\n\
275             \x20       {struct_name} {{ owner: self.handle(), prop }}\n\
276             \x20   }}\n\n"
277        ));
278    }
279}
280
281pub fn generate_delegate_structs(out: &mut String, delegates: &[DelegateInfo], class_name: &str) {
282    for d in delegates {
283        let struct_name = &d.struct_name;
284        let is_multicast = d.is_multicast;
285
286        out.push_str(&format!(
287            "pub struct {struct_name} {{\n\
288             \x20   pub owner: rusteal_core::UObjectHandle,\n\
289             \x20   pub prop: rusteal_core::FPropertyHandle,\n\
290             }}\n\n"
291        ));
292
293        let sig_name_base = d
294            .prop
295            .func_info
296            .as_ref()
297            .and_then(|fi| fi.get("name"))
298            .and_then(|n| n.as_str())
299            .unwrap_or(&d.prop.name);
300
301        let sig_name = format!("{sig_name_base}__DelegateSignature");
302        let sig_name_len = sig_name.len();
303        let sig_byte_lit = format!("b\"{sig_name}\\0\"");
304
305        let callback_params: Vec<String> = d.params.iter().map(|p| p.rust_type.clone()).collect();
306        let callback_sig = callback_params.join(", ");
307
308        let method_name = if is_multicast { "add" } else { "bind" };
309
310        let api_fn = if is_multicast {
311            "bind_multicast"
312        } else {
313            "bind_unicast"
314        };
315
316        out.push_str(&format!("impl {struct_name} {{\n"));
317
318        let ufunction_method = if is_multicast {
319            "add_ufunction"
320        } else {
321            "bind_ufunction"
322        };
323
324        out.push_str(&format!(
325            "    /// Bind `target`'s UFunction named `function` (a `#[ufunction]` of a Rust\n\
326             \x20   /// class, say), as C++'s `{}` does{}.\n\
327             \x20   pub fn {ufunction_method}(&self, target: &rusteal_core::UObjectRef<impl rusteal_core::UeClass>, function: &str) -> rusteal_core::RustealResult<()> {{\n\
328             \x20       let target = target.checked()?.raw();\n\
329             \x20       rusteal_core::check_ffi(unsafe {{\n\
330             \x20           rusteal_core::ffi_dispatch::delegate_add_function(self.owner, self.prop, target, function.as_ptr(), function.len() as u32)\n\
331             \x20       }})\n\
332             \x20   }}\n\n",
333            if is_multicast { "AddDynamic" } else { "BindDynamic" },
334            if is_multicast { ": once, however many times it is called" } else { "" },
335        ));
336
337        out.push_str(&format!(
338            "    pub fn {method_name}(&self, mut callback: impl FnMut({callback_sig}) + Send + 'static) -> rusteal_core::RustealResult<rusteal_core::DelegateBinding> {{\n"
339        ));
340
341        if !d.params.is_empty() {
342            let n_params = d.params.len();
343
344            out.push_str(&format!(
345                "        static PARAM_INFO: std::sync::OnceLock<[(u32, rusteal_core::FPropertyHandle); {n_params}]> = std::sync::OnceLock::new();\n\
346                 \x20       let param_info = PARAM_INFO.get_or_init(|| unsafe {{\n\
347                 \x20           let sig_func = rusteal_core::ffi_dispatch::reflection_find_function_by_class(\n\
348                 \x20               {class_name}::static_class(),\n\
349                 \x20               {sig_byte_lit}.as_ptr(), {sig_name_len});\n\
350                 \x20           [\n"
351            ));
352
353            for p in &d.params {
354                let param_ue_name = &d
355                    .prop
356                    .func_info
357                    .as_ref()
358                    .and_then(|fi| fi.get("params"))
359                    .and_then(|ps| ps.as_array())
360                    .and_then(|arr| {
361                        arr.iter().find(|v| {
362                            v.get("name").and_then(|n| n.as_str()).map(to_snake_case)
363                                == Some(p.name.clone())
364                        })
365                    })
366                    .and_then(|v| v.get("name"))
367                    .and_then(|n| n.as_str())
368                    .unwrap_or(&p.name);
369
370                let pname_len = param_ue_name.len();
371                let pname_lit = format!("b\"{param_ue_name}\\0\"");
372
373                out.push_str(&format!(
374                    "                {{\n\
375                     \x20                   let param_prop = rusteal_core::ffi_dispatch::reflection_get_function_param(\n\
376                     \x20                       sig_func, {pname_lit}.as_ptr(), {pname_len});\n\
377                     \x20                   (rusteal_core::ffi_dispatch::reflection_get_property_offset(param_prop), param_prop)\n\
378                     \x20               }},\n"
379                ));
380            }
381
382            out.push_str(
383                "            ]\n\
384                 \x20       });\n\
385                 \x20       #[allow(unused_variables)] let param_info = param_info;\n",
386            );
387        }
388
389        if d.params.is_empty() {
390            out.push_str(&format!(
391                "        let owner = self.owner;\n\
392                 \x20       let prop = self.prop;\n\
393                 \x20       rusteal_core::delegate_registry::{api_fn}(owner, prop, move |_params: rusteal_core::ffi_dispatch::NativePtr| {{\n\
394                 \x20           callback();\n\
395                 \x20       }})\n\
396                 \x20   }}\n"
397            ));
398
399            out.push_str("}\n\n");
400            continue;
401        }
402
403        out.push_str(&format!(
404            "        let owner = self.owner;\n\
405             \x20       let prop = self.prop;\n\
406             \x20       rusteal_core::delegate_registry::{api_fn}(owner, prop, move |params: rusteal_core::ffi_dispatch::NativePtr| {{\n\
407             \x20           unsafe {{\n"
408        ));
409
410        for (i, p) in d.params.iter().enumerate() {
411            let var_name = &p.name;
412
413            match &p.conversion {
414                ParamConversion::Primitive(ty) => {
415                    out.push_str(&format!(
416                        "                let {var_name} = rusteal_core::ffi_dispatch::native_mem_read::<{ty}>(params, param_info[{i}].0 as usize);\n"
417                    ));
418                }
419                ParamConversion::ObjectRef(_cls) => {
420                    out.push_str(&format!(
421                        "                let {var_name} = rusteal_core::ObjectPointer::from_object_handle(\n\
422                         \x20                   rusteal_core::ffi_dispatch::native_mem_read::<rusteal_core::UObjectHandle>(params, param_info[{i}].0 as usize)\n\
423                         \x20               );\n"
424                    ));
425                }
426                ParamConversion::Enum { rust_type, repr } => {
427                    out.push_str(&format!(
428                        "                let __raw_{var_name} = rusteal_core::ffi_dispatch::native_mem_read::<{repr}>(params, param_info[{i}].0 as usize);\n\
429                         \x20               let {var_name} = {rust_type}::from_value(__raw_{var_name}).unwrap_or_else(|| std::mem::transmute(__raw_{var_name}));\n"
430                    ));
431                }
432                ParamConversion::FName => {
433                    out.push_str(&format!(
434                        "                let {var_name} = {{\n\
435                         \x20                   let mut __buf = [0u8; 8];\n\
436                         \x20                   let mut __written: u32 = 0;\n\
437                         \x20                   rusteal_core::ffi_infallible(rusteal_core::ffi_dispatch::delegate_read_param(\n\
438                         \x20                       param_info[{i}].1,\n\
439                         \x20                       params,\n\
440                         \x20                       param_info[{i}].0,\n\
441                         \x20                       __buf.as_mut_ptr(),\n\
442                         \x20                       8,\n\
443                         \x20                       &mut __written,\n\
444                         \x20                   ));\n\
445                         \x20                   rusteal_core::FNameHandle(u64::from_ne_bytes(__buf))\n\
446                         \x20               }};\n"
447                    ));
448                }
449                ParamConversion::String => {
450                    out.push_str(&format!(
451                        "                let {var_name} = {{\n\
452                         \x20                   let mut __buf = vec![0u8; 260];\n\
453                         \x20                   let mut __written: u32 = 0;\n\
454                         \x20                   let __err = rusteal_core::ffi_dispatch::delegate_read_param(\n\
455                         \x20                       param_info[{i}].1,\n\
456                         \x20                       params,\n\
457                         \x20                       param_info[{i}].0,\n\
458                         \x20                       __buf.as_mut_ptr(),\n\
459                         \x20                       __buf.len() as u32,\n\
460                         \x20                       &mut __written,\n\
461                         \x20                   );\n\
462                         \x20                   if __err == rusteal_core::RustealErrorCode::BufferTooSmall && __written > 0 {{\n\
463                         \x20                       __buf.resize(__written as usize, 0);\n\
464                         \x20                       rusteal_core::ffi_infallible(rusteal_core::ffi_dispatch::delegate_read_param(\n\
465                         \x20                           param_info[{i}].1,\n\
466                         \x20                           params,\n\
467                         \x20                           param_info[{i}].0,\n\
468                         \x20                           __buf.as_mut_ptr(),\n\
469                         \x20                           __buf.len() as u32,\n\
470                         \x20                           &mut __written,\n\
471                         \x20                       ));\n\
472                         \x20                   }}\n\
473                         \x20                   if __written >= 4 {{\n\
474                         \x20                       let __slen = u32::from_ne_bytes([__buf[0], __buf[1], __buf[2], __buf[3]]) as usize;\n\
475                         \x20                       String::from_utf8_lossy(&__buf[4..4 + __slen]).into_owned()\n\
476                         \x20                   }} else {{\n\
477                         \x20                       String::new()\n\
478                         \x20                   }}\n\
479                         \x20               }};\n"
480                    ));
481                }
482                ParamConversion::Struct { cpp_name, .. } => {
483                    out.push_str(&format!(
484                        "                let {var_name} = {{\n\
485                         \x20                   let __size = rusteal_core::ffi_dispatch::reflection_get_property_size(param_info[{i}].1) as usize;\n\
486                         \x20                   let mut __buf = vec![0u8; __size];\n\
487                         \x20                   let mut __written: u32 = 0;\n\
488                         \x20                   rusteal_core::ffi_infallible(rusteal_core::ffi_dispatch::delegate_read_param(\n\
489                         \x20                       param_info[{i}].1,\n\
490                         \x20                       params,\n\
491                         \x20                       param_info[{i}].0,\n\
492                         \x20                       __buf.as_mut_ptr(),\n\
493                         \x20                       __size as u32,\n\
494                         \x20                       &mut __written,\n\
495                         \x20                   ));\n\
496                         \x20                   rusteal_core::OwnedStruct::<{cpp_name}>::from_bytes(__buf)\n\
497                         \x20               }};\n"
498                    ));
499                }
500            }
501        }
502
503        let param_names: Vec<&str> = d.params.iter().map(|p| p.name.as_str()).collect();
504        let call_args = param_names.join(", ");
505
506        out.push_str(&format!(
507            "                callback({call_args});\n\
508             \x20           }}\n\
509             \x20       }})\n\
510             \x20   }}\n"
511        ));
512
513        out.push_str("}\n\n");
514    }
515}