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