Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 61 additions & 38 deletions crates/codegen/src/unrealcpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,8 @@ impl Lang for UnrealCpp<'_> {
for (param_name, param_type) in &reducer.params_for_generate.elements {
let param_pascal = param_name.deref().to_case(Case::Pascal);
let type_str = cpp_ty_fmt_with_module(module, param_type, self.module_name).to_string();
let field_decl = format!("{type_str} {param_pascal}");
let init_str = cpp_ty_init_fmt_impl(module, param_type);
let field_decl = format!("{type_str} {param_pascal}{init_str}");

// Check if the type is blueprintable
if is_blueprintable(module, param_type) {
Expand Down Expand Up @@ -570,7 +571,8 @@ impl Lang for UnrealCpp<'_> {
for (param_name, param_type) in &reducer.params_for_generate.elements {
let param_pascal = param_name.deref().to_case(Case::Pascal);
let type_str = cpp_ty_fmt_with_module(module, param_type, self.module_name).to_string();
let field_decl = format!("{type_str} {param_pascal}");
let init_str = cpp_ty_init_fmt_impl(module, param_type);
let field_decl = format!("{type_str} {param_pascal}{init_str}");

// Check if the type is blueprintable
if is_blueprintable(module, param_type) {
Expand Down Expand Up @@ -643,7 +645,8 @@ impl Lang for UnrealCpp<'_> {
for (param_name, param_type) in &procedure.params_for_generate.elements {
let param_pascal = param_name.deref().to_case(Case::Pascal);
let type_str = cpp_ty_fmt_with_module(module, param_type, self.module_name).to_string();
let field_decl = format!("{type_str} {param_pascal}");
let init_str = cpp_ty_init_fmt_impl(module, param_type);
let field_decl = format!("{type_str} {param_pascal}{init_str}");

// Check if the type is blueprintable
if is_blueprintable(module, param_type) {
Expand Down Expand Up @@ -4323,7 +4326,7 @@ fn autogen_cpp_struct(
for (orig_name, ty) in product_type.into_iter() {
let field_name = orig_name.deref().to_case(Case::Pascal);
let ty_str = cpp_ty_fmt_with_module(module, ty, module_name).to_string();
let init_str = cpp_ty_init_fmt_impl(ty);
let init_str = cpp_ty_init_fmt_impl(module, ty);
let field_decl = format!("{ty_str} {field_name}{init_str}");

// Check if the type is blueprintable
Expand Down Expand Up @@ -5020,44 +5023,64 @@ fn cpp_ty_fmt_impl<'a>(
})
}

// For UPROPERTY() Unreal expects initialization values for certain types
// (e.g. bools default to true if not explicitly initialized to false).
fn cpp_ty_init_fmt_impl<'a>(ty: &'a AlgebraicTypeUse) -> impl fmt::Display + 'a {
fmt_fn(move |f| match ty {
AlgebraicTypeUse::Primitive(p) => f.write_str(match p {
PrimitiveType::Bool => " = false",
PrimitiveType::I8 => " = 0",
PrimitiveType::U8 => " = 0",
PrimitiveType::I16 => " = 0",
PrimitiveType::U16 => " = 0",
PrimitiveType::I32 => " = 0",
PrimitiveType::U32 => " = 0",
PrimitiveType::I64 => " = 0",
PrimitiveType::U64 => " = 0",
PrimitiveType::F32 => " = 0.0f",
PrimitiveType::F64 => " = 0.0",
PrimitiveType::I128 => "",
PrimitiveType::U128 => "",
PrimitiveType::I256 => "",
PrimitiveType::U256 => "",
}),
AlgebraicTypeUse::Array(_elem) => f.write_str(""),
AlgebraicTypeUse::String => f.write_str(""),
AlgebraicTypeUse::Identity => f.write_str(""),
AlgebraicTypeUse::ConnectionId => f.write_str(""),
AlgebraicTypeUse::Timestamp => f.write_str(""),
AlgebraicTypeUse::TimeDuration => f.write_str(""),
AlgebraicTypeUse::ScheduleAt => f.write_str(""),
AlgebraicTypeUse::Uuid => f.write_str(""),
AlgebraicTypeUse::Unit => f.write_str(""),
// For UPROPERTY() Unreal expects initialization values for certain types.
// UE5 strict mode requires all UPROPERTY fields to be explicitly initialized,
// otherwise the engine logs "property not initialized properly" errors.
// This includes primitives (bool defaults to true if not initialized to false),
// and enum types (must be initialized to a valid enum value).
fn cpp_ty_init_fmt_impl(module: &ModuleDef, ty: &AlgebraicTypeUse) -> String {
match ty {
AlgebraicTypeUse::Primitive(p) => match p {
PrimitiveType::Bool => " = false".to_string(),
PrimitiveType::I8 => " = 0".to_string(),
PrimitiveType::U8 => " = 0".to_string(),
PrimitiveType::I16 => " = 0".to_string(),
PrimitiveType::U16 => " = 0".to_string(),
PrimitiveType::I32 => " = 0".to_string(),
PrimitiveType::U32 => " = 0".to_string(),
PrimitiveType::I64 => " = 0".to_string(),
PrimitiveType::U64 => " = 0".to_string(),
PrimitiveType::F32 => " = 0.0f".to_string(),
PrimitiveType::F64 => " = 0.0".to_string(),
PrimitiveType::I128 => String::new(),
PrimitiveType::U128 => String::new(),
PrimitiveType::I256 => String::new(),
PrimitiveType::U256 => String::new(),
},
AlgebraicTypeUse::Array(_elem) => String::new(),
AlgebraicTypeUse::String => String::new(),
AlgebraicTypeUse::Identity => String::new(),
AlgebraicTypeUse::ConnectionId => String::new(),
AlgebraicTypeUse::Timestamp => String::new(),
AlgebraicTypeUse::TimeDuration => String::new(),
AlgebraicTypeUse::ScheduleAt => String::new(),
AlgebraicTypeUse::Uuid => String::new(),
AlgebraicTypeUse::Unit => String::new(),
// --------- references to user-defined types ---------
AlgebraicTypeUse::Ref(_r) => f.write_str(""),
AlgebraicTypeUse::Ref(r) => {
// Enum types must be initialized to a valid enum value in UE5.
// Use the first variant as the default value.
match &module.typespace_for_generate()[*r] {
AlgebraicTypeDef::PlainEnum(plain_enum) => {
let type_name = type_ref_name(module, *r);
if let Some(first_variant) = plain_enum.variants.first() {
let variant_name = first_variant.deref().to_case(Case::Pascal);
format!(" = E{type_name}Type::{variant_name}")
} else {
String::new()
}
}
// Product and Sum types have proper default constructors
AlgebraicTypeDef::Product(_) => String::new(),
AlgebraicTypeDef::Sum(_) => String::new(),
}
}
// Options use the generated optional types
AlgebraicTypeUse::Option(_inner) => f.write_str(""),
AlgebraicTypeUse::Option(_inner) => String::new(),
// Result use the generated result types
AlgebraicTypeUse::Result { ok_ty: _, err_ty: _ } => f.write_str(""),
AlgebraicTypeUse::Result { ok_ty: _, err_ty: _ } => String::new(),
AlgebraicTypeUse::Never => unreachable!("never type"),
})
}
}

// Given an `AlgebraicTypeUse`, add every referenced type’s generated
Expand Down