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 #[track_caller]\n\
328 \x20 pub fn {ufunction_method}(&self, target: &rusteal_core::UObjectRef<impl rusteal_core::UeClass>, function: &str) -> rusteal_core::RustealResult<()> {{\n\
329 \x20 let target = target.checked()?.raw();\n\
330 \x20 rusteal_core::check_ffi(unsafe {{\n\
331 \x20 rusteal_core::ffi_dispatch::delegate_add_function(self.owner, self.prop, target, function.as_ptr(), function.len() as u32)\n\
332 \x20 }})\n\
333 \x20 }}\n\n",
334 if is_multicast { "AddDynamic" } else { "BindDynamic" },
335 if is_multicast { ": once, however many times it is called" } else { "" },
336 ));
337
338 out.push_str(&format!(
339 " #[track_caller]\n pub fn {method_name}(&self, mut callback: impl FnMut({callback_sig}) + Send + 'static) -> rusteal_core::RustealResult<rusteal_core::DelegateBinding> {{\n"
340 ));
341
342 if !d.params.is_empty() {
343 let n_params = d.params.len();
344
345 out.push_str(&format!(
346 " static PARAM_INFO: std::sync::OnceLock<[(u32, rusteal_core::FPropertyHandle); {n_params}]> = std::sync::OnceLock::new();\n\
347 \x20 let param_info = PARAM_INFO.get_or_init(|| unsafe {{\n\
348 \x20 let sig_func = rusteal_core::ffi_dispatch::reflection_find_function_by_class(\n\
349 \x20 {class_name}::static_class(),\n\
350 \x20 {sig_byte_lit}.as_ptr(), {sig_name_len});\n\
351 \x20 [\n"
352 ));
353
354 for p in &d.params {
355 let param_ue_name = &d
356 .prop
357 .func_info
358 .as_ref()
359 .and_then(|fi| fi.get("params"))
360 .and_then(|ps| ps.as_array())
361 .and_then(|arr| {
362 arr.iter().find(|v| {
363 v.get("name").and_then(|n| n.as_str()).map(to_snake_case)
364 == Some(p.name.clone())
365 })
366 })
367 .and_then(|v| v.get("name"))
368 .and_then(|n| n.as_str())
369 .unwrap_or(&p.name);
370
371 let pname_len = param_ue_name.len();
372 let pname_lit = format!("b\"{param_ue_name}\\0\"");
373
374 out.push_str(&format!(
375 " {{\n\
376 \x20 let param_prop = rusteal_core::ffi_dispatch::reflection_get_function_param(\n\
377 \x20 sig_func, {pname_lit}.as_ptr(), {pname_len});\n\
378 \x20 (rusteal_core::ffi_dispatch::reflection_get_property_offset(param_prop), param_prop)\n\
379 \x20 }},\n"
380 ));
381 }
382
383 out.push_str(
384 " ]\n\
385 \x20 });\n\
386 \x20 #[allow(unused_variables)] let param_info = param_info;\n",
387 );
388 }
389
390 if d.params.is_empty() {
391 out.push_str(&format!(
392 " let owner = self.owner;\n\
393 \x20 let prop = self.prop;\n\
394 \x20 rusteal_core::delegate_registry::{api_fn}(owner, prop, move |_params: rusteal_core::ffi_dispatch::NativePtr| {{\n\
395 \x20 callback();\n\
396 \x20 }})\n\
397 \x20 }}\n"
398 ));
399
400 out.push_str("}\n\n");
401 continue;
402 }
403
404 out.push_str(&format!(
405 " let owner = self.owner;\n\
406 \x20 let prop = self.prop;\n\
407 \x20 rusteal_core::delegate_registry::{api_fn}(owner, prop, move |params: rusteal_core::ffi_dispatch::NativePtr| {{\n\
408 \x20 unsafe {{\n"
409 ));
410
411 for (i, p) in d.params.iter().enumerate() {
412 let var_name = &p.name;
413
414 match &p.conversion {
415 ParamConversion::Primitive(ty) => {
416 out.push_str(&format!(
417 " let {var_name} = rusteal_core::ffi_dispatch::native_mem_read::<{ty}>(params, param_info[{i}].0 as usize);\n"
418 ));
419 }
420 ParamConversion::ObjectRef(_cls) => {
421 out.push_str(&format!(
422 " let {var_name} = rusteal_core::ObjectPointer::from_object_handle(\n\
423 \x20 rusteal_core::ffi_dispatch::native_mem_read::<rusteal_core::UObjectHandle>(params, param_info[{i}].0 as usize)\n\
424 \x20 );\n"
425 ));
426 }
427 ParamConversion::Enum { rust_type, repr } => {
428 out.push_str(&format!(
429 " let __raw_{var_name} = rusteal_core::ffi_dispatch::native_mem_read::<{repr}>(params, param_info[{i}].0 as usize);\n\
430 \x20 let {var_name} = {rust_type}::from_value(__raw_{var_name}).unwrap_or_else(|| std::mem::transmute(__raw_{var_name}));\n"
431 ));
432 }
433 ParamConversion::FName => {
434 out.push_str(&format!(
435 " let {var_name} = {{\n\
436 \x20 let mut __buf = [0u8; 8];\n\
437 \x20 let mut __written: u32 = 0;\n\
438 \x20 rusteal_core::ffi_infallible(rusteal_core::ffi_dispatch::delegate_read_param(\n\
439 \x20 param_info[{i}].1,\n\
440 \x20 params,\n\
441 \x20 param_info[{i}].0,\n\
442 \x20 __buf.as_mut_ptr(),\n\
443 \x20 8,\n\
444 \x20 &mut __written,\n\
445 \x20 ));\n\
446 \x20 rusteal_core::FNameHandle(u64::from_ne_bytes(__buf))\n\
447 \x20 }};\n"
448 ));
449 }
450 ParamConversion::String => {
451 out.push_str(&format!(
452 " let {var_name} = {{\n\
453 \x20 let mut __buf = vec![0u8; 260];\n\
454 \x20 let mut __written: u32 = 0;\n\
455 \x20 let __err = rusteal_core::ffi_dispatch::delegate_read_param(\n\
456 \x20 param_info[{i}].1,\n\
457 \x20 params,\n\
458 \x20 param_info[{i}].0,\n\
459 \x20 __buf.as_mut_ptr(),\n\
460 \x20 __buf.len() as u32,\n\
461 \x20 &mut __written,\n\
462 \x20 );\n\
463 \x20 if __err == rusteal_core::RustealErrorCode::BufferTooSmall && __written > 0 {{\n\
464 \x20 __buf.resize(__written as usize, 0);\n\
465 \x20 rusteal_core::ffi_infallible(rusteal_core::ffi_dispatch::delegate_read_param(\n\
466 \x20 param_info[{i}].1,\n\
467 \x20 params,\n\
468 \x20 param_info[{i}].0,\n\
469 \x20 __buf.as_mut_ptr(),\n\
470 \x20 __buf.len() as u32,\n\
471 \x20 &mut __written,\n\
472 \x20 ));\n\
473 \x20 }}\n\
474 \x20 if __written >= 4 {{\n\
475 \x20 let __slen = u32::from_ne_bytes([__buf[0], __buf[1], __buf[2], __buf[3]]) as usize;\n\
476 \x20 String::from_utf8_lossy(&__buf[4..4 + __slen]).into_owned()\n\
477 \x20 }} else {{\n\
478 \x20 String::new()\n\
479 \x20 }}\n\
480 \x20 }};\n"
481 ));
482 }
483 ParamConversion::Struct { cpp_name, .. } => {
484 out.push_str(&format!(
485 " let {var_name} = {{\n\
486 \x20 let __size = rusteal_core::ffi_dispatch::reflection_get_property_size(param_info[{i}].1) as usize;\n\
487 \x20 let mut __buf = vec![0u8; __size];\n\
488 \x20 let mut __written: u32 = 0;\n\
489 \x20 rusteal_core::ffi_infallible(rusteal_core::ffi_dispatch::delegate_read_param(\n\
490 \x20 param_info[{i}].1,\n\
491 \x20 params,\n\
492 \x20 param_info[{i}].0,\n\
493 \x20 __buf.as_mut_ptr(),\n\
494 \x20 __size as u32,\n\
495 \x20 &mut __written,\n\
496 \x20 ));\n\
497 \x20 rusteal_core::OwnedStruct::<{cpp_name}>::from_bytes(__buf)\n\
498 \x20 }};\n"
499 ));
500 }
501 }
502 }
503
504 let param_names: Vec<&str> = d.params.iter().map(|p| p.name.as_str()).collect();
505 let call_args = param_names.join(", ");
506
507 out.push_str(&format!(
508 " callback({call_args});\n\
509 \x20 }}\n\
510 \x20 }})\n\
511 \x20 }}\n"
512 ));
513
514 out.push_str("}\n\n");
515 }
516}