EM_Task/CoreUObject/Private/UObject/EnumProperty.cpp

498 lines
17 KiB
C++
Raw Permalink Normal View History

2026-02-13 16:18:33 +08:00
// 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 <typename OldIntType>
void ConvertIntToEnumProperty(FStructuredArchive::FSlot Slot, FEnumProperty* EnumProp, FNumericProperty* UnderlyingProp, UEnum* Enum, void* Obj)
{
OldIntType OldValue;
Slot << OldValue;
using LargeIntType = typename TChooseClass<TIsSigned<OldIntType>::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<UEnumProperty>(InField);
Enum = SourceProperty->Enum;
UnderlyingProp = CastField<FNumericProperty>(SourceProperty->UnderlyingProp->GetAssociatedFField());
if (!UnderlyingProp)
{
UnderlyingProp = CastField<FNumericProperty>(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<const FEnumProperty&>(InField);
Enum = Source.Enum;
UnderlyingProp = CastFieldChecked<FNumericProperty>(FField::Duplicate(Source.UnderlyingProp, this));
Super::PostDuplicate(InField);
}
void FEnumProperty::AddCppProperty(FProperty* Inner)
{
check(!UnderlyingProp);
UnderlyingProp = CastFieldChecked<FNumericProperty>(Inner);
check(UnderlyingProp->GetOwner<FEnumProperty>() == 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<uint8>* 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<UObject*>& 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<const FEnumProperty*>(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<FProperty>();
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<void>(Data, Tag.ArrayIndex), (uint64)PreviousValue);
}
else if (Tag.Type == NAME_Int8Property)
{
UE4EnumProperty_Private::ConvertIntToEnumProperty<int8>(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr<void>(Data, Tag.ArrayIndex));
}
else if (Tag.Type == NAME_Int16Property)
{
UE4EnumProperty_Private::ConvertIntToEnumProperty<int16>(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr<void>(Data, Tag.ArrayIndex));
}
else if (Tag.Type == NAME_IntProperty)
{
UE4EnumProperty_Private::ConvertIntToEnumProperty<int32>(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr<void>(Data, Tag.ArrayIndex));
}
else if (Tag.Type == NAME_Int64Property)
{
UE4EnumProperty_Private::ConvertIntToEnumProperty<int64>(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr<void>(Data, Tag.ArrayIndex));
}
else if (Tag.Type == NAME_UInt16Property)
{
UE4EnumProperty_Private::ConvertIntToEnumProperty<uint16>(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr<void>(Data, Tag.ArrayIndex));
}
else if (Tag.Type == NAME_UInt32Property)
{
UE4EnumProperty_Private::ConvertIntToEnumProperty<uint32>(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr<void>(Data, Tag.ArrayIndex));
}
else if (Tag.Type == NAME_UInt64Property)
{
UE4EnumProperty_Private::ConvertIntToEnumProperty<uint64>(Slot, this, UnderlyingProp, Enum, ContainerPtrToValuePtr<void>(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<FField*>& 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"