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