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