// Copyright Epic Games, Inc. All Rights Reserved. #include "UObject/EnumProperty.h" #include "UObject/PropertyPortFlags.h" #include "UObject/UObjectThreadContext.h" #include "UObject/PropertyTag.h" #include "UObject/UnrealTypePrivate.h" #include "Templates/ChooseClass.h" #include "Templates/IsSigned.h" #include "Algo/Find.h" #include "UObject/LinkerLoad.h" #include "Misc/NetworkVersion.h" // WARNING: This should always be the last include in any file that needs it (except .generated.h) #include "UObject/UndefineUPropertyMacros.h" namespace UE4EnumProperty_Private { template void ConvertIntToEnumProperty(FStructuredArchive::FSlot Slot, FEnumProperty* EnumProp, FNumericProperty* UnderlyingProp, UEnum* Enum, void* Obj) { OldIntType OldValue; Slot << OldValue; using LargeIntType = typename TChooseClass::Value, int64, uint64>::Result; LargeIntType NewValue = OldValue; if (!UnderlyingProp->CanHoldValue(NewValue) || !Enum->IsValidEnumValue(NewValue)) { UE_LOG( LogClass, Warning, TEXT("Failed to find valid enum value '%s' for enum type '%s' when converting property '%s' during property loading - setting to '%s'"), *LexToString(OldValue), *Enum->GetName(), *EnumProp->GetName(), *Enum->GetNameByValue(Enum->GetMaxEnumValue()).ToString()); NewValue = Enum->GetMaxEnumValue(); } UnderlyingProp->SetIntPropertyValue(Obj, NewValue); } } // namespace UE4EnumProperty_Private IMPLEMENT_FIELD(FEnumProperty) FEnumProperty::FEnumProperty(FFieldVariant InOwner, const FName& InName, EObjectFlags InObjectFlags) : FProperty(InOwner, InName, InObjectFlags), UnderlyingProp(nullptr), Enum(nullptr) { } FEnumProperty::FEnumProperty(FFieldVariant InOwner, const FName& InName, EObjectFlags InObjectFlags, UEnum* InEnum) : FProperty(InOwner, InName, InObjectFlags, 0, CPF_HasGetValueTypeHash), Enum(InEnum) { // This is expected to be set post-construction by AddCppProperty UnderlyingProp = nullptr; } FEnumProperty::FEnumProperty(FFieldVariant InOwner, const FName& InName, EObjectFlags InObjectFlags, int32 InOffset, EPropertyFlags InFlags, UEnum* InEnum) : FProperty(InOwner, InName, InObjectFlags, InOffset, InFlags | CPF_HasGetValueTypeHash), Enum(InEnum) { // This is expected to be set post-construction by AddCppProperty UnderlyingProp = nullptr; } #if WITH_EDITORONLY_DATA FEnumProperty::FEnumProperty(UField* InField) : FProperty(InField) { UEnumProperty* SourceProperty = CastChecked(InField); Enum = SourceProperty->Enum; UnderlyingProp = CastField(SourceProperty->UnderlyingProp->GetAssociatedFField()); if (!UnderlyingProp) { UnderlyingProp = CastField(CreateFromUField(SourceProperty->UnderlyingProp)); SourceProperty->UnderlyingProp->SetAssociatedFField(UnderlyingProp); } } #endif // WITH_EDITORONLY_DATA FEnumProperty::~FEnumProperty() { delete UnderlyingProp; UnderlyingProp = nullptr; } void FEnumProperty::PostDuplicate(const FField& InField) { const FEnumProperty& Source = static_cast(InField); Enum = Source.Enum; UnderlyingProp = CastFieldChecked(FField::Duplicate(Source.UnderlyingProp, this)); Super::PostDuplicate(InField); } void FEnumProperty::AddCppProperty(FProperty* Inner) { check(!UnderlyingProp); UnderlyingProp = CastFieldChecked(Inner); check(UnderlyingProp->GetOwner() == this); if (UnderlyingProp->HasAnyPropertyFlags(CPF_HasGetValueTypeHash)) { PropertyFlags |= CPF_HasGetValueTypeHash; } } void FEnumProperty::SerializeItem(FStructuredArchive::FSlot Slot, void* Value, void const* Defaults) const { FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive(); check(UnderlyingProp); if (Enum && UnderlyingArchive.UseToResolveEnumerators()) { Slot.EnterStream(); int64 IntValue = UnderlyingProp->GetSignedIntPropertyValue(Value); int64 ResolvedIndex = Enum->ResolveEnumerator(UnderlyingArchive, IntValue); UnderlyingProp->SetIntPropertyValue(Value, ResolvedIndex); return; } // Loading if (UnderlyingArchive.IsLoading()) { FName EnumValueName; Slot << EnumValueName; int64 NewEnumValue = 0; if (Enum) { // Make sure enum is properly populated if (Enum->HasAnyFlags(RF_NeedLoad)) { UnderlyingArchive.Preload(Enum); } // There's no guarantee EnumValueName is still present in Enum, in which case Value will be set to the enum's max value. // On save, it will then be serialized as NAME_None. const int32 EnumIndex = Enum->GetIndexByName(EnumValueName, EGetByNameFlags::ErrorIfNotFound); if (EnumIndex == INDEX_NONE) { NewEnumValue = Enum->GetMaxEnumValue(); } else { NewEnumValue = Enum->GetValueByIndex(EnumIndex); } } UnderlyingProp->SetIntPropertyValue(Value, NewEnumValue); } // Saving else if (UnderlyingArchive.IsSaving()) { FName EnumValueName; if (Enum) { const int64 IntValue = UnderlyingProp->GetSignedIntPropertyValue(Value); if (Enum->IsValidEnumValue(IntValue)) { EnumValueName = Enum->GetNameByValue(IntValue); } } Slot << EnumValueName; } else { UnderlyingProp->SerializeItem(Slot, Value, Defaults); } } bool FEnumProperty::NetSerializeItem(FArchive& Ar, UPackageMap* Map, void* Data, TArray* MetaData) const { if (Ar.EngineNetVer() < HISTORY_FIX_ENUM_SERIALIZATION) { Ar.SerializeBits(Data, FMath::CeilLogTwo64(Enum->GetMaxEnumValue())); } else { Ar.SerializeBits(Data, GetMaxNetSerializeBits()); } return true; } void FEnumProperty::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar << Enum; if (Enum != nullptr) { Ar.Preload(Enum); } SerializeSingleField(Ar, UnderlyingProp, this); } void FEnumProperty::AddReferencedObjects(FReferenceCollector& Collector) { Collector.AddReferencedObject(Enum); Super::AddReferencedObjects(Collector); } FString FEnumProperty::GetCPPType(FString* ExtendedTypeText, uint32 CPPExportFlags) const { check(Enum); check(UnderlyingProp); const bool bNonNativeEnum = Enum->GetClass() != UEnum::StaticClass(); // cannot use RF_Native flag, because in UHT the flag is not set if (!Enum->CppType.IsEmpty()) { return Enum->CppType; } FString EnumName = Enum->GetName(); // This would give the wrong result if it's a namespaced type and the CppType hasn't // been set, but we do this here in case existing code relies on it... somehow. if ((CPPExportFlags & CPPF_BlueprintCppBackend) && bNonNativeEnum) { ensure(Enum->CppType.IsEmpty()); FString Result = ::UnicodeToCPPIdentifier(EnumName, false, TEXT("E__")); return Result; } return EnumName; } void FEnumProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, const void* DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const { if (Enum == nullptr) { UE_LOG( LogClass, Warning, TEXT("Member 'Enum' of %s is nullptr, export operation would fail. This can occur when the enum class has been moved or deleted."), *GetFullName()); return; } check(UnderlyingProp); FNumericProperty* LocalUnderlyingProp = UnderlyingProp; if (PortFlags & PPF_ExportCpp) { const int64 ActualValue = LocalUnderlyingProp->GetSignedIntPropertyValue(PropertyValue); const int64 MaxValue = Enum->GetMaxEnumValue(); const int64 GoodValue = Enum->IsValidEnumValue(ActualValue) ? ActualValue : MaxValue; const bool bNonNativeEnum = Enum->GetClass() != UEnum::StaticClass(); ensure(!bNonNativeEnum || Enum->CppType.IsEmpty()); const FString FullyQualifiedEnumName = bNonNativeEnum ? ::UnicodeToCPPIdentifier(Enum->GetName(), false, TEXT("E__")) : (Enum->CppType.IsEmpty() ? Enum->GetName() : Enum->CppType); if (GoodValue == MaxValue) { // not all native enums have Max value declared ValueStr += FString::Printf(TEXT("(%s)(%ull)"), *FullyQualifiedEnumName, ActualValue); } else { ValueStr += FString::Printf(TEXT("%s::%s"), *FullyQualifiedEnumName, *Enum->GetNameStringByValue(GoodValue)); } return; } if (PortFlags & PPF_ConsoleVariable) { UnderlyingProp->ExportTextItem(ValueStr, PropertyValue, DefaultValue, Parent, PortFlags, ExportRootScope); return; } int64 Value = LocalUnderlyingProp->GetSignedIntPropertyValue(PropertyValue); // if the value is the max value (the autogenerated *_MAX value), export as "INVALID", unless we're exporting text for copy/paste (for copy/paste, // the property text value must actually match an entry in the enum's names array) if (!Enum->IsValidEnumValue(Value) || (!(PortFlags & PPF_Copy) && Value == Enum->GetMaxEnumValue())) { ValueStr += TEXT("(INVALID)"); return; } // We do not want to export the enum text for non-display uses, localization text is very dynamic and would cause issues on import if (PortFlags & PPF_PropertyWindow) { ValueStr += Enum->GetDisplayNameTextByValue(Value).ToString(); } else if (PortFlags & PPF_ExternalEditor) { ValueStr += Enum->GetAuthoredNameStringByValue(Value); } else { ValueStr += Enum->GetNameStringByValue(Value); } } const TCHAR* FEnumProperty::ImportText_Internal(const TCHAR* InBuffer, void* Data, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText) const { check(Enum); check(UnderlyingProp); if (!(PortFlags & PPF_ConsoleVariable)) { FString Temp; if (const TCHAR* Buffer = FPropertyHelpers::ReadToken(InBuffer, Temp, true)) { int32 EnumIndex = Enum->GetIndexByName(*Temp, EGetByNameFlags::CheckAuthoredName); if (EnumIndex == INDEX_NONE && (Temp.IsNumeric() && !Algo::Find(Temp, TEXT('.')))) { int64 EnumValue = INDEX_NONE; LexFromString(EnumValue, *Temp); EnumIndex = Enum->GetIndexByValue(EnumValue); } if (EnumIndex != INDEX_NONE) { UnderlyingProp->SetIntPropertyValue(Data, Enum->GetValueByIndex(EnumIndex)); return Buffer; } // Enum could not be created from value. This indicates a bad value so // return null so that the caller of ImportText can generate a more meaningful // warning/error UObject* SerializedObject = nullptr; if (FLinkerLoad* Linker = GetLinker()) { if (FUObjectSerializeContext* LoadContext = Linker->GetSerializeContext()) { SerializedObject = LoadContext->SerializedObject; } } UE_LOG(LogClass, Warning, TEXT("In asset '%s', there is an enum property of type '%s' with an invalid value of '%s'"), *GetPathNameSafe(SerializedObject ? SerializedObject : FUObjectThreadContext::Get().ConstructedObject), *Enum->GetName(), *Temp); return nullptr; } } const TCHAR* Result = UnderlyingProp->ImportText(InBuffer, Data, PortFlags, Parent, ErrorText); return Result; } FString FEnumProperty::GetCPPMacroType(FString& ExtendedTypeText) const { ExtendedTypeText = Enum->GetName(); return TEXT("ENUM"); } FString FEnumProperty::GetCPPTypeForwardDeclaration() const { check(Enum); check(Enum->GetCppForm() == UEnum::ECppForm::EnumClass); return FString::Printf(TEXT("enum class %s : %s;"), *Enum->GetName(), *UnderlyingProp->GetCPPType()); } void FEnumProperty::GetPreloadDependencies(TArray& OutDeps) { Super::GetPreloadDependencies(OutDeps); // OutDeps.Add(UnderlyingProp); OutDeps.Add(Enum); } void FEnumProperty::LinkInternal(FArchive& Ar) { check(UnderlyingProp); UnderlyingProp->Link(Ar); this->ElementSize = UnderlyingProp->ElementSize; this->PropertyFlags |= CPF_IsPlainOldData | CPF_NoDestructor | CPF_ZeroConstructor; PropertyFlags |= (UnderlyingProp->PropertyFlags & CPF_HasGetValueTypeHash); } bool FEnumProperty::Identical(const void* A, const void* B, uint32 PortFlags) const { return UnderlyingProp->Identical(A, B, PortFlags); } int32 FEnumProperty::GetMinAlignment() const { return UnderlyingProp->GetMinAlignment(); } bool FEnumProperty::SameType(const FProperty* Other) const { return Super::SameType(Other) && static_cast(Other)->Enum == Enum; } EConvertFromTypeResult FEnumProperty::ConvertFromType(const FPropertyTag& Tag, FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct) { if ((Enum == nullptr) || (UnderlyingProp == nullptr)) { return EConvertFromTypeResult::UseSerializeItem; } if (Tag.Type == NAME_ByteProperty) { uint8 PreviousValue = 0; if (Tag.EnumName == NAME_None) { // If we're a nested property the EnumName tag got lost. Handle this case for backward compatibility reasons FProperty* const PropertyOwner = GetOwner(); if (PropertyOwner) { FPropertyTag InnerPropertyTag; InnerPropertyTag.Type = Tag.Type; InnerPropertyTag.EnumName = Enum->GetFName(); InnerPropertyTag.ArrayIndex = 0; PreviousValue = (uint8)FNumericProperty::ReadEnumAsInt64(Slot, DefaultsStruct, InnerPropertyTag); } else { // a byte property gained an enum Slot << PreviousValue; } } else { PreviousValue = (uint8)FNumericProperty::ReadEnumAsInt64(Slot, DefaultsStruct, Tag); } // now copy the value into the object's address space UnderlyingProp->SetIntPropertyValue(ContainerPtrToValuePtr(Data, Tag.ArrayIndex), (uint64)PreviousValue); } else if (Tag.Type == NAME_Int8Property) { UE4EnumProperty_Private::ConvertIntToEnumProperty(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr(Data, Tag.ArrayIndex)); } else if (Tag.Type == NAME_Int16Property) { UE4EnumProperty_Private::ConvertIntToEnumProperty(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr(Data, Tag.ArrayIndex)); } else if (Tag.Type == NAME_IntProperty) { UE4EnumProperty_Private::ConvertIntToEnumProperty(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr(Data, Tag.ArrayIndex)); } else if (Tag.Type == NAME_Int64Property) { UE4EnumProperty_Private::ConvertIntToEnumProperty(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr(Data, Tag.ArrayIndex)); } else if (Tag.Type == NAME_UInt16Property) { UE4EnumProperty_Private::ConvertIntToEnumProperty(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr(Data, Tag.ArrayIndex)); } else if (Tag.Type == NAME_UInt32Property) { UE4EnumProperty_Private::ConvertIntToEnumProperty(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr(Data, Tag.ArrayIndex)); } else if (Tag.Type == NAME_UInt64Property) { UE4EnumProperty_Private::ConvertIntToEnumProperty(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr(Data, Tag.ArrayIndex)); } else { return EConvertFromTypeResult::UseSerializeItem; } return EConvertFromTypeResult::Converted; } uint32 FEnumProperty::GetValueTypeHashInternal(const void* Src) const { check(UnderlyingProp); return UnderlyingProp->GetValueTypeHash(Src); } FField* FEnumProperty::GetInnerFieldByName(const FName& InName) { if (UnderlyingProp && UnderlyingProp->GetFName() == InName) { return UnderlyingProp; } return nullptr; } void FEnumProperty::GetInnerFields(TArray& OutFields) { if (UnderlyingProp) { OutFields.Add(UnderlyingProp); UnderlyingProp->GetInnerFields(OutFields); } } uint64 FEnumProperty::GetMaxNetSerializeBits() const { const uint64 MaxBits = ElementSize * 8; const uint64 DesiredBits = FMath::CeilLogTwo64(Enum->GetMaxEnumValue() + 1); return FMath::Min(DesiredBits, MaxBits); } #include "UObject/DefineUPropertyMacros.h"