EM_Task/CoreUObject/Private/UObject/PropertyArray.cpp

776 lines
27 KiB
C++
Raw Permalink 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 "Templates/Casts.h"
#include "UObject/PropertyTag.h"
#include "UObject/UnrealType.h"
#include "UObject/UnrealTypePrivate.h"
#include "UObject/LinkerLoad.h"
#include "UObject/PropertyHelper.h"
// WARNING: This should always be the last include in any file that needs it (except .generated.h)
#include "UObject/UndefineUPropertyMacros.h"
/*-----------------------------------------------------------------------------
FArrayProperty.
-----------------------------------------------------------------------------*/
IMPLEMENT_FIELD(FArrayProperty)
#if WITH_EDITORONLY_DATA
FArrayProperty::FArrayProperty(UField* InField)
: FArrayProperty_Super(InField), ArrayFlags(EArrayPropertyFlags::None)
{
UArrayProperty* SourceProperty = CastChecked<UArrayProperty>(InField);
Inner = CastField<FProperty>(SourceProperty->Inner->GetAssociatedFField());
if (!Inner)
{
Inner = CastField<FProperty>(CreateFromUField(SourceProperty->Inner));
SourceProperty->Inner->SetAssociatedFField(Inner);
}
}
#endif // WITH_EDITORONLY_DATA
FArrayProperty::~FArrayProperty()
{
delete Inner;
Inner = nullptr;
}
void FArrayProperty::GetPreloadDependencies(TArray<UObject*>& OutDeps)
{
Super::GetPreloadDependencies(OutDeps);
if (Inner)
{
Inner->GetPreloadDependencies(OutDeps);
}
}
void FArrayProperty::PostDuplicate(const FField& InField)
{
const FArrayProperty& Source = static_cast<const FArrayProperty&>(InField);
Inner = CastFieldChecked<FProperty>(FField::Duplicate(Source.Inner, this));
Super::PostDuplicate(InField);
}
void FArrayProperty::LinkInternal(FArchive& Ar)
{
// FLinkerLoad* MyLinker = GetLinker();
// if( MyLinker )
//{
// MyLinker->Preload(this);
// }
// Ar.Preload(Inner);
Inner->Link(Ar);
SetElementSize();
}
bool FArrayProperty::Identical(const void* A, const void* B, uint32 PortFlags) const
{
checkSlow(Inner);
FScriptArrayHelper ArrayHelperA(this, A);
const int32 ArrayNum = ArrayHelperA.Num();
if (B == NULL)
{
return ArrayNum == 0;
}
FScriptArrayHelper ArrayHelperB(this, B);
if (ArrayNum != ArrayHelperB.Num())
{
return false;
}
for (int32 ArrayIndex = 0; ArrayIndex < ArrayNum; ArrayIndex++)
{
if (!Inner->Identical(ArrayHelperA.GetRawPtr(ArrayIndex), ArrayHelperB.GetRawPtr(ArrayIndex), PortFlags))
{
return false;
}
}
return true;
}
static bool CanBulkSerialize(FProperty* Property)
{
#if PLATFORM_LITTLE_ENDIAN
// All numeric properties except TEnumAsByte
uint64 CastFlags = Property->GetClass()->GetCastFlags();
if (!!(CastFlags & CASTCLASS_FNumericProperty))
{
bool bEnumAsByte = (CastFlags & CASTCLASS_FByteProperty) != 0 && static_cast<FByteProperty*>(Property)->Enum;
return !bEnumAsByte;
}
#endif
return false;
}
void FArrayProperty::SerializeItem(FStructuredArchive::FSlot Slot, void* Value, void const* Defaults) const
{
check(Inner);
FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive();
const bool bIsTextFormat = UnderlyingArchive.IsTextFormat();
const bool bUPS = Slot.GetArchiveState().UseUnversionedPropertySerialization();
TOptional<FPropertyTag> MaybeInnerTag;
// Ensure that the Inner itself has been loaded before calling SerializeItem() on it
// UnderlyingArchive.Preload(Inner);
FScriptArrayHelper ArrayHelper(this, Value);
int32 n = ArrayHelper.Num();
// Custom branch for UPS to try and take advantage of bulk serialization
if (bUPS)
{
checkf(!UnderlyingArchive.ArUseCustomPropertyList, TEXT("Custom property lists are not supported with UPS"));
checkf(!bIsTextFormat, TEXT("Text-based archives are not supported with UPS"));
if (CanBulkSerialize(Inner))
{
// We need to enter the slot as *something* to keep the structured archive system happy,
// but which maps down to straight writes to the underlying archive.
FStructuredArchiveStream Stream = Slot.EnterStream();
Stream.EnterElement() << n;
if (UnderlyingArchive.IsLoading())
{
ArrayHelper.EmptyAndAddUninitializedValues(n);
}
Stream.EnterElement().Serialize(ArrayHelper.GetRawPtr(), n * Inner->ElementSize);
}
else
{
FStructuredArchiveArray Array = Slot.EnterArray(n);
if (UnderlyingArchive.IsLoading())
{
ArrayHelper.EmptyAndAddValues(n);
}
FSerializedPropertyScope SerializedProperty(UnderlyingArchive, Inner, this);
for (int32 i = 0; i < n; ++i)
{
#if WITH_EDITOR
static const FName NAME_UArraySerialize = FName(TEXT("FArrayProperty::Serialize"));
FName NAME_UArraySerializeCount = FName(NAME_UArraySerialize);
NAME_UArraySerializeCount.SetNumber(i);
FArchive::FScopeAddDebugData P(UnderlyingArchive, NAME_UArraySerializeCount);
#endif
Inner->SerializeItem(Array.EnterElement(), ArrayHelper.GetRawPtr(i));
}
}
return;
}
if (bIsTextFormat && Inner->IsA<FStructProperty>())
{
MaybeInnerTag.Emplace(UnderlyingArchive, Inner, 0, (uint8*)Value, (uint8*)Defaults);
Slot << SA_ATTRIBUTE(TEXT("InnerStructName"), MaybeInnerTag.GetValue().StructName);
Slot << SA_OPTIONAL_ATTRIBUTE(TEXT("InnerStructGuid"), MaybeInnerTag.GetValue().StructGuid, FGuid());
}
FStructuredArchiveArray Array = Slot.EnterArray(n);
if (UnderlyingArchive.IsLoading())
{
// If using a custom property list, don't empty the array on load. Not all indices may have been serialized, so we need to preserve existing values at those slots.
if (UnderlyingArchive.ArUseCustomPropertyList)
{
const int32 OldNum = ArrayHelper.Num();
if (n > OldNum)
{
ArrayHelper.AddValues(n - OldNum);
}
else if (n < OldNum)
{
ArrayHelper.RemoveValues(n, OldNum - n);
}
}
else
{
ArrayHelper.EmptyAndAddValues(n);
}
}
ArrayHelper.CountBytes(UnderlyingArchive);
// Serialize a PropertyTag for the inner property of this array, allows us to validate the inner struct to see if it has changed
if (UnderlyingArchive.UE4Ver() >= VER_UE4_INNER_ARRAY_TAG_INFO && Inner->IsA<FStructProperty>())
{
if (!MaybeInnerTag)
{
MaybeInnerTag.Emplace(UnderlyingArchive, Inner, 0, (uint8*)Value, (uint8*)Defaults);
UnderlyingArchive << MaybeInnerTag.GetValue();
}
FPropertyTag& InnerTag = MaybeInnerTag.GetValue();
if (UnderlyingArchive.IsLoading())
{
auto CanSerializeFromStructWithDifferentName = [](const FPropertyTag& PropertyTag, const FStructProperty* StructProperty)
{
return PropertyTag.StructGuid.IsValid() && StructProperty && StructProperty->Struct && (PropertyTag.StructGuid == StructProperty->Struct->GetCustomGuid());
};
// Check if the Inner property can successfully serialize, the type may have changed
FStructProperty* StructProperty = CastFieldChecked<FStructProperty>(Inner);
// if check redirector to make sure if the name has changed
FName NewName = FLinkerLoad::FindNewNameForStruct(InnerTag.StructName);
FName StructName = StructProperty->Struct->GetFName();
if (NewName != NAME_None && NewName == StructName)
{
InnerTag.StructName = NewName;
}
if (InnerTag.StructName != StructProperty->Struct->GetFName() && !CanSerializeFromStructWithDifferentName(InnerTag, StructProperty))
{
UE_LOG(LogClass, Warning, TEXT("Property %s of %s has a struct type mismatch (tag %s != prop %s) in package: %s. If that struct got renamed, add an entry to ActiveStructRedirects."),
*InnerTag.Name.ToString(), *GetName(), *InnerTag.StructName.ToString(), *CastFieldChecked<FStructProperty>(Inner)->Struct->GetName(), *UnderlyingArchive.GetArchiveName());
#if WITH_EDITOR
// Ensure the structure is initialized
for (int32 i = 0; i < n; i++)
{
StructProperty->Struct->InitializeDefaultValue(ArrayHelper.GetRawPtr(i));
}
#endif // WITH_EDITOR
if (!bIsTextFormat)
{
// Skip the property
const int64 StartOfProperty = UnderlyingArchive.Tell();
const int64 RemainingSize = InnerTag.Size - (UnderlyingArchive.Tell() - StartOfProperty);
uint8 B;
for (int64 i = 0; i < RemainingSize; i++)
{
UnderlyingArchive << B;
}
}
return;
}
}
}
// need to know how much data this call to SerializeItem consumes, so mark where we are
int64 DataOffset = UnderlyingArchive.Tell();
// If we're using a custom property list, first serialize any explicit indices
int32 i = 0;
bool bSerializeRemainingItems = true;
bool bUsingCustomPropertyList = UnderlyingArchive.ArUseCustomPropertyList;
if (bUsingCustomPropertyList && UnderlyingArchive.ArCustomPropertyList != nullptr)
{
// Initially we only serialize indices that are explicitly specified (in order)
bSerializeRemainingItems = false;
const FCustomPropertyListNode* CustomPropertyList = UnderlyingArchive.ArCustomPropertyList;
const FCustomPropertyListNode* PropertyNode = CustomPropertyList;
FSerializedPropertyScope SerializedProperty(UnderlyingArchive, Inner, this);
while (PropertyNode && i < n && !bSerializeRemainingItems)
{
if (PropertyNode->Property != Inner)
{
// A null property value signals that we should serialize the remaining array values in full starting at this index
if (PropertyNode->Property == nullptr)
{
i = PropertyNode->ArrayIndex;
}
bSerializeRemainingItems = true;
}
else
{
// Set a temporary node to represent the item
FCustomPropertyListNode ItemNode = *PropertyNode;
ItemNode.ArrayIndex = 0;
ItemNode.PropertyListNext = nullptr;
UnderlyingArchive.ArCustomPropertyList = &ItemNode;
// Serialize the item at this array index
i = PropertyNode->ArrayIndex;
Inner->SerializeItem(Array.EnterElement(), ArrayHelper.GetRawPtr(i));
PropertyNode = PropertyNode->PropertyListNext;
// Restore the current property list
UnderlyingArchive.ArCustomPropertyList = CustomPropertyList;
}
}
}
if (bSerializeRemainingItems)
{
// Temporarily suspend the custom property list (as we need these items to be serialized in full)
UnderlyingArchive.ArUseCustomPropertyList = false;
// Serialize each item until we get to the end of the array
FSerializedPropertyScope SerializedProperty(UnderlyingArchive, Inner, this);
while (i < n)
{
#if WITH_EDITOR
static const FName NAME_UArraySerialize = FName(TEXT("FArrayProperty::Serialize"));
FName NAME_UArraySerializeCount = FName(NAME_UArraySerialize);
NAME_UArraySerializeCount.SetNumber(i);
FArchive::FScopeAddDebugData P(UnderlyingArchive, NAME_UArraySerializeCount);
#endif
Inner->SerializeItem(Array.EnterElement(), ArrayHelper.GetRawPtr(i++));
}
// Restore use of the custom property list (if it was previously enabled)
UnderlyingArchive.ArUseCustomPropertyList = bUsingCustomPropertyList;
}
if (MaybeInnerTag.IsSet() && UnderlyingArchive.IsSaving() && !bIsTextFormat)
{
FPropertyTag& InnerTag = MaybeInnerTag.GetValue();
// set the tag's size
InnerTag.Size = UnderlyingArchive.Tell() - DataOffset;
if (InnerTag.Size > 0)
{
// mark our current location
DataOffset = UnderlyingArchive.Tell();
// go back and re-serialize the size now that we know it
UnderlyingArchive.Seek(InnerTag.SizeOffset);
UnderlyingArchive << InnerTag.Size;
// return to the current location
UnderlyingArchive.Seek(DataOffset);
}
}
}
bool FArrayProperty::NetSerializeItem(FArchive& Ar, UPackageMap* Map, void* Data, TArray<uint8>* MetaData) const
{
UE_LOG(LogProperty, Fatal, TEXT("Deprecated code path"));
return 1;
}
void FArrayProperty::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
SerializeSingleField(Ar, Inner, this);
checkSlow(Inner);
}
void FArrayProperty::AddReferencedObjects(FReferenceCollector& Collector)
{
Super::AddReferencedObjects(Collector);
if (Inner)
{
Inner->AddReferencedObjects(Collector);
}
}
FString FArrayProperty::GetCPPTypeCustom(FString* ExtendedTypeText, uint32 CPPExportFlags, const FString& InnerTypeText, const FString& InInnerExtendedTypeText) const
{
if (ExtendedTypeText != NULL)
{
FString InnerExtendedTypeText = InInnerExtendedTypeText;
if (InnerExtendedTypeText.Len() && InnerExtendedTypeText.Right(1) == TEXT(">"))
{
// if our internal property type is a template class, add a space between the closing brackets b/c VS.NET cannot parse this correctly
InnerExtendedTypeText += TEXT(" ");
}
else if (!InnerExtendedTypeText.Len() && InnerTypeText.Len() && InnerTypeText.Right(1) == TEXT(">"))
{
// if our internal property type is a template class, add a space between the closing brackets b/c VS.NET cannot parse this correctly
InnerExtendedTypeText += TEXT(" ");
}
*ExtendedTypeText = FString::Printf(TEXT("<%s%s>"), *InnerTypeText, *InnerExtendedTypeText);
}
return TEXT("TArray");
}
FString FArrayProperty::GetCPPType(FString* ExtendedTypeText /*=NULL*/, uint32 CPPExportFlags /*=0*/) const
{
checkSlow(Inner);
FString InnerExtendedTypeText;
FString InnerTypeText;
if (ExtendedTypeText != NULL)
{
InnerTypeText = Inner->GetCPPType(&InnerExtendedTypeText, CPPExportFlags & ~CPPF_ArgumentOrReturnValue); // we won't consider array inners to be "arguments or return values"
}
return GetCPPTypeCustom(ExtendedTypeText, CPPExportFlags, InnerTypeText, InnerExtendedTypeText);
}
FString FArrayProperty::GetCPPTypeForwardDeclaration() const
{
checkSlow(Inner);
return Inner->GetCPPTypeForwardDeclaration();
}
FString FArrayProperty::GetCPPMacroType(FString& ExtendedTypeText) const
{
checkSlow(Inner);
ExtendedTypeText = Inner->GetCPPType();
return TEXT("TARRAY");
}
void FArrayProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, const void* DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const
{
checkSlow(Inner);
if (0 != (PortFlags & PPF_ExportCpp))
{
FString ExtendedTypeText;
FString TypeText = GetCPPType(&ExtendedTypeText, EPropertyExportCPPFlags::CPPF_BlueprintCppBackend);
ValueStr += FString::Printf(TEXT("%s%s()"), *TypeText, *ExtendedTypeText);
return;
}
FScriptArrayHelper ArrayHelper(this, PropertyValue);
int32 DefaultSize = 0;
if (DefaultValue)
{
FScriptArrayHelper DefaultArrayHelper(this, DefaultValue);
DefaultSize = DefaultArrayHelper.Num();
DefaultValue = DefaultArrayHelper.GetRawPtr(0);
}
ExportTextInnerItem(ValueStr, Inner, ArrayHelper.GetRawPtr(0), ArrayHelper.Num(), DefaultValue, DefaultSize, Parent, PortFlags, ExportRootScope);
}
void FArrayProperty::ExportTextInnerItem(FString& ValueStr, const FProperty* Inner, const void* PropertyValue, int32 PropertySize, const void* DefaultValue, int32 DefaultSize, UObject* Parent, int32 PortFlags, UObject* ExportRootScope)
{
checkSlow(Inner);
uint8* StructDefaults = NULL;
const FStructProperty* StructProperty = CastField<FStructProperty>(Inner);
const bool bReadableForm = (0 != (PPF_BlueprintDebugView & PortFlags));
const bool bExternalEditor = (0 != (PPF_ExternalEditor & PortFlags));
// ArrayProperties only export a diff because array entries are cleared and recreated upon import. Static arrays are overwritten when importing,
// so we export the entire struct to ensure all data is copied over correctly. Behavior is currently inconsistent when copy/pasting between the two types.
// In the future, static arrays could export diffs if the property being imported to is reset to default before the import.
// When exporting to an external editor, we want to save defaults so all information is available for editing
if (StructProperty != NULL && Inner->ArrayDim == 1 && !bExternalEditor)
{
checkSlow(StructProperty->Struct);
StructDefaults = (uint8*)FMemory::Malloc(StructProperty->Struct->GetStructureSize() * Inner->ArrayDim);
StructProperty->InitializeValue(StructDefaults);
}
int32 Count = 0;
for (int32 i = 0; i < PropertySize; i++)
{
++Count;
if (!bReadableForm)
{
if (Count == 1)
{
ValueStr += TCHAR('(');
}
else
{
ValueStr += TCHAR(',');
}
}
else
{
if (Count > 1)
{
ValueStr += TCHAR('\n');
}
ValueStr += FString::Printf(TEXT("[%i] "), i);
}
uint8* PropData = (uint8*)PropertyValue + i * Inner->ElementSize;
// Always use struct defaults if the inner is a struct, for symmetry with the import of array inner struct defaults
uint8* PropDefault = nullptr;
if (bExternalEditor)
{
PropDefault = PropData;
}
else if (StructProperty)
{
PropDefault = StructDefaults;
}
else
{
if (DefaultValue && DefaultSize > i)
{
PropDefault = (uint8*)DefaultValue + i * Inner->ElementSize;
}
}
Inner->ExportTextItem(ValueStr, PropData, PropDefault, Parent, PortFlags | PPF_Delimited, ExportRootScope);
}
if ((Count > 0) && !bReadableForm)
{
ValueStr += TEXT(")");
}
if (StructDefaults)
{
StructProperty->DestroyValue(StructDefaults);
FMemory::Free(StructDefaults);
}
}
const TCHAR* FArrayProperty::ImportText_Internal(const TCHAR* Buffer, void* Data, int32 PortFlags, UObject* OwnerObject, FOutputDevice* ErrorText) const
{
FScriptArrayHelper ArrayHelper(this, Data);
return ImportTextInnerItem(Buffer, Inner, Data, PortFlags, OwnerObject, &ArrayHelper, ErrorText);
}
const TCHAR* FArrayProperty::ImportTextInnerItem(const TCHAR* Buffer, const FProperty* Inner, void* Data, int32 PortFlags, UObject* Parent, FScriptArrayHelper* ArrayHelper, FOutputDevice* ErrorText)
{
checkSlow(Inner);
// If we export an empty array we export an empty string, so ensure that if we're passed an empty string
// we interpret it as an empty array.
if (*Buffer == TCHAR('\0') || *Buffer == TCHAR(')') || *Buffer == TCHAR(','))
{
if (ArrayHelper)
{
ArrayHelper->EmptyValues();
}
return Buffer;
}
if (*Buffer++ != TCHAR('('))
{
return NULL;
}
if (ArrayHelper)
{
ArrayHelper->EmptyValues();
ArrayHelper->ExpandForIndex(0);
}
SkipWhitespace(Buffer);
int32 Index = 0;
while (*Buffer != TCHAR(')'))
{
SkipWhitespace(Buffer);
if (*Buffer != TCHAR(','))
{
uint8* Address = ArrayHelper ? ArrayHelper->GetRawPtr(Index) : ((uint8*)Data + Inner->ElementSize * Index);
// Parse the item
Buffer = Inner->ImportText(Buffer, Address, PortFlags | PPF_Delimited, Parent, ErrorText);
if (!Buffer)
{
return NULL;
}
SkipWhitespace(Buffer);
}
if (*Buffer == TCHAR(','))
{
Buffer++;
Index++;
if (ArrayHelper)
{
ArrayHelper->ExpandForIndex(Index);
}
else if (Index >= Inner->ArrayDim)
{
UE_LOG(LogProperty, Warning, TEXT("%s is a fixed-sized array of %i values. Additional data after %i has been ignored during import."), *Inner->GetName(), Inner->ArrayDim, Inner->ArrayDim);
break;
}
}
else
{
break;
}
}
// Make sure we ended on a )
if (*Buffer++ != TCHAR(')'))
{
return NULL;
}
return Buffer;
}
void FArrayProperty::AddCppProperty(FProperty* Property)
{
check(!Inner);
check(Property);
Inner = Property;
}
void FArrayProperty::CopyValuesInternal(void* Dest, void const* Src, int32 Count) const
{
check(Count == 1); // this was never supported, apparently
FScriptArrayHelper SrcArrayHelper(this, Src);
FScriptArrayHelper DestArrayHelper(this, Dest);
int32 Num = SrcArrayHelper.Num();
if (!(Inner->PropertyFlags & CPF_IsPlainOldData))
{
DestArrayHelper.EmptyAndAddValues(Num);
}
else
{
DestArrayHelper.EmptyAndAddUninitializedValues(Num);
}
if (Num)
{
int32 Size = Inner->ElementSize;
uint8* SrcData = (uint8*)SrcArrayHelper.GetRawPtr();
uint8* DestData = (uint8*)DestArrayHelper.GetRawPtr();
if (!(Inner->PropertyFlags & CPF_IsPlainOldData))
{
for (int32 i = 0; i < Num; i++)
{
Inner->CopyCompleteValue(DestData + i * Size, SrcData + i * Size);
}
}
else
{
FMemory::Memcpy(DestData, SrcData, Num * Size);
}
}
}
void FArrayProperty::ClearValueInternal(void* Data) const
{
FScriptArrayHelper ArrayHelper(this, Data);
ArrayHelper.EmptyValues();
}
void FArrayProperty::DestroyValueInternal(void* Dest) const
{
FScriptArrayHelper ArrayHelper(this, Dest);
ArrayHelper.EmptyValues();
//@todo UE4 potential double destroy later from this...would be ok for a script array, but still
ArrayHelper.DestroyContainer_Unsafe();
}
bool FArrayProperty::PassCPPArgsByRef() const
{
return true;
}
/**
* Creates new copies of components
*
* @param Data pointer to the address of the instanced object referenced by this UComponentProperty
* @param DefaultData pointer to the address of the default value of the instanced object referenced by this UComponentProperty
* @param Owner the object that contains this property's data
* @param InstanceGraph contains the mappings of instanced objects and components to their templates
*/
void FArrayProperty::InstanceSubobjects(void* Data, void const* DefaultData, UObject* InOwner, FObjectInstancingGraph* InstanceGraph)
{
if (Data && Inner->ContainsInstancedObjectProperty())
{
FScriptArrayHelper ArrayHelper(this, Data);
FScriptArrayHelper DefaultArrayHelper(this, DefaultData);
int32 InnerElementSize = Inner->ElementSize;
void* TempElement = FMemory_Alloca(InnerElementSize);
for (int32 ElementIndex = 0; ElementIndex < ArrayHelper.Num(); ElementIndex++)
{
uint8* DefaultValue = (DefaultData && ElementIndex < DefaultArrayHelper.Num()) ? DefaultArrayHelper.GetRawPtr(ElementIndex) : nullptr;
FMemory::Memmove(TempElement, ArrayHelper.GetRawPtr(ElementIndex), InnerElementSize);
Inner->InstanceSubobjects(TempElement, DefaultValue, InOwner, InstanceGraph);
if (ElementIndex < ArrayHelper.Num())
{
FMemory::Memmove(ArrayHelper.GetRawPtr(ElementIndex), TempElement, InnerElementSize);
}
else
{
Inner->DestroyValue(TempElement);
}
}
}
}
bool FArrayProperty::SameType(const FProperty* Other) const
{
return Super::SameType(Other) && Inner && Inner->SameType(((FArrayProperty*)Other)->Inner);
}
EConvertFromTypeResult FArrayProperty::ConvertFromType(const FPropertyTag& Tag, FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct)
{
// TODO: The ArrayProperty Tag really doesn't have adequate information for
// many types. This should probably all be moved in to ::SerializeItem
if (Tag.Type == NAME_ArrayProperty && Tag.InnerType != NAME_None && Tag.InnerType != Inner->GetID())
{
void* ArrayPropertyData = ContainerPtrToValuePtr<void>(Data);
int32 ElementCount = 0;
if (Slot.GetUnderlyingArchive().IsTextFormat())
{
Slot.EnterArray(ElementCount);
}
else
{
Slot.GetUnderlyingArchive() << ElementCount;
}
FScriptArrayHelper ScriptArrayHelper(this, ArrayPropertyData);
ScriptArrayHelper.EmptyAndAddValues(ElementCount);
// Convert properties from old type to new type automatically if types are compatible (array case)
if (ElementCount > 0)
{
FPropertyTag InnerPropertyTag;
InnerPropertyTag.Type = Tag.InnerType;
InnerPropertyTag.ArrayIndex = 0;
FStructuredArchive::FStream ValueStream = Slot.EnterStream();
if (Inner->ConvertFromType(InnerPropertyTag, ValueStream.EnterElement(), ScriptArrayHelper.GetRawPtr(0), DefaultsStruct) == EConvertFromTypeResult::Converted)
{
for (int32 i = 1; i < ElementCount; ++i)
{
verify(Inner->ConvertFromType(InnerPropertyTag, ValueStream.EnterElement(), ScriptArrayHelper.GetRawPtr(i), DefaultsStruct) == EConvertFromTypeResult::Converted);
}
return EConvertFromTypeResult::Converted;
}
// TODO: Implement SerializeFromMismatchedTag handling for arrays of structs
else
{
UE_LOG(LogClass, Warning, TEXT("Array Inner Type mismatch in %s of %s - Previous (%s) Current(%s) for package: %s"), *Tag.Name.ToString(), *GetName(), *Tag.InnerType.ToString(), *Inner->GetID().ToString(), *Slot.GetUnderlyingArchive().GetArchiveName());
return EConvertFromTypeResult::CannotConvert;
}
}
else
{
return EConvertFromTypeResult::Converted;
}
}
return EConvertFromTypeResult::UseSerializeItem;
}
FField* FArrayProperty::GetInnerFieldByName(const FName& InName)
{
if (Inner && Inner->GetFName() == InName)
{
return Inner;
}
return nullptr;
}
void FArrayProperty::GetInnerFields(TArray<FField*>& OutFields)
{
if (Inner)
{
OutFields.Add(Inner);
Inner->GetInnerFields(OutFields);
}
}
#include "UObject/DefineUPropertyMacros.h"