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