EM_Task/CoreUObject/Private/UObject/PropertyByte.cpp

459 lines
16 KiB
C++
Raw Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Class.h"
#include "UObject/PropertyPortFlags.h"
#include "UObject/UnrealType.h"
#include "UObject/UnrealTypePrivate.h"
#include "UObject/UObjectThreadContext.h"
#include "Serialization/ArchiveUObjectFromStructuredArchive.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"
/*-----------------------------------------------------------------------------
FByteProperty.
-----------------------------------------------------------------------------*/
IMPLEMENT_FIELD(FByteProperty)
#if WITH_EDITORONLY_DATA
FByteProperty::FByteProperty(UField* InField)
: TProperty_Numeric(InField)
{
UByteProperty* SourceProperty = CastChecked<UByteProperty>(InField);
Enum = SourceProperty->Enum;
}
#endif // WITH_EDITORONLY_DATA
void FByteProperty::GetPreloadDependencies(TArray<UObject*>& OutDeps)
{
Super::GetPreloadDependencies(OutDeps);
OutDeps.Add(Enum);
}
void FByteProperty::SerializeItem(FStructuredArchive::FSlot Slot, void* Value, void const* Defaults) const
{
FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive();
if (Enum && UnderlyingArchive.UseToResolveEnumerators())
{
Slot.EnterStream();
const int64 ResolvedIndex = Enum->ResolveEnumerator(UnderlyingArchive, *(uint8*)Value);
*(uint8*)Value = static_cast<uint8>(ResolvedIndex);
return;
}
// Serialize enum values by name unless we're not saving or loading OR for backwards compatibility
const bool bUseBinarySerialization = (Enum == NULL) || (!UnderlyingArchive.IsLoading() && !UnderlyingArchive.IsSaving());
if (bUseBinarySerialization)
{
Super::SerializeItem(Slot, Value, Defaults);
}
// Loading
else if (UnderlyingArchive.IsLoading())
{
FName EnumValueName;
Slot << EnumValueName;
// 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.
int32 EnumIndex = Enum->GetIndexByName(EnumValueName, EGetByNameFlags::ErrorIfNotFound);
if (EnumIndex == INDEX_NONE)
{
*(uint8*)Value = Enum->GetMaxEnumValue();
}
else
{
*(uint8*)Value = Enum->GetValueByIndex(EnumIndex);
}
}
// Saving
else
{
FName EnumValueName;
uint8 ByteValue = *(uint8*)Value;
// subtract 1 because the last entry in the enum's Names array
// is the _MAX entry
if (Enum->IsValidEnumValue(ByteValue))
{
EnumValueName = Enum->GetNameByValue(ByteValue);
}
else
{
EnumValueName = NAME_None;
}
Slot << EnumValueName;
}
}
bool FByteProperty::NetSerializeItem(FArchive& Ar, UPackageMap* Map, void* Data, TArray<uint8>* MetaData) const
{
if (Ar.EngineNetVer() < HISTORY_ENUM_SERIALIZATION_COMPAT)
{
Ar.SerializeBits(Data, Enum ? FMath::CeilLogTwo(Enum->GetMaxEnumValue()) : 8);
}
else
{
Ar.SerializeBits(Data, GetMaxNetSerializeBits());
}
return true;
}
void FByteProperty::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar << Enum;
if (Enum != nullptr)
{
Ar.Preload(Enum);
}
}
void FByteProperty::PostDuplicate(const FField& InField)
{
const FByteProperty& Source = static_cast<const FByteProperty&>(InField);
Enum = Source.Enum;
Super::PostDuplicate(InField);
}
void FByteProperty::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObject(Enum, nullptr);
Super::AddReferencedObjects(Collector);
}
FString FByteProperty::GetCPPType(FString* ExtendedTypeText /*=NULL*/, uint32 CPPExportFlags /*=0*/) const
{
if (Enum)
{
const bool bEnumClassForm = Enum->GetCppForm() == UEnum::ECppForm::EnumClass;
const bool bNonNativeEnum = Enum->GetClass() != UEnum::StaticClass(); // cannot use RF_Native flag, because in UHT the flag is not set
const bool bRawParam = (CPPExportFlags & CPPF_ArgumentOrReturnValue) && (((PropertyFlags & CPF_ReturnParm) || !(PropertyFlags & CPF_OutParm)) || bNonNativeEnum);
const bool bConvertedCode = (CPPExportFlags & CPPF_BlueprintCppBackend) && bNonNativeEnum;
FString FullyQualifiedEnumName;
if (!Enum->CppType.IsEmpty())
{
FullyQualifiedEnumName = Enum->CppType;
}
else
{
// 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());
FullyQualifiedEnumName = ::UnicodeToCPPIdentifier(Enum->GetName(), false, TEXT("E__"));
}
else
{
FullyQualifiedEnumName = Enum->GetName();
}
}
if (bEnumClassForm || bRawParam || bConvertedCode)
{
return FullyQualifiedEnumName;
}
else
{
return FString::Printf(TEXT("TEnumAsByte<%s>"), *FullyQualifiedEnumName);
}
}
return Super::GetCPPType(ExtendedTypeText, CPPExportFlags);
}
template <typename OldIntType>
struct TConvertIntToEnumProperty
{
static void Convert(FStructuredArchive::FSlot Slot, FByteProperty* Property, UEnum* Enum, void* Obj, const FPropertyTag& Tag)
{
OldIntType OldValue;
Slot << OldValue;
uint8 NewValue = OldValue;
if (OldValue > (OldIntType)TNumericLimits<uint8>::Max() || !Enum->IsValidEnumValue(NewValue))
{
UE_LOG(
LogClass,
Warning,
TEXT("Failed to find valid enum value '%d' for enum type '%s' when converting property '%s' during property loading - setting to '%s'"),
OldValue,
*Enum->GetName(),
*Property->GetName(),
*Enum->GetNameByValue(Enum->GetMaxEnumValue()).ToString());
NewValue = Enum->GetMaxEnumValue();
}
Property->SetPropertyValue_InContainer(Obj, NewValue, Tag.ArrayIndex);
}
};
EConvertFromTypeResult FByteProperty::ConvertFromType(const FPropertyTag& Tag, FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct)
{
if (Tag.Type == NAME_ByteProperty && ((Tag.EnumName == NAME_None) != (Enum == nullptr)))
{
// a byte property gained or lost an enum
// attempt to convert it
uint8 PreviousValue = 0;
if (Tag.EnumName == NAME_None)
{
// If we're a nested property the EnumName tag got lost. Fail to read in this case
FProperty* const PropertyOwner = GetOwner<FProperty>();
if (PropertyOwner)
{
return EConvertFromTypeResult::UseSerializeItem;
}
// simply pretend the property still doesn't have an enum and serialize the single byte
Slot << PreviousValue;
}
else
{
// attempt to find the old enum and get the byte value from the serialized enum name
PreviousValue = (uint8)ReadEnumAsInt64(Slot, DefaultsStruct, Tag);
}
// now copy the value into the object's address space
SetPropertyValue_InContainer(Data, PreviousValue, Tag.ArrayIndex);
}
else if (Tag.Type == NAME_EnumProperty && (Enum == nullptr || Tag.EnumName == Enum->GetFName()))
{
// an enum property became a byte
// attempt to find the old enum and get the byte value from the serialized enum name
uint8 PreviousValue = (uint8)ReadEnumAsInt64(Slot, DefaultsStruct, Tag);
// now copy the value into the object's address space
SetPropertyValue_InContainer(Data, PreviousValue, Tag.ArrayIndex);
}
else if (Tag.Type == NAME_Int8Property)
{
if (Enum)
{
TConvertIntToEnumProperty<int8>::Convert(Slot, this, Enum, Data, Tag);
}
else
{
ConvertFromArithmeticValue<int8>(Slot, Data, Tag);
}
}
else if (Tag.Type == NAME_Int16Property)
{
if (Enum)
{
TConvertIntToEnumProperty<int16>::Convert(Slot, this, Enum, Data, Tag);
}
else
{
ConvertFromArithmeticValue<int16>(Slot, Data, Tag);
}
}
else if (Tag.Type == NAME_IntProperty)
{
if (Enum)
{
TConvertIntToEnumProperty<int32>::Convert(Slot, this, Enum, Data, Tag);
}
else
{
ConvertFromArithmeticValue<int32>(Slot, Data, Tag);
}
}
else if (Tag.Type == NAME_Int64Property)
{
if (Enum)
{
TConvertIntToEnumProperty<int64>::Convert(Slot, this, Enum, Data, Tag);
}
else
{
ConvertFromArithmeticValue<int64>(Slot, Data, Tag);
}
}
else if (Tag.Type == NAME_UInt16Property)
{
if (Enum)
{
TConvertIntToEnumProperty<uint16>::Convert(Slot, this, Enum, Data, Tag);
}
else
{
ConvertFromArithmeticValue<uint16>(Slot, Data, Tag);
}
}
else if (Tag.Type == NAME_UInt32Property)
{
if (Enum)
{
TConvertIntToEnumProperty<uint32>::Convert(Slot, this, Enum, Data, Tag);
}
else
{
ConvertFromArithmeticValue<uint32>(Slot, Data, Tag);
}
}
else if (Tag.Type == NAME_UInt64Property)
{
if (Enum)
{
TConvertIntToEnumProperty<uint64>::Convert(Slot, this, Enum, Data, Tag);
}
else
{
ConvertFromArithmeticValue<uint64>(Slot, Data, Tag);
}
}
else
{
return EConvertFromTypeResult::UseSerializeItem;
}
return EConvertFromTypeResult::Converted;
}
void FByteProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, const void* DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const
{
if (0 != (PortFlags & PPF_ExportCpp))
{
if (Enum)
{
const int32 ActualValue = *(const uint8*)PropertyValue;
const int32 MaxValue = Enum->GetMaxEnumValue();
const int32 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)(%d)"), *FullyQualifiedEnumName, ActualValue);
}
else
{
ValueStr += FString::Printf(TEXT("%s::%s"), *FullyQualifiedEnumName,
*Enum->GetNameStringByValue(GoodValue));
}
}
else
{
Super::ExportTextItem(ValueStr, PropertyValue, DefaultValue, Parent, PortFlags, ExportRootScope);
}
return;
}
if (Enum && (PortFlags & PPF_ConsoleVariable) == 0)
{
// 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)
bool bIsValid = Enum->IsValidEnumValue(*(const uint8*)PropertyValue);
bool bIsMax = *(const uint8*)PropertyValue == Enum->GetMaxEnumValue();
if (bIsValid && (!bIsMax || (PortFlags & PPF_Copy)))
{
// 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(*(const uint8*)PropertyValue).ToString();
}
else if (PortFlags & PPF_ExternalEditor)
{
ValueStr += Enum->GetAuthoredNameStringByValue(*(const uint8*)PropertyValue);
}
else
{
ValueStr += Enum->GetNameStringByValue(*(const uint8*)PropertyValue);
}
}
else
{
ValueStr += TEXT("(INVALID)");
}
}
else
{
Super::ExportTextItem(ValueStr, PropertyValue, DefaultValue, Parent, PortFlags, ExportRootScope);
}
}
const TCHAR* FByteProperty::ImportText_Internal(const TCHAR* InBuffer, void* Data, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText) const
{
if (Enum && (PortFlags & PPF_ConsoleVariable) == 0)
{
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)
{
*(uint8*)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;
}
}
// Interpret "True" and "False" as 1 and 0. This is mostly for importing a property that was exported as a bool and is imported as a non-enum byte.
if (!Enum)
{
FString Temp;
if (const TCHAR* Buffer = FPropertyHelpers::ReadToken(InBuffer, Temp))
{
const FCoreTexts& CoreTexts = FCoreTexts::Get();
if (Temp == TEXT("True") || Temp == *(CoreTexts.True.ToString()))
{
SetIntPropertyValue(Data, 1ull);
return Buffer;
}
else if (Temp == TEXT("False") || Temp == *(CoreTexts.False.ToString()))
{
SetIntPropertyValue(Data, 0ull);
return Buffer;
}
}
}
return Super::ImportText_Internal(InBuffer, Data, PortFlags, Parent, ErrorText);
}
UEnum* FByteProperty::GetIntPropertyEnum() const
{
return Enum;
}
uint64 FByteProperty::GetMaxNetSerializeBits() const
{
const uint64 MaxBits = 8;
const uint64 DesiredBits = Enum ? FMath::CeilLogTwo64(Enum->GetMaxEnumValue() + 1) : MaxBits;
return FMath::Min(DesiredBits, MaxBits);
}
#include "UObject/DefineUPropertyMacros.h"