1use crate::context::CodegenContext;
2use crate::naming::{strip_bool_prefix, to_snake_case};
3use crate::schema::PropertyInfo;
4use crate::type_map::{self, ConversionKind, MappedType};
5
6pub struct PropertyContext {
7 pub find_prop_fn: String,
8 pub handle_expr: String,
9 pub pre_access: String,
10 pub container_expr: String,
11 pub is_class: bool,
12}
13
14pub fn rust_property_name(prop: &PropertyInfo) -> String {
15 if prop.prop_type == "BoolProperty" {
16 strip_bool_prefix(&prop.name)
17 } else {
18 to_snake_case(&prop.name)
19 }
20}
21
22pub fn is_read_only_non_public(prop: &PropertyInfo) -> bool {
23 use crate::schema::{
24 CPF_BLUEPRINT_READ_ONLY, CPF_NATIVE_ACCESS_PRIVATE, CPF_NATIVE_ACCESS_PROTECTED,
25 };
26
27 prop.prop_flags & (CPF_NATIVE_ACCESS_PRIVATE | CPF_NATIVE_ACCESS_PROTECTED) != 0
28 && prop.prop_flags & CPF_BLUEPRINT_READ_ONLY != 0
29}
30
31pub fn collect_deduped_properties<'a>(
32 props: &'a [PropertyInfo],
33 ctx: Option<&CodegenContext>,
34) -> (std::collections::HashSet<String>, Vec<&'a PropertyInfo>) {
35 let mut prop_names = std::collections::HashSet::new();
36 let mut deduped = Vec::new();
37
38 for prop in props {
39 if prop.getter.is_some() || prop.setter.is_some() {
40 continue;
41 }
42
43 let mapped = type_map::map_property_type(
44 &prop.prop_type,
45 prop.class_name.as_deref(),
46 prop.struct_name.as_deref(),
47 prop.enum_name.as_deref(),
48 prop.enum_underlying_type.as_deref(),
49 prop.meta_class_name.as_deref(),
50 prop.interface_name.as_deref(),
51 );
52
53 if !mapped.supported {
54 continue;
55 }
56
57 if matches!(mapped.rust_to_ffi, ConversionKind::ObjectRef)
58 && let Some(ctx) = ctx
59 {
60 let type_available = match prop.prop_type.as_str() {
61 "ClassProperty" => {
62 let effective = prop
63 .meta_class_name
64 .as_deref()
65 .or(prop.class_name.as_deref());
66
67 effective.is_none_or(|c| ctx.classes.contains_key(c))
68 }
69 "InterfaceProperty" => prop
70 .interface_name
71 .as_deref()
72 .is_some_and(|i| ctx.classes.contains_key(i)),
73 _ => prop
74 .class_name
75 .as_deref()
76 .is_none_or(|c| ctx.classes.contains_key(c)),
77 };
78
79 if !type_available {
80 continue;
81 }
82 }
83
84 if matches!(mapped.rust_to_ffi, ConversionKind::StructOpaque) {
85 let valid = if let Some(ctx) = ctx {
86 prop.struct_name
87 .as_deref()
88 .is_some_and(|sn| ctx.structs.get(sn).is_some_and(|si| si.has_static_struct))
89 } else {
90 prop.struct_name.is_some()
91 };
92
93 if !valid {
94 continue;
95 }
96 }
97
98 if matches!(
99 mapped.rust_to_ffi,
100 ConversionKind::ContainerArray
101 | ConversionKind::ContainerMap
102 | ConversionKind::ContainerSet
103 ) && type_map::resolve_container_rust_type(prop, ctx).is_none()
104 {
105 continue;
106 }
107
108 if let Some(ctx) = ctx {
109 match mapped.rust_to_ffi {
110 ConversionKind::EnumCast => {
111 if let Some(en) = &prop.enum_name
112 && !ctx.enums.contains_key(en.as_str())
113 {
114 continue;
115 }
116 }
117 ConversionKind::ObjectRef => {
118 if let Some(cn) = &prop.class_name
119 && !ctx.classes.contains_key(cn.as_str())
120 {
121 continue;
122 }
123 }
124 _ => {}
125 }
126 }
127
128 let rust_name = if prop.prop_type == "BoolProperty" {
129 strip_bool_prefix(&prop.name)
130 } else {
131 to_snake_case(&prop.name)
132 };
133
134 let is_container = matches!(
135 mapped.rust_to_ffi,
136 ConversionKind::ContainerArray
137 | ConversionKind::ContainerMap
138 | ConversionKind::ContainerSet
139 );
140
141 let is_delegate = matches!(
142 mapped.rust_to_ffi,
143 ConversionKind::Delegate | ConversionKind::MulticastDelegate
144 );
145
146 let getter_name = if is_container || is_delegate {
147 rust_name.clone()
148 } else {
149 format!("get_{rust_name}")
150 };
151
152 if prop_names.contains(&getter_name) {
153 continue;
154 }
155
156 prop_names.insert(getter_name);
157
158 if !is_container && !is_delegate {
159 prop_names.insert(format!("set_{rust_name}"));
160 }
161
162 deduped.push(prop);
163 }
164
165 (prop_names, deduped)
166}
167
168pub fn generate_property(
169 out: &mut String,
170 prop: &PropertyInfo,
171 pctx: &PropertyContext,
172 ctx: &CodegenContext,
173 suppress_setters: &std::collections::HashSet<String>,
174) {
175 let mapped = type_map::map_property_type(
176 &prop.prop_type,
177 prop.class_name.as_deref(),
178 prop.struct_name.as_deref(),
179 prop.enum_name.as_deref(),
180 prop.enum_underlying_type.as_deref(),
181 prop.meta_class_name.as_deref(),
182 prop.interface_name.as_deref(),
183 );
184
185 if !mapped.supported {
186 return;
187 }
188
189 if matches!(
190 mapped.rust_to_ffi,
191 ConversionKind::Delegate | ConversionKind::MulticastDelegate
192 ) {
193 return;
194 }
195
196 let prop_name = &prop.name;
197
198 let rust_name = if prop.prop_type == "BoolProperty" {
199 strip_bool_prefix(prop_name)
200 } else {
201 to_snake_case(prop_name)
202 };
203
204 let prop_name_len = prop_name.len();
205 let byte_lit = format!("b\"{prop_name}\\0\"");
206
207 if prop.array_dim > 1 {
208 generate_fixed_array_property(
209 out,
210 prop,
211 &rust_name,
212 &byte_lit,
213 prop_name_len,
214 pctx,
215 ctx,
216 &mapped,
217 );
218
219 return;
220 }
221
222 if matches!(
223 mapped.rust_to_ffi,
224 ConversionKind::ContainerArray
225 | ConversionKind::ContainerMap
226 | ConversionKind::ContainerSet
227 ) {
228 if pctx.is_class
229 && let Some(container_type) = type_map::resolve_container_rust_type(prop, Some(ctx))
230 {
231 generate_container_getter(
232 out,
233 &rust_name,
234 &byte_lit,
235 prop_name_len,
236 pctx,
237 &container_type,
238 );
239 }
240
241 return;
242 }
243
244 match mapped.rust_to_ffi {
245 ConversionKind::StringUtf8 => {
246 generate_string_getter(out, &rust_name, &byte_lit, prop_name_len, pctx);
247 }
248 ConversionKind::StructOpaque => {
249 let struct_cpp = prop
250 .struct_name
251 .as_deref()
252 .and_then(|sn| ctx.structs.get(sn))
253 .map(|si| si.cpp_name.clone())
254 .unwrap_or_else(|| {
255 format!("F{}", prop.struct_name.as_deref().unwrap_or("Unknown"))
256 });
257
258 generate_struct_getter(out, &rust_name, &byte_lit, prop_name_len, pctx, &struct_cpp);
259 let setter_name = format!("set_{rust_name}");
260
261 if !suppress_setters.contains(&setter_name) {
262 generate_struct_setter(
263 out,
264 &rust_name,
265 &byte_lit,
266 prop_name_len,
267 pctx,
268 &struct_cpp,
269 );
270 }
271
272 return;
273 }
274 ConversionKind::ObjectRef => {
275 generate_object_getter(out, &rust_name, &byte_lit, prop_name_len, pctx, &mapped);
276 }
277 ConversionKind::EnumCast => {
278 let actual_repr = prop
279 .enum_name
280 .as_deref()
281 .and_then(|en| ctx.enum_actual_repr(en))
282 .unwrap_or(&mapped.rust_ffi_type);
283
284 generate_enum_getter(
285 out,
286 &rust_name,
287 &byte_lit,
288 prop_name_len,
289 pctx,
290 &mapped,
291 actual_repr,
292 );
293 }
294 ConversionKind::FName => {
295 generate_fname_getter(out, &rust_name, &byte_lit, prop_name_len, pctx);
296 }
297 ConversionKind::IntCast => {
298 generate_int_cast_getter(out, &rust_name, &byte_lit, prop_name_len, pctx, &mapped);
299 }
300 _ => {
301 generate_primitive_getter(out, &rust_name, &byte_lit, prop_name_len, pctx, &mapped);
302 }
303 }
304
305 let setter_name = format!("set_{rust_name}");
306
307 if suppress_setters.contains(&setter_name) {
308 return;
309 }
310
311 match mapped.rust_to_ffi {
312 ConversionKind::StringUtf8 => {
313 generate_string_setter(out, &rust_name, &byte_lit, prop_name_len, pctx);
314 }
315 ConversionKind::StructOpaque => {}
316 ConversionKind::ObjectRef => {
317 generate_object_setter(out, &rust_name, &byte_lit, prop_name_len, pctx, &mapped);
318 }
319 ConversionKind::EnumCast => {
320 generate_enum_setter(out, &rust_name, &byte_lit, prop_name_len, pctx, &mapped);
321 }
322 ConversionKind::FName => {
323 generate_fname_setter(out, &rust_name, &byte_lit, prop_name_len, pctx);
324 }
325 ConversionKind::IntCast => {
326 generate_int_cast_setter(out, &rust_name, &byte_lit, prop_name_len, pctx, &mapped);
327 }
328 _ => {
329 generate_primitive_setter(out, &rust_name, &byte_lit, prop_name_len, pctx, &mapped);
330 }
331 }
332}
333
334pub fn default_value_for(rust_type: &str) -> &'static str {
335 match rust_type {
336 "bool" => "false",
337 "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" => "0",
338 "f32" => "0.0f32",
339 "f64" => "0.0f64",
340 _ => "Default::default()",
341 }
342}
343
344fn emit_prop_lookup(
345 out: &mut String,
346 byte_lit: &str,
347 prop_name_len: usize,
348 pctx: &PropertyContext,
349) {
350 let find = &pctx.find_prop_fn;
351 let handle = &pctx.handle_expr;
352
353 out.push_str(&format!(
354 " static PROP: std::sync::OnceLock<rusteal_core::FPropertyHandle> = std::sync::OnceLock::new();\n\
355 \x20 let prop = *PROP.get_or_init(|| unsafe {{\n\
356 \x20 rusteal_core::ffi_dispatch::reflection_{find}(\n\
357 \x20 {handle}, {byte_lit}.as_ptr(), {prop_name_len}\n\
358 \x20 )\n\
359 \x20 }});\n"
360 ));
361}
362
363fn emit_pre_access(out: &mut String, pctx: &PropertyContext) {
364 if !pctx.pre_access.is_empty() {
365 out.push_str(&format!(" {}\n", pctx.pre_access));
366 }
367}
368
369fn generate_primitive_getter(
370 out: &mut String,
371 rust_name: &str,
372 byte_lit: &str,
373 prop_name_len: usize,
374 pctx: &PropertyContext,
375 mapped: &MappedType,
376) {
377 let rust_type = &mapped.rust_type;
378 let getter = &mapped.property_getter;
379 let default = default_value_for(rust_type);
380 let c = &pctx.container_expr;
381
382 out.push_str(&format!(
383 " fn get_{rust_name}(&self) -> {rust_type} {{\n"
384 ));
385
386 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
387 emit_pre_access(out, pctx);
388
389 out.push_str(&format!(
390 " let mut out = {default};\n\
391 \x20 rusteal_core::ffi_infallible_ctx(unsafe {{ rusteal_core::ffi_dispatch::property_{getter}({c}, prop, &mut out) }}, \"{rust_name}\");\n\
392 \x20 out\n\
393 \x20 }}\n\n"
394 ));
395}
396
397fn generate_int_cast_getter(
398 out: &mut String,
399 rust_name: &str,
400 byte_lit: &str,
401 prop_name_len: usize,
402 pctx: &PropertyContext,
403 mapped: &MappedType,
404) {
405 let rust_type = &mapped.rust_type;
406 let ffi_type = &mapped.rust_ffi_type;
407 let getter = &mapped.property_getter;
408 let default = default_value_for(ffi_type);
409 let c = &pctx.container_expr;
410
411 out.push_str(&format!(
412 " fn get_{rust_name}(&self) -> {rust_type} {{\n"
413 ));
414
415 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
416 emit_pre_access(out, pctx);
417
418 out.push_str(&format!(
419 " let mut out = {default};\n\
420 \x20 rusteal_core::ffi_infallible_ctx(unsafe {{ rusteal_core::ffi_dispatch::property_{getter}({c}, prop, &mut out) }}, \"{rust_name}\");\n\
421 \x20 out as {rust_type}\n\
422 \x20 }}\n\n"
423 ));
424}
425
426fn generate_string_getter(
427 out: &mut String,
428 rust_name: &str,
429 byte_lit: &str,
430 prop_name_len: usize,
431 pctx: &PropertyContext,
432) {
433 let c = &pctx.container_expr;
434
435 out.push_str(&format!(" fn get_{rust_name}(&self) -> String {{\n"));
436
437 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
438 emit_pre_access(out, pctx);
439
440 out.push_str(&format!(
441 " let mut buf = vec![0u8; 512];\n\
442 \x20 let mut out_len: u32 = 0;\n\
443 \x20 rusteal_core::ffi_infallible_ctx(unsafe {{\n\
444 \x20 rusteal_core::ffi_dispatch::property_get_string({c}, prop, buf.as_mut_ptr(), buf.len() as u32, &mut out_len)\n\
445 \x20 }}, \"{rust_name}\");\n\
446 \x20 buf.truncate(out_len as usize);\n\
447 \x20 String::from_utf8_lossy(&buf).into_owned()\n\
448 \x20 }}\n\n"
449 ));
450}
451
452fn generate_object_getter(
453 out: &mut String,
454 rust_name: &str,
455 byte_lit: &str,
456 prop_name_len: usize,
457 pctx: &PropertyContext,
458 mapped: &MappedType,
459) {
460 let rust_type = &mapped.rust_type;
461 let c = &pctx.container_expr;
462
463 out.push_str(&format!(
464 " fn get_{rust_name}(&self) -> {rust_type} {{\n"
465 ));
466
467 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
468 emit_pre_access(out, pctx);
469
470 out.push_str(&format!(
471 " let mut raw = rusteal_core::UObjectHandle::null();\n\
472 \x20 rusteal_core::ffi_infallible_ctx(unsafe {{ rusteal_core::ffi_dispatch::property_get_object({c}, prop, &mut raw) }}, \"{rust_name}\");\n\
473 \x20 unsafe {{ rusteal_core::ObjectPointer::from_object_handle(raw) }}\n\
474 \x20 }}\n\n"
475 ));
476}
477
478fn generate_enum_getter(
479 out: &mut String,
480 rust_name: &str,
481 byte_lit: &str,
482 prop_name_len: usize,
483 pctx: &PropertyContext,
484 mapped: &MappedType,
485 actual_repr: &str,
486) {
487 let rust_type = &mapped.rust_type;
488 let c = &pctx.container_expr;
489
490 out.push_str(&format!(
491 " fn get_{rust_name}(&self) -> {rust_type} {{\n"
492 ));
493
494 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
495 emit_pre_access(out, pctx);
496
497 out.push_str(&format!(
498 " let mut raw: i64 = 0;\n\
499 \x20 rusteal_core::ffi_infallible_ctx(unsafe {{ rusteal_core::ffi_dispatch::property_get_enum({c}, prop, &mut raw) }}, \"{rust_name}\");\n\
500 \x20 {rust_type}::from_value(raw as {actual_repr}).expect(\"unknown enum value\")\n\
501 \x20 }}\n\n"
502 ));
503}
504
505fn generate_fname_getter(
506 out: &mut String,
507 rust_name: &str,
508 byte_lit: &str,
509 prop_name_len: usize,
510 pctx: &PropertyContext,
511) {
512 let c = &pctx.container_expr;
513
514 out.push_str(&format!(
515 " fn get_{rust_name}(&self) -> rusteal_core::FNameHandle {{\n"
516 ));
517
518 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
519 emit_pre_access(out, pctx);
520
521 out.push_str(&format!(
522 " let mut out = rusteal_core::FNameHandle(0);\n\
523 \x20 rusteal_core::ffi_infallible_ctx(unsafe {{ rusteal_core::ffi_dispatch::property_get_fname({c}, prop, &mut out) }}, \"{rust_name}\");\n\
524 \x20 out\n\
525 \x20 }}\n\n"
526 ));
527}
528
529fn generate_primitive_setter(
530 out: &mut String,
531 rust_name: &str,
532 byte_lit: &str,
533 prop_name_len: usize,
534 pctx: &PropertyContext,
535 mapped: &MappedType,
536) {
537 let rust_type = &mapped.rust_type;
538 let setter = &mapped.property_setter;
539 let c = &pctx.container_expr;
540
541 out.push_str(&format!(
542 " fn set_{rust_name}(&self, val: {rust_type}) {{\n"
543 ));
544
545 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
546 emit_pre_access(out, pctx);
547
548 out.push_str(&format!(
549 " rusteal_core::ffi_infallible_ctx(unsafe {{ rusteal_core::ffi_dispatch::property_{setter}({c}, prop, val) }}, \"{rust_name}\");\n\
550 \x20 }}\n\n"
551 ));
552}
553
554fn generate_int_cast_setter(
555 out: &mut String,
556 rust_name: &str,
557 byte_lit: &str,
558 prop_name_len: usize,
559 pctx: &PropertyContext,
560 mapped: &MappedType,
561) {
562 let rust_type = &mapped.rust_type;
563 let ffi_type = &mapped.rust_ffi_type;
564 let setter = &mapped.property_setter;
565 let c = &pctx.container_expr;
566
567 out.push_str(&format!(
568 " fn set_{rust_name}(&self, val: {rust_type}) {{\n"
569 ));
570
571 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
572 emit_pre_access(out, pctx);
573
574 out.push_str(&format!(
575 " rusteal_core::ffi_infallible_ctx(unsafe {{ rusteal_core::ffi_dispatch::property_{setter}({c}, prop, val as {ffi_type}) }}, \"{rust_name}\");\n\
576 \x20 }}\n\n"
577 ));
578}
579
580fn generate_string_setter(
581 out: &mut String,
582 rust_name: &str,
583 byte_lit: &str,
584 prop_name_len: usize,
585 pctx: &PropertyContext,
586) {
587 let c = &pctx.container_expr;
588
589 out.push_str(&format!(" fn set_{rust_name}(&self, val: &str) {{\n"));
590
591 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
592 emit_pre_access(out, pctx);
593
594 out.push_str(&format!(
595 " rusteal_core::ffi_infallible_ctx(unsafe {{\n\
596 \x20 rusteal_core::ffi_dispatch::property_set_string({c}, prop, val.as_ptr(), val.len() as u32)\n\
597 \x20 }}, \"{rust_name}\");\n\
598 \x20 }}\n\n"
599 ));
600}
601
602fn generate_object_setter(
603 out: &mut String,
604 rust_name: &str,
605 byte_lit: &str,
606 prop_name_len: usize,
607 pctx: &PropertyContext,
608 mapped: &MappedType,
609) {
610 let rust_type = &mapped.rust_type;
611 let c = &pctx.container_expr;
612
613 out.push_str(&format!(
614 " fn set_{rust_name}(&self, val: {rust_type}) {{\n"
615 ));
616
617 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
618 emit_pre_access(out, pctx);
619
620 out.push_str(&format!(
621 " rusteal_core::ffi_infallible_ctx(unsafe {{ rusteal_core::ffi_dispatch::property_set_object({c}, prop, rusteal_core::ObjectPointer::object_handle(&val)) }}, \"{rust_name}\");\n\
622 \x20 }}\n\n"
623 ));
624}
625
626fn generate_enum_setter(
627 out: &mut String,
628 rust_name: &str,
629 byte_lit: &str,
630 prop_name_len: usize,
631 pctx: &PropertyContext,
632 mapped: &MappedType,
633) {
634 let rust_type = &mapped.rust_type;
635 let c = &pctx.container_expr;
636
637 out.push_str(&format!(
638 " fn set_{rust_name}(&self, val: {rust_type}) {{\n"
639 ));
640
641 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
642 emit_pre_access(out, pctx);
643
644 out.push_str(&format!(
645 " rusteal_core::ffi_infallible_ctx(unsafe {{ rusteal_core::ffi_dispatch::property_set_enum({c}, prop, val as i64) }}, \"{rust_name}\");\n\
646 \x20 }}\n\n"
647 ));
648}
649
650fn generate_fname_setter(
651 out: &mut String,
652 rust_name: &str,
653 byte_lit: &str,
654 prop_name_len: usize,
655 pctx: &PropertyContext,
656) {
657 let c = &pctx.container_expr;
658
659 out.push_str(&format!(
660 " fn set_{rust_name}(&self, val: rusteal_core::FNameHandle) {{\n"
661 ));
662
663 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
664 emit_pre_access(out, pctx);
665
666 out.push_str(&format!(
667 " rusteal_core::ffi_infallible_ctx(unsafe {{ rusteal_core::ffi_dispatch::property_set_fname({c}, prop, val) }}, \"{rust_name}\");\n\
668 \x20 }}\n\n"
669 ));
670}
671
672fn generate_struct_getter(
673 out: &mut String,
674 rust_name: &str,
675 byte_lit: &str,
676 prop_name_len: usize,
677 pctx: &PropertyContext,
678 struct_cpp: &str,
679) {
680 let c = &pctx.container_expr;
681
682 out.push_str(&format!(
683 " fn get_{rust_name}(&self) -> rusteal_core::OwnedStruct<{struct_cpp}> {{\n"
684 ));
685
686 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
687 emit_pre_access(out, pctx);
688
689 out.push_str(&format!(
690 " let size = unsafe {{ rusteal_core::ffi_dispatch::reflection_get_property_size(prop) }} as usize;\n\
691 \x20 let mut buf = vec![0u8; size];\n\
692 \x20 rusteal_core::ffi_infallible_ctx(unsafe {{\n\
693 \x20 rusteal_core::ffi_dispatch::property_get_struct({c}, prop, buf.as_mut_ptr(), size as u32)\n\
694 \x20 }}, \"{rust_name}\");\n\
695 \x20 rusteal_core::OwnedStruct::from_bytes(buf)\n\
696 \x20 }}\n\n"
697 ));
698}
699
700fn generate_struct_setter(
701 out: &mut String,
702 rust_name: &str,
703 byte_lit: &str,
704 prop_name_len: usize,
705 pctx: &PropertyContext,
706 struct_cpp: &str,
707) {
708 let c = &pctx.container_expr;
709
710 out.push_str(&format!(
711 " fn set_{rust_name}(&self, val: &rusteal_core::OwnedStruct<{struct_cpp}>) {{\n"
712 ));
713
714 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
715 emit_pre_access(out, pctx);
716
717 out.push_str(&format!(
718 " let __bytes = val.to_bytes();\n\
719 \x20 rusteal_core::ffi_infallible_ctx(unsafe {{\n\
720 \x20 rusteal_core::ffi_dispatch::property_set_struct({c}, prop, __bytes.as_ptr(), __bytes.len() as u32)\n\
721 \x20 }}, \"{rust_name}\");\n\
722 \x20 }}\n\n"
723 ));
724}
725
726#[allow(clippy::too_many_arguments)]
727fn generate_fixed_array_property(
728 out: &mut String,
729 prop: &PropertyInfo,
730 rust_name: &str,
731 byte_lit: &str,
732 prop_name_len: usize,
733 pctx: &PropertyContext,
734 ctx: &CodegenContext,
735 mapped: &MappedType,
736) {
737 let array_dim = prop.array_dim;
738 let c = &pctx.container_expr;
739
740 let (getter_ret_type, getter_conversion, setter_param_type, setter_conversion) = match mapped
741 .rust_to_ffi
742 {
743 ConversionKind::Identity if mapped.rust_type == "bool" => (
744 "bool".to_string(),
745 "Ok(buf[0] != 0)".to_string(),
746 "bool".to_string(),
747 "let mut buf = vec![0u8; elem_size];\n\
748 \x20 if val { buf[0] = 1; }"
749 .to_string(),
750 ),
751 ConversionKind::Identity | ConversionKind::IntCast => {
752 let rust_type = &mapped.rust_type;
753 let byte_count = rust_type_byte_size(rust_type);
754
755 (
756 rust_type.clone(),
757 format!("Ok({rust_type}::from_ne_bytes(buf[..{byte_count}].try_into().unwrap()))"),
758 rust_type.clone(),
759 "let buf = val.to_ne_bytes().to_vec();".to_string(),
760 )
761 }
762 ConversionKind::ObjectRef => {
763 let rust_type = &mapped.rust_type;
764
765 (
766 rust_type.clone(),
767 "let handle = rusteal_core::UObjectHandle::from_addr(u64::from_ne_bytes(buf[..8].try_into().unwrap()));\n\
768 \x20 Ok(unsafe { rusteal_core::ObjectPointer::from_object_handle(handle) })".to_string(),
769 rust_type.clone(),
770 "let buf = rusteal_core::ObjectPointer::object_handle(&val).to_addr().to_ne_bytes().to_vec();".to_string(),
771 )
772 }
773 ConversionKind::EnumCast => {
774 let rust_type = &mapped.rust_type;
775
776 let actual_repr = prop
777 .enum_name
778 .as_deref()
779 .and_then(|en| ctx.enum_actual_repr(en))
780 .unwrap_or(&mapped.rust_ffi_type);
781
782 let byte_count = rust_type_byte_size(actual_repr);
783
784 (
785 rust_type.clone(),
786 format!(
787 "let raw = {actual_repr}::from_ne_bytes(buf[..{byte_count}].try_into().unwrap());\n\
788 \x20 {rust_type}::from_value(raw).ok_or(rusteal_core::RustealError::TypeMismatch)"
789 ),
790 rust_type.clone(),
791 format!("let buf = (val as {actual_repr}).to_ne_bytes().to_vec();"),
792 )
793 }
794 ConversionKind::StructOpaque => {
795 let struct_cpp = prop
796 .struct_name
797 .as_deref()
798 .and_then(|sn| ctx.structs.get(sn))
799 .map(|si| si.cpp_name.clone())
800 .unwrap_or_else(|| {
801 format!("F{}", prop.struct_name.as_deref().unwrap_or("Unknown"))
802 });
803
804 (
805 format!("rusteal_core::OwnedStruct<{struct_cpp}>"),
806 "Ok(rusteal_core::OwnedStruct::from_bytes(buf))".to_string(),
807 format!("&rusteal_core::OwnedStruct<{struct_cpp}>"),
808 "let buf = val.to_bytes();".to_string(),
809 )
810 }
811 _ => return,
812 };
813
814 out.push_str(&format!(
815 " fn get_{rust_name}(&self, index: u32) -> rusteal_core::RustealResult<{getter_ret_type}> {{\n"
816 ));
817
818 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
819 emit_pre_access(out, pctx);
820
821 out.push_str(&format!(
822 " if index >= {array_dim} {{ return Err(rusteal_core::RustealError::IndexOutOfRange); }}\n\
823 \x20 let elem_size = unsafe {{ rusteal_core::ffi_dispatch::reflection_get_element_size(prop) }} as usize;\n\
824 \x20 let mut buf = vec![0u8; elem_size];\n\
825 \x20 rusteal_core::check_ffi_ctx(unsafe {{\n\
826 \x20 rusteal_core::ffi_dispatch::property_get_property_at({c}, prop, index, buf.as_mut_ptr(), elem_size as u32)\n\
827 \x20 }}, \"{rust_name}\")?;\n\
828 \x20 {getter_conversion}\n\
829 \x20 }}\n\n"
830 ));
831
832 out.push_str(&format!(
833 " fn set_{rust_name}(&self, index: u32, val: {setter_param_type}) -> rusteal_core::RustealResult<()> {{\n"
834 ));
835
836 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
837 emit_pre_access(out, pctx);
838
839 out.push_str(&format!(
840 " if index >= {array_dim} {{ return Err(rusteal_core::RustealError::IndexOutOfRange); }}\n\
841 \x20 let elem_size = unsafe {{ rusteal_core::ffi_dispatch::reflection_get_element_size(prop) }} as usize;\n\
842 \x20 {setter_conversion}\n\
843 \x20 rusteal_core::check_ffi_ctx(unsafe {{\n\
844 \x20 rusteal_core::ffi_dispatch::property_set_property_at({c}, prop, index, buf.as_ptr(), elem_size as u32)\n\
845 \x20 }}, \"{rust_name}\")?;\n\
846 \x20 Ok(())\n\
847 \x20 }}\n\n"
848 ));
849}
850
851fn rust_type_byte_size(rust_type: &str) -> usize {
852 match rust_type {
853 "bool" | "i8" | "u8" => 1,
854 "i16" | "u16" => 2,
855 "i32" | "u32" | "f32" => 4,
856 "i64" | "u64" | "f64" => 8,
857 _ => 8,
858 }
859}
860
861fn generate_container_getter(
862 out: &mut String,
863 rust_name: &str,
864 byte_lit: &str,
865 prop_name_len: usize,
866 pctx: &PropertyContext,
867 container_type: &str,
868) {
869 out.push_str(&format!(
870 " fn {rust_name}(&self) -> {container_type} {{\n"
871 ));
872
873 emit_prop_lookup(out, byte_lit, prop_name_len, pctx);
874 emit_pre_access(out, pctx);
875
876 let turbofish_type = container_type.replace('<', "::<");
877
878 out.push_str(&format!(
879 " {turbofish_type}::new(h, prop)\n\
880 \x20 }}\n\n"
881 ));
882}
883
884#[cfg(test)]
885mod tests {
886 use super::*;
887 use rusteal_ue_flags::{
888 CPF_BLUEPRINT_READ_ONLY, CPF_BLUEPRINT_VISIBLE, CPF_NATIVE_ACCESS_SPECIFIER_PRIVATE,
889 CPF_NATIVE_ACCESS_SPECIFIER_PUBLIC,
890 };
891
892 fn prop(name: &str, prop_type: &str, flags: u64) -> PropertyInfo {
893 serde_json::from_value(serde_json::json!({
894 "name": name,
895 "type": prop_type,
896 "prop_flags": flags,
897 }))
898 .unwrap()
899 }
900
901 #[test]
902 fn only_non_public_read_only_properties_lose_their_setter() {
903 let read_only = CPF_BLUEPRINT_VISIBLE | CPF_BLUEPRINT_READ_ONLY;
904
905 assert!(is_read_only_non_public(&prop(
906 "Mesh",
907 "ObjectProperty",
908 CPF_NATIVE_ACCESS_SPECIFIER_PRIVATE | read_only
909 )));
910
911 assert!(!is_read_only_non_public(&prop(
912 "Mesh",
913 "ObjectProperty",
914 CPF_NATIVE_ACCESS_SPECIFIER_PRIVATE | CPF_BLUEPRINT_VISIBLE
915 )));
916
917 assert!(!is_read_only_non_public(&prop(
918 "Speed",
919 "FloatProperty",
920 CPF_NATIVE_ACCESS_SPECIFIER_PUBLIC | read_only
921 )));
922 }
923
924 #[test]
925 fn accessor_stems() {
926 assert_eq!(
927 rust_property_name(&prop("CharacterMovement", "ObjectProperty", 0)),
928 "character_movement"
929 );
930
931 assert_eq!(
932 rust_property_name(&prop("bUseControllerRotationYaw", "BoolProperty", 0)),
933 "use_controller_rotation_yaw"
934 );
935 }
936}