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