1use std::collections::HashSet;
4
5use crate::context::{CodegenContext, FuncEntry};
6use crate::defaults;
7use crate::naming::{escape_reserved, to_snake_case};
8use crate::schema::*;
9use crate::type_map::{self, ConversionKind, MappedType, ParamDirection};
10
11use super::delegates;
12use super::param_helpers;
13use super::properties::{self, PropertyContext};
14
15pub fn generate_class(class: &ClassInfo, ctx: &CodegenContext) -> String {
17 let mut out = String::with_capacity(8192);
18
19 out.push_str("use super::*;\n");
21 out.push_str("use rusteal_core::{UeClass, UeStruct, UeEnum, UeHandle, ValidHandle, Pinned, Checked};\n");
22 let current_module = ctx.package_to_module.get(&class.package).map(|s| s.as_str()).unwrap_or("");
23 let mut modules: Vec<&String> = ctx.enabled_modules.iter().collect();
26 modules.sort();
27 for module in modules {
28 if module != current_module {
29 if let Some(feature) = ctx.feature_for_module(module) {
30 out.push_str(&format!("#[cfg(feature = \"{feature}\")]\n"));
31 }
32 out.push_str(&format!("use crate::{module}::*;\n"));
33 }
34 }
35 out.push('\n');
36
37 let name = &class.name; let cpp_name = &class.cpp_name; out.push_str(&format!(
43 "/// UE class `{cpp_name}`.\n\
44 pub struct {name};\n\n"
45 ));
46
47 let name_bytes_len = name.len();
49 let byte_lit = format!("b\"{}\\0\"", name);
50 out.push_str(&format!(
51 "impl rusteal_core::UeClass for {name} {{\n\
52 \x20 fn static_class() -> rusteal_core::UClassHandle {{\n\
53 \x20 static CACHE: std::sync::OnceLock<rusteal_core::UClassHandle> = std::sync::OnceLock::new();\n\
54 \x20 *CACHE.get_or_init(|| unsafe {{\n\
55 \x20 rusteal_core::ffi_dispatch::reflection_get_static_class({byte_lit}.as_ptr(), {name_bytes_len})\n\
56 \x20 }})\n\
57 \x20 }}\n\
58 }}\n\n"
59 ));
60
61 if let Some(parent) = &class.super_class
64 && ctx.classes.contains_key(parent.as_str())
65 {
66 let parent_class = ctx.classes.get(parent.as_str()).unwrap();
68 let parent_module = ctx.package_to_module.get(&parent_class.package)
69 .map(|s| s.as_str()).unwrap_or("");
70 if parent_module != current_module
71 && let Some(feature) = ctx.feature_for_module(parent_module) {
72 out.push_str(&format!("#[cfg(feature = \"{feature}\")]\n"));
73 }
74 out.push_str(&format!(
75 "impl rusteal_core::HasParent for {name} {{\n\
76 \x20 type Parent = {parent};\n\
77 }}\n\n"
78 ));
79 }
80
81 let mut ancestor = Some(name.as_str());
84 while let Some(class_name) = ancestor {
85 let Some(ancestor_class) = ctx.classes.get(class_name) else {
86 break;
87 };
88 let ancestor_module = ctx
89 .package_to_module
90 .get(&ancestor_class.package)
91 .map(|s| s.as_str())
92 .unwrap_or("");
93 if ancestor_module != current_module
94 && let Some(feature) = ctx.feature_for_module(ancestor_module)
95 {
96 out.push_str(&format!("#[cfg(feature = \"{feature}\")]\n"));
97 }
98 out.push_str(&format!("impl rusteal_core::Inherits<{class_name}> for {name} {{}}\n"));
99 ancestor = ancestor_class.super_class.as_deref();
100 }
101 out.push('\n');
102
103 let mut seen_func_names: HashSet<String> = HashSet::new();
105 let mut class_funcs: Vec<&FuncEntry> = Vec::new();
106 for entry in ctx.func_table.iter().filter(|e| e.class_name == *name) {
107 if seen_func_names.insert(entry.rust_func_name.clone()) {
108 class_funcs.push(entry);
109 }
110 }
111
112 let (mut prop_names, deduped_props) = properties::collect_deduped_properties(&class.props, Some(ctx));
114
115 let own_delegate_infos = delegates::collect_delegate_props(&class.props, name, ctx);
117
118 if deduped_props.is_empty() && class_funcs.is_empty()
120 && own_delegate_infos.is_empty()
121 {
122 return out;
123 }
124
125 let func_names: HashSet<String> = class_funcs
128 .iter()
129 .map(|e| escape_reserved(&e.rust_func_name))
130 .collect();
131
132 let mut suppress_setters: HashSet<String> = prop_names
133 .iter()
134 .filter(|n| n.starts_with("set_") && func_names.contains(n.as_str()))
135 .cloned()
136 .collect();
137
138 suppress_setters.extend(
141 deduped_props
142 .iter()
143 .filter(|p| properties::is_read_only_non_public(p))
144 .map(|p| format!("set_{}", properties::rust_property_name(p))),
145 );
146
147 for setter in &suppress_setters {
149 prop_names.remove(setter);
150 }
151
152 let class_funcs: Vec<&FuncEntry> = class_funcs
154 .into_iter()
155 .filter(|e| !prop_names.contains(&escape_reserved(&e.rust_func_name)))
156 .collect();
157
158 let pctx = PropertyContext {
160 find_prop_fn: "find_property".to_string(),
161 handle_expr: format!("{name}::static_class()"),
162 pre_access: "let h = self.handle();".to_string(),
163 container_expr: "h".to_string(),
164 is_class: true,
165 };
166
167 delegates::generate_delegate_structs(&mut out, &own_delegate_infos, name);
169
170 let trait_name = format!("{name}Ext");
173 out.push_str(&format!(
174 "pub trait {trait_name}: rusteal_core::ValidHandle {{\n"
175 ));
176
177 for prop in &deduped_props {
179 properties::generate_property(&mut out, prop, &pctx, ctx, &suppress_setters);
180 }
181
182 delegates::generate_delegate_impls(&mut out, &own_delegate_infos);
184
185 let (static_funcs, instance_funcs): (Vec<&FuncEntry>, Vec<&FuncEntry>) =
188 class_funcs.iter().partition(|e| is_static_fn(e));
189 for entry in &instance_funcs {
190 generate_function(&mut out, entry, &entry.class_name, ctx);
191 }
192
193 out.push_str("}\n\n");
194
195 if !static_funcs.is_empty() {
198 out.push_str(&format!("impl {name} {{\n"));
199 for entry in &static_funcs {
200 generate_function(&mut out, entry, &entry.class_name, ctx);
201 }
202 out.push_str("}\n\n");
203 }
204
205 out.push_str(&format!(
207 "impl {trait_name} for rusteal_core::Checked<{name}> {{}}\n"
208 ));
209 out.push_str(&format!(
210 "impl {trait_name} for rusteal_core::Pinned<{name}> {{}}\n"
211 ));
212
213 out
214}
215
216fn is_static_fn(entry: &FuncEntry) -> bool {
217 entry.func.is_static || (entry.func.func_flags & FUNC_STATIC != 0)
218}
219
220pub(super) use type_map::is_container_param;
225pub(super) use type_map::container_param_input_type;
226pub(super) use type_map::container_param_output_type;
227pub(super) use type_map::container_elem_type_str;
228
229fn build_return_type(output_types: &[String]) -> String {
231 match output_types.len() {
232 0 => "()".to_string(),
233 1 => output_types[0].clone(),
234 _ => format!("({})", output_types.join(", ")),
235 }
236}
237
238fn scalar_out_rust_type_ctx(mapped: &MappedType, struct_name: Option<&str>, ctx: &CodegenContext) -> String {
242 match mapped.ffi_to_rust {
243 ConversionKind::StructOpaque => {
244 if let Some(sn) = struct_name
245 && let Some(si) = ctx.structs.get(sn)
246 && si.has_static_struct
247 {
248 return format!("rusteal_core::OwnedStruct<{}>", si.cpp_name);
249 }
250 mapped.rust_type.clone()
252 }
253 _ => mapped.rust_type.clone(),
254 }
255}
256
257pub(super) fn is_struct_owned(struct_name: Option<&str>, ctx: &CodegenContext) -> bool {
259 struct_name.is_some_and(|sn| ctx.structs.get(sn).is_some_and(|si| si.has_static_struct))
260}
261
262pub(super) fn is_scalar_output_returnable(dir: ParamDirection, mapped: &MappedType) -> bool {
266 if dir == ParamDirection::InOut && mapped.ffi_to_rust == ConversionKind::StructOpaque {
267 return false;
268 }
269 dir == ParamDirection::Out || dir == ParamDirection::InOut
270}
271
272fn generate_function(out: &mut String, entry: &FuncEntry, class_name: &str, ctx: &CodegenContext) {
278 let has_container = entry.func.params.iter().any(is_container_param);
279 if has_container {
280 generate_container_function(out, entry, class_name, ctx);
281 } else {
282 generate_scalar_function(out, entry, class_name, ctx);
283 }
284}
285
286fn generate_scalar_function(out: &mut String, entry: &FuncEntry, class_name: &str, ctx: &CodegenContext) {
291 let func = &entry.func;
292 let rust_fn_name = escape_reserved(&entry.rust_func_name);
293 let func_id = entry.func_id;
294
295 let mut return_param: Option<&ParamInfo> = None;
297
298 for param in &func.params {
299 let dir = type_map::param_direction(param);
300 if dir == ParamDirection::Return {
301 return_param = Some(param);
302 }
303 }
304
305 let mut all_mapped: Vec<(&ParamInfo, ParamDirection, MappedType)> = Vec::new();
307 let mut all_supported = true;
308
309 for param in &func.params {
310 let dir = type_map::param_direction(param);
311 let mapped = type_map::map_property_type(
312 ¶m.prop_type,
313 param.class_name.as_deref(),
314 param.struct_name.as_deref(),
315 param.enum_name.as_deref(),
316 param.enum_underlying_type.as_deref(),
317 param.meta_class_name.as_deref(),
318 param.interface_name.as_deref(),
319 );
320 if !mapped.supported {
321 all_supported = false;
322 break;
323 }
324 all_mapped.push((param, dir, mapped));
325 }
326
327 if !all_supported {
328 out.push_str(&format!(
329 " // Skipped: {}.{} (unsupported param type)\n\n",
330 class_name, func.name
331 ));
332 return;
333 }
334
335 let ret_mapped = return_param.map(|rp| {
337 type_map::map_property_type(
338 &rp.prop_type,
339 rp.class_name.as_deref(),
340 rp.struct_name.as_deref(),
341 rp.enum_name.as_deref(),
342 rp.enum_underlying_type.as_deref(),
343 rp.meta_class_name.as_deref(),
344 rp.interface_name.as_deref(),
345 )
346 });
347
348 let return_rust_type = {
350 let mut output_types = Vec::new();
351 if let Some(m) = &ret_mapped {
352 let rp_struct = return_param.and_then(|rp| rp.struct_name.as_deref());
353 output_types.push(scalar_out_rust_type_ctx(m, rp_struct, ctx));
354 }
355 for (param, dir, mapped) in &all_mapped {
356 if is_scalar_output_returnable(*dir, mapped) {
357 output_types.push(scalar_out_rust_type_ctx(mapped, param.struct_name.as_deref(), ctx));
358 }
359 }
360 build_return_type(&output_types)
361 };
362
363 let is_static = func.is_static || (func.func_flags & FUNC_STATIC != 0);
365
366 let mut sig = String::new();
368 if is_static {
369 sig.push_str(&format!(
370 " pub fn {rust_fn_name}("
371 ));
372 } else {
373 sig.push_str(&format!(
374 " fn {rust_fn_name}(&self, "
375 ));
376 }
377
378 let mut param_names = Vec::new();
380 let mut default_unwraps: Vec<(String, String)> = Vec::new(); for (param, dir, mapped) in &all_mapped {
382 if *dir == ParamDirection::Return {
383 continue;
384 }
385 let pname = escape_reserved(&to_snake_case(¶m.name));
386 let has_default = *dir == ParamDirection::In
387 && defaults::parse_default_literal(param, mapped, ctx).is_some();
388 if has_default {
389 let default_expr = defaults::parse_default_literal(param, mapped, ctx)
390 .expect("default literal must be parseable (has_default was true)");
391 default_unwraps.push((pname.clone(), default_expr));
392 }
393 match dir {
394 ParamDirection::In | ParamDirection::InOut => {
395 match mapped.rust_to_ffi {
396 ConversionKind::StringUtf8 => {
397 if has_default {
398 sig.push_str(&format!("{pname}: Option<&str>, "));
399 } else {
400 sig.push_str(&format!("{pname}: &str, "));
401 }
402 }
403 ConversionKind::StructOpaque if *dir == ParamDirection::In
404 && is_struct_owned(param.struct_name.as_deref(), ctx) =>
405 {
406 let si = ctx.structs.get(param.struct_name.as_deref().expect("StructOpaque param must have struct_name"))
407 .expect("struct must exist in context");
408 sig.push_str(&format!(
409 "{pname}: &rusteal_core::OwnedStruct<{}>, ", si.cpp_name
410 ));
411 }
412 ConversionKind::StructOpaque if *dir == ParamDirection::InOut => {
413 sig.push_str(&format!("{pname}: *mut u8, "));
414 }
415 _ => {
416 if has_default {
417 sig.push_str(&format!("{pname}: Option<{}>, ", mapped.rust_type));
418 } else {
419 sig.push_str(&format!("{pname}: {}, ", mapped.rust_type));
420 }
421 }
422 }
423 }
424 ParamDirection::Out => {
425 }
427 ParamDirection::Return => {}
428 }
429 param_names.push((pname, param, *dir, mapped));
430 }
431
432 if sig.ends_with(", ") {
434 sig.truncate(sig.len() - 2);
435 }
436
437 if return_rust_type == "()" {
438 sig.push(')');
439 } else {
440 sig.push_str(&format!(") -> {return_rust_type}"));
441 }
442
443 out.push_str(&sig);
444 out.push_str(" {\n");
445
446 for (pname, default_expr) in &default_unwraps {
448 out.push_str(&format!(" let {pname} = {pname}.unwrap_or({default_expr});\n"));
449 }
450
451 out.push_str(" {\n");
453
454 let mut ffi_params = String::new();
456 if !is_static {
457 ffi_params.push_str("rusteal_core::UObjectHandle, ");
458 }
459 for (_param, dir, mapped) in &all_mapped {
460 match dir {
461 ParamDirection::In | ParamDirection::InOut => {
462 match mapped.rust_to_ffi {
463 ConversionKind::StringUtf8 => {
464 ffi_params.push_str("*const u8, u32, ");
465 if *dir == ParamDirection::InOut {
467 ffi_params.push_str("*mut u8, u32, *mut u32, ");
468 }
469 }
470 ConversionKind::ObjectRef => {
471 ffi_params.push_str("rusteal_core::UObjectHandle, ");
472 }
473 ConversionKind::EnumCast => {
474 ffi_params.push_str(&format!("{}, ", mapped.rust_ffi_type));
475 }
476 ConversionKind::StructOpaque => {
477 if *dir == ParamDirection::InOut {
478 ffi_params.push_str("*mut u8, "); } else {
480 ffi_params.push_str("*const u8, ");
481 }
482 }
483 _ => {
484 ffi_params.push_str(&format!("{}, ", mapped.rust_ffi_type));
485 }
486 }
487 }
488 ParamDirection::Out | ParamDirection::Return => {
489 match mapped.ffi_to_rust {
490 ConversionKind::StringUtf8 => {
491 ffi_params.push_str("*mut u8, u32, *mut u32, ");
492 }
493 ConversionKind::ObjectRef => {
494 ffi_params.push_str("*mut rusteal_core::UObjectHandle, ");
495 }
496 ConversionKind::StructOpaque => {
497 ffi_params.push_str("*mut u8, ");
498 }
499 ConversionKind::EnumCast => {
500 ffi_params.push_str(&format!("*mut {}, ", mapped.rust_ffi_type));
501 }
502 _ => {
503 ffi_params.push_str(&format!("*mut {}, ", mapped.rust_ffi_type));
504 }
505 }
506 }
507 }
508 }
509 if ffi_params.ends_with(", ") {
511 ffi_params.truncate(ffi_params.len() - 2);
512 }
513
514 out.push_str(&format!(
515 " const FN_ID: u32 = {func_id};\n\
516 \x20 type Fn = unsafe extern \"C\" fn({ffi_params}) -> rusteal_core::RustealErrorCode;\n\
517 \x20 let __rusteal_fn: Fn = unsafe {{ std::mem::transmute(*(rusteal_core::api().func_table.add(FN_ID as usize))) }};\n"
518 ));
519
520 if !is_static {
522 out.push_str(" let h = self.handle();\n");
523 }
524
525 if let Some(_rp) = return_param {
527 let rm = ret_mapped.as_ref().expect("return param must have mapped type");
528 match rm.ffi_to_rust {
529 ConversionKind::ObjectRef => {
530 out.push_str(" let mut _ret = rusteal_core::UObjectHandle::null();\n");
531 }
532 ConversionKind::StringUtf8 => {
533 out.push_str(" let mut _ret_buf = vec![0u8; 512];\n");
534 out.push_str(" let mut _ret_len: u32 = 0;\n");
535 }
536 ConversionKind::EnumCast => {
537 out.push_str(&format!(" let mut _ret: {} = 0;\n", rm.rust_ffi_type));
538 }
539 ConversionKind::StructOpaque => {
540 out.push_str(" let mut _ret_struct_buf = vec![0u8; 256];\n");
541 }
542 _ => {
543 let default = properties::default_value_for(&rm.rust_ffi_type);
544 out.push_str(&format!(" let mut _ret = {default};\n"));
545 }
546 }
547 }
548
549 for (param, dir, mapped) in &all_mapped {
550 if *dir == ParamDirection::Out {
551 param_helpers::emit_out_param_var_decl(out, param, mapped);
552 }
553 if *dir == ParamDirection::InOut {
554 param_helpers::emit_inout_string_buf_decl(out, param, mapped);
555 }
556 }
557
558 out.push_str(" rusteal_core::ffi_infallible(unsafe { __rusteal_fn(");
560 if !is_static {
561 out.push_str("h, ");
562 }
563 for (param, dir, mapped) in &all_mapped {
564 let pname = escape_reserved(&to_snake_case(¶m.name));
565 match dir {
566 ParamDirection::In | ParamDirection::InOut => {
567 match mapped.rust_to_ffi {
568 ConversionKind::StringUtf8 => {
569 out.push_str(&format!("{pname}.as_ptr(), {pname}.len() as u32, "));
570 if *dir == ParamDirection::InOut {
572 out.push_str(&format!("{pname}_buf.as_mut_ptr(), {pname}_buf.len() as u32, &mut {pname}_len, "));
573 }
574 }
575 ConversionKind::ObjectRef => {
576 out.push_str(&format!("rusteal_core::ObjectPointer::object_handle(&{pname}), "));
577 }
578 ConversionKind::EnumCast => {
579 out.push_str(&format!("{pname} as {}, ", mapped.rust_ffi_type));
580 }
581 ConversionKind::StructOpaque if *dir == ParamDirection::In
582 && is_struct_owned(param.struct_name.as_deref(), ctx) =>
583 {
584 out.push_str(&format!("{pname}.as_bytes().as_ptr(), "));
585 }
586 _ => {
587 out.push_str(&format!("{pname}, "));
588 }
589 }
590 }
591 ParamDirection::Out => {
592 match mapped.ffi_to_rust {
593 ConversionKind::StructOpaque => {
594 out.push_str(&format!("{pname}_buf.as_mut_ptr(), "));
595 }
596 ConversionKind::StringUtf8 => {
597 out.push_str(&format!("{pname}_buf.as_mut_ptr(), {pname}_buf.len() as u32, &mut {pname}_len, "));
598 }
599 _ => {
600 out.push_str(&format!("&mut {pname}, "));
601 }
602 }
603 }
604 ParamDirection::Return => {
605 let rm = ret_mapped.as_ref().expect("return param must have mapped type");
606 match rm.ffi_to_rust {
607 ConversionKind::StringUtf8 => {
608 out.push_str("_ret_buf.as_mut_ptr(), _ret_buf.len() as u32, &mut _ret_len, ");
609 }
610 ConversionKind::ObjectRef => {
611 out.push_str("&mut _ret, ");
612 }
613 ConversionKind::StructOpaque => {
614 out.push_str("_ret_struct_buf.as_mut_ptr(), ");
615 }
616 _ => {
617 out.push_str("&mut _ret, ");
618 }
619 }
620 }
621 }
622 }
623 let out_len = out.len();
625 if out.ends_with(", ") {
626 out.truncate(out_len - 2);
627 }
628 out.push_str(") });\n");
629
630 {
632 let mut return_parts = Vec::new();
633
634 if let Some(rp) = return_param {
636 let rm = ret_mapped.as_ref().expect("return param must have mapped type");
637 match rm.ffi_to_rust {
638 ConversionKind::ObjectRef => {
639 return_parts.push("unsafe { rusteal_core::ObjectPointer::from_object_handle(_ret) }".to_string());
640 }
641 ConversionKind::StringUtf8 => {
642 out.push_str(" _ret_buf.truncate(_ret_len as usize);\n");
643 out.push_str(" let _ret_str = String::from_utf8_lossy(&_ret_buf).into_owned();\n");
644 return_parts.push("_ret_str".to_string());
645 }
646 ConversionKind::EnumCast => {
647 let rt = &rm.rust_type;
648 let actual_repr = rp.enum_name.as_deref()
649 .and_then(|en| ctx.enum_actual_repr(en))
650 .unwrap_or(&rm.rust_ffi_type);
651 out.push_str(&format!(" let _ret_enum = {rt}::from_value(_ret as {actual_repr}).expect(\"unknown enum value\");\n"));
652 return_parts.push("_ret_enum".to_string());
653 }
654 ConversionKind::StructOpaque => {
655 if is_struct_owned(rp.struct_name.as_deref(), ctx) {
656 out.push_str(" let _ret_owned = rusteal_core::OwnedStruct::from_bytes(_ret_struct_buf);\n");
657 return_parts.push("_ret_owned".to_string());
658 } else {
659 out.push_str(" let _ret_ptr = _ret_struct_buf.as_ptr();\n");
660 out.push_str(" std::mem::forget(_ret_struct_buf);\n");
661 return_parts.push("_ret_ptr".to_string());
662 }
663 }
664 _ => {
665 return_parts.push("_ret".to_string());
666 }
667 }
668 }
669
670 for (param, dir, mapped) in &all_mapped {
672 if !is_scalar_output_returnable(*dir, mapped) {
673 continue;
674 }
675 return_parts.push(param_helpers::emit_out_param_conversion(
676 out, param, mapped, ctx,
677 ));
678 }
679
680 param_helpers::emit_return_expr(out, &return_parts);
681 }
682
683 out.push_str(" }\n");
684 out.push_str(" }\n\n");
685}
686
687struct ContainerParamMeta<'a> {
693 param: &'a ParamInfo,
694 dir: ParamDirection,
695 index: usize,
697}
698
699fn generate_container_function(out: &mut String, entry: &FuncEntry, class_name: &str, ctx: &CodegenContext) {
702 let func = &entry.func;
703 let rust_fn_name = escape_reserved(&entry.rust_func_name);
704 let func_id = entry.func_id;
705 let is_static = func.is_static || (func.func_flags & FUNC_STATIC != 0);
706 let ue_name = if func.ue_name.is_empty() { &entry.func_name } else { &func.ue_name };
707
708 let mut container_params: Vec<ContainerParamMeta> = Vec::new();
710 for param in &func.params {
711 if is_container_param(param) {
712 let dir = type_map::param_direction(param);
713 let index = container_params.len();
714 container_params.push(ContainerParamMeta { param, dir, index });
715 }
716 }
717 let n_containers = container_params.len();
718
719 let mut return_param: Option<&ParamInfo> = None;
721 let mut all_supported = true;
722
723 for param in &func.params {
724 let dir = type_map::param_direction(param);
725 if dir == ParamDirection::Return {
726 return_param = Some(param);
727 }
728 if is_container_param(param) {
729 if (dir == ParamDirection::In || dir == ParamDirection::InOut)
730 && container_param_input_type(param, ctx).is_none()
731 {
732 all_supported = false;
733 break;
734 }
735 if (dir == ParamDirection::Out || dir == ParamDirection::Return || dir == ParamDirection::InOut)
736 && container_param_output_type(param, ctx).is_none()
737 {
738 all_supported = false;
739 break;
740 }
741 } else {
742 let mapped = type_map::map_property_type(
743 ¶m.prop_type, param.class_name.as_deref(),
744 param.struct_name.as_deref(), param.enum_name.as_deref(),
745 param.enum_underlying_type.as_deref(),
746 param.meta_class_name.as_deref(),
747 param.interface_name.as_deref(),
748 );
749 if !mapped.supported {
750 all_supported = false;
751 break;
752 }
753 }
754 }
755
756 if !all_supported {
757 out.push_str(&format!(
758 " // Skipped: {}.{} (unsupported container inner type)\n\n",
759 class_name, func.name
760 ));
761 return;
762 }
763
764 let mut output_types = Vec::new();
766 let mut scalar_return_mapped: Option<MappedType> = None;
767
768 if let Some(rp) = return_param {
769 if is_container_param(rp) {
770 output_types.push(container_param_output_type(rp, ctx)
771 .expect("container return type should be resolvable"));
772 } else {
773 let rm = type_map::map_property_type(
774 &rp.prop_type, rp.class_name.as_deref(),
775 rp.struct_name.as_deref(), rp.enum_name.as_deref(),
776 rp.enum_underlying_type.as_deref(),
777 rp.meta_class_name.as_deref(),
778 rp.interface_name.as_deref(),
779 );
780 output_types.push(scalar_out_rust_type_ctx(&rm, rp.struct_name.as_deref(), ctx));
781 scalar_return_mapped = Some(rm);
782 }
783 }
784 for param in &func.params {
785 let dir = type_map::param_direction(param);
786 if dir == ParamDirection::Out || dir == ParamDirection::InOut {
787 if is_container_param(param) {
788 output_types.push(container_param_output_type(param, ctx)
789 .expect("container out-param type should be resolvable"));
790 } else {
791 let rm = type_map::map_property_type(
792 ¶m.prop_type, param.class_name.as_deref(),
793 param.struct_name.as_deref(), param.enum_name.as_deref(),
794 param.enum_underlying_type.as_deref(),
795 param.meta_class_name.as_deref(),
796 param.interface_name.as_deref(),
797 );
798 if is_scalar_output_returnable(dir, &rm) {
799 output_types.push(scalar_out_rust_type_ctx(&rm, param.struct_name.as_deref(), ctx));
800 }
801 }
802 }
803 }
804 let return_rust_type = build_return_type(&output_types);
805
806 let mut sig = String::new();
808 if is_static {
809 sig.push_str(&format!(" pub fn {rust_fn_name}("));
810 } else {
811 sig.push_str(&format!(" fn {rust_fn_name}(&self, "));
812 }
813
814 let mut default_unwraps: Vec<(String, String)> = Vec::new();
815 for param in &func.params {
816 let dir = type_map::param_direction(param);
817 if dir == ParamDirection::Return || dir == ParamDirection::Out {
818 continue;
819 }
820 let pname = escape_reserved(&to_snake_case(¶m.name));
821 if is_container_param(param) {
822 let input_type = container_param_input_type(param, ctx)
823 .expect("container input type should be resolvable");
824 sig.push_str(&format!("{pname}: {input_type}, "));
825 } else {
826 let mapped = map_param(param);
827 let has_default = dir == ParamDirection::In
828 && defaults::parse_default_literal(param, &mapped, ctx).is_some();
829 if has_default {
830 let default_expr = defaults::parse_default_literal(param, &mapped, ctx)
831 .expect("default literal must be parseable (has_default was true)");
832 default_unwraps.push((pname.clone(), default_expr));
833 }
834 match mapped.rust_to_ffi {
835 ConversionKind::StringUtf8 => {
836 if has_default {
837 sig.push_str(&format!("{pname}: Option<&str>, "));
838 } else {
839 sig.push_str(&format!("{pname}: &str, "));
840 }
841 }
842 ConversionKind::StructOpaque if dir == ParamDirection::In
843 && is_struct_owned(param.struct_name.as_deref(), ctx) =>
844 {
845 let si = ctx.structs.get(param.struct_name.as_deref().expect("StructOpaque param must have struct_name"))
846 .expect("struct must exist in context");
847 sig.push_str(&format!(
848 "{pname}: &rusteal_core::OwnedStruct<{}>, ", si.cpp_name
849 ));
850 }
851 ConversionKind::StructOpaque if dir == ParamDirection::InOut => {
852 sig.push_str(&format!("{pname}: *mut u8, "));
853 }
854 _ => {
855 if has_default {
856 sig.push_str(&format!("{pname}: Option<{}>, ", mapped.rust_type));
857 } else {
858 sig.push_str(&format!("{pname}: {}, ", mapped.rust_type));
859 }
860 }
861 }
862 }
863 }
864 if sig.ends_with(", ") {
865 sig.truncate(sig.len() - 2);
866 }
867 if return_rust_type == "()" {
868 sig.push(')');
869 } else {
870 sig.push_str(&format!(") -> {return_rust_type}"));
871 }
872 out.push_str(&sig);
873 out.push_str(" {\n");
874
875 for (pname, default_expr) in &default_unwraps {
877 out.push_str(&format!(" let {pname} = {pname}.unwrap_or({default_expr});\n"));
878 }
879
880 out.push_str(" {\n");
881
882 let ue_name_len = ue_name.len();
884 let ue_name_byte_lit = format!("b\"{}\\0\"", ue_name);
885
886 out.push_str(&format!(
887 " const FN_ID: u32 = {func_id};\n\
888 \x20 static CPROPS: std::sync::OnceLock<[rusteal_core::FPropertyHandle; {n_containers}]> = std::sync::OnceLock::new();\n\
889 \x20 let __cprops = CPROPS.get_or_init(|| unsafe {{\n\
890 \x20 let __ufunc = ((*rusteal_core::api().reflection).find_function_by_class)(\n\
891 \x20 {class_name}::static_class(),\n\
892 \x20 {ue_name_byte_lit}.as_ptr(), {ue_name_len});\n\
893 \x20 [\n"
894 ));
895 for cp in &container_params {
896 let param_name = &cp.param.name;
897 let param_name_len = param_name.len();
898 let param_byte_lit = format!("b\"{}\\0\"", param_name);
899 out.push_str(&format!(
900 " ((*rusteal_core::api().reflection).get_function_param)(\n\
901 \x20 __ufunc, {param_byte_lit}.as_ptr(), {param_name_len}),\n"
902 ));
903 }
904 out.push_str(
905 " ]\n\
906 \x20 });\n"
907 );
908
909 let mut ffi_params = String::new();
911 if !is_static {
912 ffi_params.push_str("rusteal_core::UObjectHandle, ");
913 }
914 for param in &func.params {
915 let dir = type_map::param_direction(param);
916 if is_container_param(param) {
917 ffi_params.push_str("*mut u8, *mut u8, "); } else {
919 let mapped = map_param(param);
920 match dir {
921 ParamDirection::In | ParamDirection::InOut => {
922 match mapped.rust_to_ffi {
923 ConversionKind::StringUtf8 => {
924 ffi_params.push_str("*const u8, u32, ");
925 if dir == ParamDirection::InOut {
926 ffi_params.push_str("*mut u8, u32, *mut u32, ");
927 }
928 }
929 ConversionKind::ObjectRef => ffi_params.push_str("rusteal_core::UObjectHandle, "),
930 ConversionKind::EnumCast => ffi_params.push_str(&format!("{}, ", mapped.rust_ffi_type)),
931 ConversionKind::StructOpaque => {
932 if dir == ParamDirection::InOut {
933 ffi_params.push_str("*mut u8, ");
934 } else {
935 ffi_params.push_str("*const u8, ");
936 }
937 }
938 _ => ffi_params.push_str(&format!("{}, ", mapped.rust_ffi_type)),
939 }
940 }
941 ParamDirection::Out | ParamDirection::Return => {
942 match mapped.ffi_to_rust {
943 ConversionKind::StringUtf8 => ffi_params.push_str("*mut u8, u32, *mut u32, "),
944 ConversionKind::ObjectRef => ffi_params.push_str("*mut rusteal_core::UObjectHandle, "),
945 ConversionKind::StructOpaque => ffi_params.push_str("*mut u8, "),
946 ConversionKind::EnumCast => ffi_params.push_str(&format!("*mut {}, ", mapped.rust_ffi_type)),
947 _ => ffi_params.push_str(&format!("*mut {}, ", mapped.rust_ffi_type)),
948 }
949 }
950 }
951 }
952 }
953 if ffi_params.ends_with(", ") {
954 ffi_params.truncate(ffi_params.len() - 2);
955 }
956
957 out.push_str(&format!(
958 " type Fn = unsafe extern \"C\" fn({ffi_params}) -> rusteal_core::RustealErrorCode;\n\
959 \x20 let __rusteal_fn: Fn = unsafe {{ std::mem::transmute(*(rusteal_core::api().func_table.add(FN_ID as usize))) }};\n"
960 ));
961
962 if !is_static {
964 out.push_str(" let h = self.handle();\n");
965 }
966
967 for cp in &container_params {
969 let idx = cp.index;
970 out.push_str(&format!(
971 " let __temp_{idx} = unsafe {{ ((*rusteal_core::api().container).alloc_temp)(__cprops[{idx}]) }};\n"
972 ));
973 }
974
975 for cp in &container_params {
977 if cp.dir != ParamDirection::In && cp.dir != ParamDirection::InOut {
978 continue;
979 }
980 let idx = cp.index;
981 let pname = escape_reserved(&to_snake_case(&cp.param.name));
982 emit_container_populate(out, cp.param, idx, &pname, ctx);
983 }
984
985 let ret_mapped = scalar_return_mapped.as_ref();
987 if let Some(rm) = ret_mapped {
988 match rm.ffi_to_rust {
989 ConversionKind::ObjectRef => {
990 out.push_str(" let mut __scalar_ret = rusteal_core::UObjectHandle::null();\n");
991 }
992 ConversionKind::StringUtf8 => {
993 out.push_str(" let mut __scalar_ret_buf = vec![0u8; 512];\n");
994 out.push_str(" let mut __scalar_ret_len: u32 = 0;\n");
995 }
996 ConversionKind::EnumCast => {
997 out.push_str(&format!(" let mut __scalar_ret: {} = 0;\n", rm.rust_ffi_type));
998 }
999 ConversionKind::StructOpaque => {
1000 out.push_str(" let mut __scalar_ret_buf = vec![0u8; 256];\n");
1001 }
1002 _ => {
1003 let default = properties::default_value_for(&rm.rust_ffi_type);
1004 out.push_str(&format!(" let mut __scalar_ret = {default};\n"));
1005 }
1006 }
1007 }
1008
1009 for param in &func.params {
1011 let dir = type_map::param_direction(param);
1012 if dir == ParamDirection::Out && !is_container_param(param) {
1013 let mapped = map_param(param);
1014 param_helpers::emit_out_param_var_decl(out, param, &mapped);
1015 }
1016 if dir == ParamDirection::InOut && !is_container_param(param) {
1017 let mapped = map_param(param);
1018 param_helpers::emit_inout_string_buf_decl(out, param, &mapped);
1019 }
1020 }
1021
1022 out.push_str(" let __result = unsafe { __rusteal_fn(");
1024 if !is_static {
1025 out.push_str("h, ");
1026 }
1027 for param in &func.params {
1028 let dir = type_map::param_direction(param);
1029 if is_container_param(param) {
1030 let cp = container_params.iter().find(|c| std::ptr::eq(c.param, param))
1031 .expect("container param must have matching metadata");
1032 let idx = cp.index;
1033 out.push_str(&format!("__temp_{idx}, __cprops[{idx}].0 as *mut u8, "));
1034 } else {
1035 let pname = escape_reserved(&to_snake_case(¶m.name));
1036 let mapped = map_param(param);
1037 match dir {
1038 ParamDirection::In | ParamDirection::InOut => {
1039 match mapped.rust_to_ffi {
1040 ConversionKind::StringUtf8 => {
1041 out.push_str(&format!("{pname}.as_ptr(), {pname}.len() as u32, "));
1042 if dir == ParamDirection::InOut {
1043 out.push_str(&format!("{pname}_buf.as_mut_ptr(), {pname}_buf.len() as u32, &mut {pname}_len, "));
1044 }
1045 }
1046 ConversionKind::ObjectRef => {
1047 out.push_str(&format!("rusteal_core::ObjectPointer::object_handle(&{pname}), "));
1048 }
1049 ConversionKind::EnumCast => {
1050 out.push_str(&format!("{pname} as {}, ", mapped.rust_ffi_type));
1051 }
1052 ConversionKind::StructOpaque if dir == ParamDirection::In
1053 && is_struct_owned(param.struct_name.as_deref(), ctx) =>
1054 {
1055 out.push_str(&format!("{pname}.as_bytes().as_ptr(), "));
1056 }
1057 _ => {
1058 out.push_str(&format!("{pname}, "));
1059 }
1060 }
1061 }
1062 ParamDirection::Out => {
1063 match mapped.ffi_to_rust {
1064 ConversionKind::StructOpaque => {
1065 out.push_str(&format!("{pname}_buf.as_mut_ptr(), "));
1066 }
1067 ConversionKind::StringUtf8 => {
1068 out.push_str(&format!("{pname}_buf.as_mut_ptr(), {pname}_buf.len() as u32, &mut {pname}_len, "));
1069 }
1070 _ => {
1071 out.push_str(&format!("&mut {pname}, "));
1072 }
1073 }
1074 }
1075 ParamDirection::Return => {
1076 let rm = ret_mapped.expect("return param must have mapped type");
1077 match rm.ffi_to_rust {
1078 ConversionKind::StringUtf8 => {
1079 out.push_str("__scalar_ret_buf.as_mut_ptr(), __scalar_ret_buf.len() as u32, &mut __scalar_ret_len, ");
1080 }
1081 ConversionKind::ObjectRef => {
1082 out.push_str("&mut __scalar_ret, ");
1083 }
1084 ConversionKind::StructOpaque => {
1085 out.push_str("__scalar_ret_buf.as_mut_ptr(), ");
1086 }
1087 _ => {
1088 out.push_str("&mut __scalar_ret, ");
1089 }
1090 }
1091 }
1092 }
1093 }
1094 }
1095 let out_len = out.len();
1097 if out.ends_with(", ") {
1098 out.truncate(out_len - 2);
1099 }
1100 out.push_str(") };\n");
1101
1102 for cp in &container_params {
1104 if cp.dir != ParamDirection::Out && cp.dir != ParamDirection::Return && cp.dir != ParamDirection::InOut {
1105 continue;
1106 }
1107 let idx = cp.index;
1108 emit_container_read(out, cp.param, idx, ctx);
1109 }
1110
1111 out.push_str(" unsafe {\n");
1113 for cp in &container_params {
1114 let idx = cp.index;
1115 out.push_str(&format!(
1116 " ((*rusteal_core::api().container).free_temp)(__cprops[{idx}], __temp_{idx});\n"
1117 ));
1118 }
1119 out.push_str(" }\n");
1120
1121 out.push_str(" rusteal_core::ffi_infallible(__result);\n");
1123
1124 emit_container_return(out, return_param, ret_mapped, &container_params, &func.params, ctx);
1126
1127 out.push_str(" }\n");
1128 out.push_str(" }\n\n");
1129}
1130
1131fn emit_container_populate(out: &mut String, param: &ParamInfo, idx: usize, pname: &str, ctx: &CodegenContext) {
1133 let elem_type = container_elem_type_str(param, ctx)
1134 .expect("container element type must be resolvable");
1135 match param.prop_type.as_str() {
1136 "ArrayProperty" => {
1137 out.push_str(&format!(
1138 " {{\n\
1139 \x20 let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1140 \x20 let __arr = rusteal_core::UeArray::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1141 \x20 for __elem in {pname} {{\n\
1142 \x20 let _ = __arr.push(__elem);\n\
1143 \x20 }}\n\
1144 \x20 }}\n"
1145 ));
1146 }
1147 "SetProperty" => {
1148 out.push_str(&format!(
1149 " {{\n\
1150 \x20 let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1151 \x20 let __set = rusteal_core::UeSet::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1152 \x20 for __elem in {pname} {{\n\
1153 \x20 let _ = __set.add(__elem);\n\
1154 \x20 }}\n\
1155 \x20 }}\n"
1156 ));
1157 }
1158 "MapProperty" => {
1159 out.push_str(&format!(
1160 " {{\n\
1161 \x20 let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1162 \x20 let __map = rusteal_core::UeMap::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1163 \x20 for (__k, __v) in {pname} {{\n\
1164 \x20 let _ = __map.add(__k, __v);\n\
1165 \x20 }}\n\
1166 \x20 }}\n"
1167 ));
1168 }
1169 _ => {}
1170 }
1171}
1172
1173fn emit_container_read(out: &mut String, param: &ParamInfo, idx: usize, ctx: &CodegenContext) {
1175 let elem_type = container_elem_type_str(param, ctx)
1176 .expect("container element type must be resolvable");
1177 match param.prop_type.as_str() {
1178 "ArrayProperty" => {
1179 out.push_str(&format!(
1180 " let __out_{idx} = if __result == rusteal_core::RustealErrorCode::Ok {{\n\
1181 \x20 let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1182 \x20 let __arr = rusteal_core::UeArray::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1183 \x20 let __len = __arr.len().unwrap_or(0);\n\
1184 \x20 let mut __v = Vec::with_capacity(__len);\n\
1185 \x20 for __i in 0..__len {{\n\
1186 \x20 if let Ok(__val) = __arr.get(__i) {{ __v.push(__val); }}\n\
1187 \x20 }}\n\
1188 \x20 __v\n\
1189 \x20 }} else {{ Vec::new() }};\n"
1190 ));
1191 }
1192 "SetProperty" => {
1193 out.push_str(&format!(
1194 " let __out_{idx} = if __result == rusteal_core::RustealErrorCode::Ok {{\n\
1195 \x20 let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1196 \x20 let __set = rusteal_core::UeSet::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1197 \x20 let __len = __set.len().unwrap_or(0);\n\
1198 \x20 let mut __v = Vec::with_capacity(__len);\n\
1199 \x20 for __i in 0..__len {{\n\
1200 \x20 if let Ok(__val) = __set.get_element(__i) {{ __v.push(__val); }}\n\
1201 \x20 }}\n\
1202 \x20 __v\n\
1203 \x20 }} else {{ Vec::new() }};\n"
1204 ));
1205 }
1206 "MapProperty" => {
1207 out.push_str(&format!(
1208 " let __out_{idx} = if __result == rusteal_core::RustealErrorCode::Ok {{\n\
1209 \x20 let __h = rusteal_core::UObjectHandle(__temp_{idx} as *mut std::ffi::c_void);\n\
1210 \x20 let __map = rusteal_core::UeMap::<{elem_type}>::new(__h, __cprops[{idx}]);\n\
1211 \x20 let __len = __map.len().unwrap_or(0);\n\
1212 \x20 let mut __v = Vec::with_capacity(__len);\n\
1213 \x20 for __i in 0..__len {{\n\
1214 \x20 if let Ok(__pair) = __map.get_pair(__i) {{ __v.push(__pair); }}\n\
1215 \x20 }}\n\
1216 \x20 __v\n\
1217 \x20 }} else {{ Vec::new() }};\n"
1218 ));
1219 }
1220 _ => {}
1221 }
1222}
1223
1224fn emit_container_return(
1226 out: &mut String,
1227 return_param: Option<&ParamInfo>,
1228 ret_mapped: Option<&MappedType>,
1229 container_params: &[ContainerParamMeta],
1230 func_params: &[ParamInfo],
1231 ctx: &CodegenContext,
1232) {
1233 let mut return_parts = Vec::new();
1234
1235 if let Some(rp) = return_param {
1237 if is_container_param(rp) {
1238 let cp = container_params.iter().find(|c| c.dir == ParamDirection::Return)
1239 .expect("container return param must exist");
1240 return_parts.push(format!("__out_{}", cp.index));
1241 } else if let Some(rm) = ret_mapped {
1242 match rm.ffi_to_rust {
1243 ConversionKind::ObjectRef => {
1244 return_parts.push("unsafe { rusteal_core::ObjectPointer::from_object_handle(__scalar_ret) }".to_string());
1245 }
1246 ConversionKind::StringUtf8 => {
1247 out.push_str(" __scalar_ret_buf.truncate(__scalar_ret_len as usize);\n");
1248 out.push_str(" let __scalar_str = String::from_utf8_lossy(&__scalar_ret_buf).into_owned();\n");
1249 return_parts.push("__scalar_str".to_string());
1250 }
1251 ConversionKind::EnumCast => {
1252 let rt = &rm.rust_type;
1253 let rp_ref = return_param.expect("return_param must be Some in return conversion");
1254 let actual_repr = rp_ref.enum_name.as_deref()
1255 .and_then(|en| ctx.enum_actual_repr(en))
1256 .unwrap_or(&rm.rust_ffi_type);
1257 out.push_str(&format!(
1258 " let __scalar_enum = {rt}::from_value(__scalar_ret as {actual_repr}).expect(\"unknown enum value\");\n"
1259 ));
1260 return_parts.push("__scalar_enum".to_string());
1261 }
1262 ConversionKind::StructOpaque => {
1263 let rp_ref = return_param.expect("return_param must be Some in return conversion");
1264 if is_struct_owned(rp_ref.struct_name.as_deref(), ctx) {
1265 out.push_str(" let __scalar_owned = rusteal_core::OwnedStruct::from_bytes(__scalar_ret_buf);\n");
1266 return_parts.push("__scalar_owned".to_string());
1267 } else {
1268 out.push_str(" let __scalar_ptr = __scalar_ret_buf.as_ptr();\n");
1269 out.push_str(" std::mem::forget(__scalar_ret_buf);\n");
1270 return_parts.push("__scalar_ptr".to_string());
1271 }
1272 }
1273 _ => {
1274 return_parts.push("__scalar_ret".to_string());
1275 }
1276 }
1277 }
1278 }
1279
1280 for param in func_params {
1282 let dir = type_map::param_direction(param);
1283 if dir != ParamDirection::Out && dir != ParamDirection::InOut {
1284 continue;
1285 }
1286
1287 if is_container_param(param) {
1288 let cp = container_params.iter().find(|c| std::ptr::eq(c.param, param))
1289 .expect("container param must have matching metadata");
1290 return_parts.push(format!("__out_{}", cp.index));
1291 } else {
1292 let mapped = map_param(param);
1293 if !is_scalar_output_returnable(dir, &mapped) {
1294 continue;
1295 }
1296 return_parts.push(param_helpers::emit_out_param_conversion(
1297 out, param, &mapped, ctx,
1298 ));
1299 }
1300 }
1301
1302 param_helpers::emit_return_expr(out, &return_parts);
1303}
1304
1305pub(super) fn map_param(param: &ParamInfo) -> MappedType {
1307 type_map::map_property_type(
1308 ¶m.prop_type,
1309 param.class_name.as_deref(),
1310 param.struct_name.as_deref(),
1311 param.enum_name.as_deref(),
1312 param.enum_underlying_type.as_deref(),
1313 param.meta_class_name.as_deref(),
1314 param.interface_name.as_deref(),
1315 )
1316}