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 "ObjectProperty" | "ClassProperty" => {
197 let cls = value.get("class_name").and_then(|v| v.as_str());
198 if let Some(cls) = cls {
199 if ctx.classes.contains_key(cls) {
200 Some(DelegateParam {
201 name: param_name,
202 rust_type: format!("rusteal_core::UObjectRef<{cls}>"),
203 conversion: ParamConversion::ObjectRef(cls.to_string()),
204 })
205 } else {
206 None
207 }
208 } else {
209 Some(DelegateParam {
210 name: param_name,
211 rust_type: "rusteal_core::UObjectHandle".into(),
212 conversion: ParamConversion::Primitive("rusteal_core::UObjectHandle".into()),
213 })
214 }
215 }
216 "EnumProperty" => {
217 let en = value.get("enum_name").and_then(|v| v.as_str())?;
218 if !ctx.enums.contains_key(en) {
219 return None;
220 }
221 Some(DelegateParam {
222 name: param_name,
223 rust_type: en.to_string(),
224 conversion: ParamConversion::Enum {
225 rust_type: en.to_string(),
226 repr: ctx.enum_actual_repr(en).unwrap_or("u8").to_string(),
227 },
228 })
229 }
230 "StructProperty" => {
231 let sn = value.get("struct_name").and_then(|v| v.as_str())?;
232 let si = ctx.structs.get(sn)?;
233 if !si.has_static_struct {
234 return None;
235 }
236 Some(DelegateParam {
237 name: param_name,
238 rust_type: format!("rusteal_core::OwnedStruct<{}>", si.cpp_name),
239 conversion: ParamConversion::Struct {
240 struct_name: sn.to_string(),
241 cpp_name: si.cpp_name.clone(),
242 },
243 })
244 }
245 _ => None,
246 }
247}
248
249fn prim_param(name: String, ty: &str) -> Option<DelegateParam> {
250 Some(DelegateParam {
251 name,
252 rust_type: ty.into(),
253 conversion: ParamConversion::Primitive(ty.into()),
254 })
255}
256
257pub fn generate_delegate_impls(
263 out: &mut String,
264 delegates: &[DelegateInfo],
265) {
266 for d in delegates {
267 let rust_name = &d.rust_name;
268 let struct_name = &d.struct_name;
269 let class_name = d.class_name;
270 let prop_name = &d.prop.name;
271 let prop_name_len = prop_name.len();
272 let byte_lit = format!("b\"{}\\0\"", prop_name);
273
274 out.push_str(&format!(
275 " fn {rust_name}(&self) -> {struct_name} {{\n\
276 \x20 static PROP: std::sync::OnceLock<rusteal_core::FPropertyHandle> = std::sync::OnceLock::new();\n\
277 \x20 let prop = *PROP.get_or_init(|| unsafe {{\n\
278 \x20 rusteal_core::ffi_dispatch::reflection_find_property(\n\
279 \x20 {class_name}::static_class(), {byte_lit}.as_ptr(), {prop_name_len}\n\
280 \x20 )\n\
281 \x20 }});\n\
282 \x20 {struct_name} {{ owner: self.handle(), prop }}\n\
283 \x20 }}\n\n"
284 ));
285 }
286}
287
288pub fn generate_delegate_structs(
291 out: &mut String,
292 delegates: &[DelegateInfo],
293 class_name: &str,
294) {
295 for d in delegates {
296 let struct_name = &d.struct_name;
297 let is_multicast = d.is_multicast;
298
299 out.push_str(&format!(
300 "pub struct {struct_name} {{\n\
301 \x20 pub owner: rusteal_core::UObjectHandle,\n\
302 \x20 pub prop: rusteal_core::FPropertyHandle,\n\
303 }}\n\n"
304 ));
305
306 let sig_name_base = d.prop.func_info.as_ref()
310 .and_then(|fi| fi.get("name"))
311 .and_then(|n| n.as_str())
312 .unwrap_or(&d.prop.name);
313 let sig_name = format!("{sig_name_base}__DelegateSignature");
314 let sig_name_len = sig_name.len();
315 let sig_byte_lit = format!("b\"{}\\0\"", sig_name);
316
317 let callback_params: Vec<String> = d.params.iter().map(|p| p.rust_type.clone()).collect();
319 let callback_sig = callback_params.join(", ");
320
321 let method_name = if is_multicast { "add" } else { "bind" };
322 let api_fn = if is_multicast { "bind_multicast" } else { "bind_unicast" };
323
324 out.push_str(&format!(
325 "impl {struct_name} {{\n"
326 ));
327
328 out.push_str(&format!(
330 " pub fn {method_name}(&self, mut callback: impl FnMut({callback_sig}) + Send + 'static) -> rusteal_core::RustealResult<rusteal_core::DelegateBinding> {{\n"
331 ));
332
333 if !d.params.is_empty() {
335 let n_params = d.params.len();
336 out.push_str(&format!(
337 " static PARAM_INFO: std::sync::OnceLock<[(u32, rusteal_core::FPropertyHandle); {n_params}]> = std::sync::OnceLock::new();\n\
338 \x20 let param_info = PARAM_INFO.get_or_init(|| unsafe {{\n\
339 \x20 let sig_func = rusteal_core::ffi_dispatch::reflection_find_function_by_class(\n\
340 \x20 {class_name}::static_class(),\n\
341 \x20 {sig_byte_lit}.as_ptr(), {sig_name_len});\n\
342 \x20 [\n"
343 ));
344
345 for p in &d.params {
346 let param_ue_name = &d.prop.func_info.as_ref()
347 .and_then(|fi| fi.get("params"))
348 .and_then(|ps| ps.as_array())
349 .and_then(|arr| arr.iter().find(|v| {
350 v.get("name").and_then(|n| n.as_str()).map(to_snake_case) == Some(p.name.clone())
351 }))
352 .and_then(|v| v.get("name"))
353 .and_then(|n| n.as_str())
354 .unwrap_or(&p.name);
355 let pname_len = param_ue_name.len();
356 let pname_lit = format!("b\"{}\\0\"", param_ue_name);
357 out.push_str(&format!(
358 " {{\n\
359 \x20 let param_prop = rusteal_core::ffi_dispatch::reflection_get_function_param(\n\
360 \x20 sig_func, {pname_lit}.as_ptr(), {pname_len});\n\
361 \x20 (rusteal_core::ffi_dispatch::reflection_get_property_offset(param_prop), param_prop)\n\
362 \x20 }},\n"
363 ));
364 }
365
366 out.push_str(
367 " ]\n\
368 \x20 });\n\
369 \x20 #[allow(unused_variables)] let param_info = param_info;\n"
370 );
371 }
372
373 if d.params.is_empty() {
375 out.push_str(&format!(
376 " let owner = self.owner;\n\
377 \x20 let prop = self.prop;\n\
378 \x20 rusteal_core::delegate_registry::{api_fn}(owner, prop, move |_params: rusteal_core::ffi_dispatch::NativePtr| {{\n\
379 \x20 callback();\n\
380 \x20 }})\n\
381 \x20 }}\n"
382 ));
383 out.push_str("}\n\n");
384 continue;
385 }
386
387 out.push_str(&format!(
388 " let owner = self.owner;\n\
389 \x20 let prop = self.prop;\n\
390 \x20 rusteal_core::delegate_registry::{api_fn}(owner, prop, move |params: rusteal_core::ffi_dispatch::NativePtr| {{\n\
391 \x20 unsafe {{\n"
392 ));
393
394 for (i, p) in d.params.iter().enumerate() {
396 let var_name = &p.name;
397 match &p.conversion {
398 ParamConversion::Primitive(ty) => {
399 out.push_str(&format!(
400 " let {var_name} = rusteal_core::ffi_dispatch::native_mem_read::<{ty}>(params, param_info[{i}].0 as usize);\n"
401 ));
402 }
403 ParamConversion::ObjectRef(_cls) => {
404 out.push_str(&format!(
405 " let {var_name} = rusteal_core::UObjectRef::from_raw(\n\
406 \x20 rusteal_core::ffi_dispatch::native_mem_read::<rusteal_core::UObjectHandle>(params, param_info[{i}].0 as usize)\n\
407 \x20 );\n"
408 ));
409 }
410 ParamConversion::Enum { rust_type, repr } => {
411 out.push_str(&format!(
412 " let __raw_{var_name} = rusteal_core::ffi_dispatch::native_mem_read::<{repr}>(params, param_info[{i}].0 as usize);\n\
413 \x20 let {var_name} = {rust_type}::from_value(__raw_{var_name}).unwrap_or_else(|| std::mem::transmute(__raw_{var_name}));\n"
414 ));
415 }
416 ParamConversion::FName => {
417 out.push_str(&format!(
418 " let {var_name} = {{\n\
419 \x20 let mut __buf = [0u8; 8];\n\
420 \x20 let mut __written: u32 = 0;\n\
421 \x20 rusteal_core::ffi_infallible(rusteal_core::ffi_dispatch::delegate_read_param(\n\
422 \x20 param_info[{i}].1,\n\
423 \x20 params,\n\
424 \x20 param_info[{i}].0,\n\
425 \x20 __buf.as_mut_ptr(),\n\
426 \x20 8,\n\
427 \x20 &mut __written,\n\
428 \x20 ));\n\
429 \x20 rusteal_core::FNameHandle(u64::from_ne_bytes(__buf))\n\
430 \x20 }};\n"
431 ));
432 }
433 ParamConversion::String => {
434 out.push_str(&format!(
435 " let {var_name} = {{\n\
436 \x20 let mut __buf = vec![0u8; 260];\n\
437 \x20 let mut __written: u32 = 0;\n\
438 \x20 let __err = 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 __buf.len() as u32,\n\
444 \x20 &mut __written,\n\
445 \x20 );\n\
446 \x20 if __err == rusteal_core::RustealErrorCode::BufferTooSmall && __written > 0 {{\n\
447 \x20 __buf.resize(__written as usize, 0);\n\
448 \x20 rusteal_core::ffi_infallible(rusteal_core::ffi_dispatch::delegate_read_param(\n\
449 \x20 param_info[{i}].1,\n\
450 \x20 params,\n\
451 \x20 param_info[{i}].0,\n\
452 \x20 __buf.as_mut_ptr(),\n\
453 \x20 __buf.len() as u32,\n\
454 \x20 &mut __written,\n\
455 \x20 ));\n\
456 \x20 }}\n\
457 \x20 if __written >= 4 {{\n\
458 \x20 let __slen = u32::from_ne_bytes([__buf[0], __buf[1], __buf[2], __buf[3]]) as usize;\n\
459 \x20 String::from_utf8_lossy(&__buf[4..4 + __slen]).into_owned()\n\
460 \x20 }} else {{\n\
461 \x20 String::new()\n\
462 \x20 }}\n\
463 \x20 }};\n"
464 ));
465 }
466 ParamConversion::Struct { cpp_name, .. } => {
467 out.push_str(&format!(
468 " let {var_name} = {{\n\
469 \x20 let __size = rusteal_core::ffi_dispatch::reflection_get_property_size(param_info[{i}].1) as usize;\n\
470 \x20 let mut __buf = vec![0u8; __size];\n\
471 \x20 let mut __written: u32 = 0;\n\
472 \x20 rusteal_core::ffi_infallible(rusteal_core::ffi_dispatch::delegate_read_param(\n\
473 \x20 param_info[{i}].1,\n\
474 \x20 params,\n\
475 \x20 param_info[{i}].0,\n\
476 \x20 __buf.as_mut_ptr(),\n\
477 \x20 __size as u32,\n\
478 \x20 &mut __written,\n\
479 \x20 ));\n\
480 \x20 rusteal_core::OwnedStruct::<{cpp_name}>::from_bytes(__buf)\n\
481 \x20 }};\n"
482 ));
483 }
484 }
485 }
486
487 let param_names: Vec<&str> = d.params.iter().map(|p| p.name.as_str()).collect();
489 let call_args = param_names.join(", ");
490 out.push_str(&format!(
491 " callback({call_args});\n\
492 \x20 }}\n\
493 \x20 }})\n\
494 \x20 }}\n"
495 ));
496
497 out.push_str("}\n\n");
498 }
499}