6219 lines
220 KiB
C++
6219 lines
220 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
UnClass.cpp: Object class implementation.
|
|
=============================================================================*/
|
|
|
|
#include "UObject/Class.h"
|
|
#include "HAL/ThreadSafeBool.h"
|
|
#include "HAL/LowLevelMemTracker.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "Serialization/MemoryWriter.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/OutputDeviceHelper.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "Misc/OutputDeviceConsole.h"
|
|
#include "Misc/AutomationTest.h"
|
|
#include "Misc/EnumClassFlags.h"
|
|
#include "Misc/StringBuilder.h"
|
|
#include "UObject/ErrorException.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "UObject/UObjectAllocator.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/MetaData.h"
|
|
#include "Templates/Casts.h"
|
|
#include "UObject/DebugSerializationFlags.h"
|
|
#include "UObject/PropertyTag.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "UObject/UnrealTypePrivate.h"
|
|
#include "UObject/Stack.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "UObject/ObjectResource.h"
|
|
#include "UObject/LinkerSave.h"
|
|
#include "UObject/Interface.h"
|
|
#include "Misc/HotReloadInterface.h"
|
|
#include "UObject/LinkerPlaceholderClass.h"
|
|
#include "UObject/LinkerPlaceholderFunction.h"
|
|
#include "UObject/StructScriptLoader.h"
|
|
#include "UObject/PropertyHelper.h"
|
|
#include "UObject/CoreRedirects.h"
|
|
#include "Internationalization/PolyglotTextData.h"
|
|
#include "Serialization/ArchiveScriptReferenceCollector.h"
|
|
#include "Serialization/ArchiveUObjectFromStructuredArchive.h"
|
|
#include "UObject/FrameworkObjectVersion.h"
|
|
#include "UObject/GarbageCollection.h"
|
|
#include "UObject/UObjectThreadContext.h"
|
|
#include "Serialization/LoadTimeTracePrivate.h"
|
|
#include "Serialization/UnversionedPropertySerialization.h"
|
|
#include "Serialization/UnversionedPropertySerializationTest.h"
|
|
#include "UObject/CoreObjectVersion.h"
|
|
#include "UObject/FastReferenceCollector.h"
|
|
#include "UObject/PropertyProxyArchive.h"
|
|
#include "UObject/FieldPath.h"
|
|
#include "HAL/ThreadSafeCounter.h"
|
|
|
|
// WARNING: This should always be the last include in any file that needs it (except .generated.h)
|
|
#include "UObject/UndefineUPropertyMacros.h"
|
|
|
|
// This flag enables some expensive class tree validation that is meant to catch mutations of
|
|
// the class tree outside of SetSuperStruct. It has been disabled because loading blueprints
|
|
// does a lot of mutation of the class tree, and the validation checks impact iteration time.
|
|
#define DO_CLASS_TREE_VALIDATION 0
|
|
|
|
DEFINE_LOG_CATEGORY(LogScriptSerialization);
|
|
DEFINE_LOG_CATEGORY(LogClass);
|
|
|
|
IMPLEMENT_STRUCT(TestUninitializedScriptStructMembersTest);
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER == 1900
|
|
#ifdef PRAGMA_DISABLE_SHADOW_VARIABLE_WARNINGS
|
|
PRAGMA_DISABLE_SHADOW_VARIABLE_WARNINGS
|
|
#endif
|
|
#endif
|
|
|
|
// If we end up pushing class flags out beyond a uint32, there are various places
|
|
// casting it to uint32 that need to be fixed up (mostly printfs but also some serialization code)
|
|
static_assert(sizeof(__underlying_type(EClassFlags)) == sizeof(uint32), "expecting ClassFlags enum to fit in a uint32");
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
FThreadSafeBool& InternalSafeGetTokenStreamDirtyFlag()
|
|
{
|
|
static FThreadSafeBool TokenStreamDirty(true);
|
|
return TokenStreamDirty;
|
|
}
|
|
|
|
/**
|
|
* Shared function called from the various InitializePrivateStaticClass functions generated my the IMPLEMENT_CLASS macro.
|
|
*/
|
|
COREUOBJECT_API void InitializePrivateStaticClass(
|
|
class UClass* TClass_Super_StaticClass,
|
|
class UClass* TClass_PrivateStaticClass,
|
|
class UClass* TClass_WithinClass_StaticClass,
|
|
const TCHAR* PackageName,
|
|
const TCHAR* Name)
|
|
{
|
|
TRACE_LOADTIME_CLASS_INFO(TClass_PrivateStaticClass, Name);
|
|
NotifyRegistrationEvent(PackageName, Name, ENotifyRegistrationType::NRT_Class, ENotifyRegistrationPhase::NRP_Started);
|
|
|
|
/* No recursive ::StaticClass calls allowed. Setup extras. */
|
|
if (TClass_Super_StaticClass != TClass_PrivateStaticClass)
|
|
{
|
|
TClass_PrivateStaticClass->SetSuperStruct(TClass_Super_StaticClass);
|
|
}
|
|
else
|
|
{
|
|
TClass_PrivateStaticClass->SetSuperStruct(NULL);
|
|
}
|
|
TClass_PrivateStaticClass->ClassWithin = TClass_WithinClass_StaticClass;
|
|
|
|
// Register the class's dependencies, then itself.
|
|
TClass_PrivateStaticClass->RegisterDependencies();
|
|
if (!TClass_PrivateStaticClass->HasAnyFlags(RF_Dynamic))
|
|
{
|
|
// Defer
|
|
TClass_PrivateStaticClass->Register(PackageName, Name);
|
|
}
|
|
else
|
|
{
|
|
// Register immediately (don't let the function name mistake you!)
|
|
TClass_PrivateStaticClass->DeferredRegister(UDynamicClass::StaticClass(), PackageName, Name);
|
|
}
|
|
NotifyRegistrationEvent(PackageName, Name, ENotifyRegistrationType::NRT_Class, ENotifyRegistrationPhase::NRP_Finished);
|
|
}
|
|
|
|
void FNativeFunctionRegistrar::RegisterFunction(class UClass* Class, const ANSICHAR* InName, FNativeFuncPtr InPointer)
|
|
{
|
|
Class->AddNativeFunction(InName, InPointer);
|
|
}
|
|
|
|
void FNativeFunctionRegistrar::RegisterFunction(class UClass* Class, const WIDECHAR* InName, FNativeFuncPtr InPointer)
|
|
{
|
|
Class->AddNativeFunction(InName, InPointer);
|
|
}
|
|
|
|
void FNativeFunctionRegistrar::RegisterFunctions(class UClass* Class, const FNameNativePtrPair* InArray, int32 NumFunctions)
|
|
{
|
|
for (; NumFunctions; ++InArray, --NumFunctions)
|
|
{
|
|
Class->AddNativeFunction(UTF8_TO_TCHAR(InArray->NameUTF8), InArray->Pointer);
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
UField implementation.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
UField::UField(EStaticConstructor, EObjectFlags InFlags)
|
|
: UObject(EC_StaticConstructor, InFlags), Next(NULL)
|
|
{}
|
|
|
|
UClass* UField::GetOwnerClass() const
|
|
{
|
|
UClass* OwnerClass = NULL;
|
|
UObject* TestObject = const_cast<UField*>(this);
|
|
|
|
while ((TestObject != NULL) && (OwnerClass == NULL))
|
|
{
|
|
OwnerClass = dynamic_cast<UClass*>(TestObject);
|
|
TestObject = TestObject->GetOuter();
|
|
}
|
|
|
|
return OwnerClass;
|
|
}
|
|
|
|
UStruct* UField::GetOwnerStruct() const
|
|
{
|
|
const UObject* Obj = this;
|
|
do
|
|
{
|
|
if (const UStruct* Result = dynamic_cast<const UStruct*>(Obj))
|
|
{
|
|
return const_cast<UStruct*>(Result);
|
|
}
|
|
|
|
Obj = Obj->GetOuter();
|
|
} while (Obj);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FString UField::GetAuthoredName() const
|
|
{
|
|
UStruct* Struct = GetOwnerStruct();
|
|
if (Struct)
|
|
{
|
|
return Struct->GetAuthoredNameForField(this);
|
|
}
|
|
return FString();
|
|
}
|
|
|
|
void UField::Bind()
|
|
{
|
|
}
|
|
|
|
void UField::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
Bind();
|
|
}
|
|
|
|
bool UField::NeedsLoadForClient() const
|
|
{
|
|
// Overridden to avoid calling the expensive generic version, which only ensures that our class is not excluded, which it never can be
|
|
return true;
|
|
}
|
|
|
|
bool UField::NeedsLoadForServer() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void UField::Serialize(FArchive& Ar)
|
|
{
|
|
Super::Serialize(Ar);
|
|
Ar.UsingCustomVersion(FFrameworkObjectVersion::GUID);
|
|
if (Ar.CustomVer(FFrameworkObjectVersion::GUID) < FFrameworkObjectVersion::RemoveUField_Next)
|
|
{
|
|
Ar << Next;
|
|
}
|
|
}
|
|
|
|
void UField::AddCppProperty(FProperty* Property)
|
|
{
|
|
UE_LOG(LogClass, Fatal, TEXT("UField::AddCppProperty"));
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
|
|
struct FDisplayNameHelper
|
|
{
|
|
static FString Get(const UObject& Object)
|
|
{
|
|
const UClass* Class = Cast<const UClass>(&Object);
|
|
if (Class && !Class->HasAnyClassFlags(CLASS_Native))
|
|
{
|
|
FString Name = Object.GetName();
|
|
Name.RemoveFromEnd(TEXT("_C"));
|
|
Name.RemoveFromStart(TEXT("SKEL_"));
|
|
return Name;
|
|
}
|
|
|
|
// if (auto Property = dynamic_cast<const FProperty*>(&Object))
|
|
//{
|
|
// return Property->GetAuthoredName();
|
|
// }
|
|
|
|
return Object.GetName();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Finds the localized display name or native display name as a fallback.
|
|
*
|
|
* @return The display name for this object.
|
|
*/
|
|
FText UField::GetDisplayNameText() const
|
|
{
|
|
FText LocalizedDisplayName;
|
|
|
|
static const FString Namespace = TEXT("UObjectDisplayNames");
|
|
static const FName NAME_DisplayName(TEXT("DisplayName"));
|
|
|
|
const FString Key = GetFullGroupName(false);
|
|
|
|
FString NativeDisplayName = GetMetaData(NAME_DisplayName);
|
|
if (NativeDisplayName.IsEmpty())
|
|
{
|
|
NativeDisplayName = FName::NameToDisplayString(FDisplayNameHelper::Get(*this), false);
|
|
}
|
|
|
|
if (!(FText::FindText(Namespace, Key, /*OUT*/ LocalizedDisplayName, &NativeDisplayName)))
|
|
{
|
|
LocalizedDisplayName = FText::FromString(NativeDisplayName);
|
|
}
|
|
|
|
return LocalizedDisplayName;
|
|
}
|
|
|
|
/**
|
|
* Finds the localized tooltip or native tooltip as a fallback.
|
|
*
|
|
* @return The tooltip for this object.
|
|
*/
|
|
FText UField::GetToolTipText(bool bShortTooltip) const
|
|
{
|
|
bool bFoundShortTooltip = false;
|
|
static const FName NAME_Tooltip(TEXT("Tooltip"));
|
|
static const FName NAME_ShortTooltip(TEXT("ShortTooltip"));
|
|
FText LocalizedToolTip;
|
|
FString NativeToolTip;
|
|
|
|
if (bShortTooltip)
|
|
{
|
|
NativeToolTip = GetMetaData(NAME_ShortTooltip);
|
|
if (NativeToolTip.IsEmpty())
|
|
{
|
|
NativeToolTip = GetMetaData(NAME_Tooltip);
|
|
}
|
|
else
|
|
{
|
|
bFoundShortTooltip = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NativeToolTip = GetMetaData(NAME_Tooltip);
|
|
}
|
|
|
|
const FString Namespace = bFoundShortTooltip ? TEXT("UObjectShortTooltips") : TEXT("UObjectToolTips");
|
|
const FString Key = GetFullGroupName(false);
|
|
if (!FText::FindText(Namespace, Key, /*OUT*/ LocalizedToolTip, &NativeToolTip))
|
|
{
|
|
if (NativeToolTip.IsEmpty())
|
|
{
|
|
NativeToolTip = FName::NameToDisplayString(FDisplayNameHelper::Get(*this), false);
|
|
}
|
|
else if (!bShortTooltip && IsNative())
|
|
{
|
|
FormatNativeToolTip(NativeToolTip, true);
|
|
}
|
|
LocalizedToolTip = FText::FromString(NativeToolTip);
|
|
}
|
|
|
|
return LocalizedToolTip;
|
|
}
|
|
|
|
void UField::FormatNativeToolTip(FString& ToolTipString, bool bRemoveExtraSections)
|
|
{
|
|
// First do doxygen replace
|
|
static const FString DoxygenSee(TEXT("@see"));
|
|
static const FString TooltipSee(TEXT("See:"));
|
|
ToolTipString.ReplaceInline(*DoxygenSee, *TooltipSee);
|
|
|
|
bool bCurrentLineIsEmpty = true;
|
|
int32 EmptyLineCount = 0;
|
|
int32 LastContentIndex = INDEX_NONE;
|
|
const int32 ToolTipLength = ToolTipString.Len();
|
|
|
|
// Start looking for empty lines and whitespace to strip
|
|
for (int32 StrIndex = 0; StrIndex < ToolTipLength; StrIndex++)
|
|
{
|
|
TCHAR CurrentChar = ToolTipString[StrIndex];
|
|
|
|
if (!FChar::IsWhitespace(CurrentChar))
|
|
{
|
|
if (FChar::IsPunct(CurrentChar))
|
|
{
|
|
// Punctuation is considered content if it's on a line with alphanumeric text
|
|
if (!bCurrentLineIsEmpty)
|
|
{
|
|
LastContentIndex = StrIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is something alphanumeric, this is always content and mark line as not empty
|
|
bCurrentLineIsEmpty = false;
|
|
LastContentIndex = StrIndex;
|
|
}
|
|
}
|
|
else if (CurrentChar == TEXT('\n'))
|
|
{
|
|
if (bCurrentLineIsEmpty)
|
|
{
|
|
EmptyLineCount++;
|
|
if (bRemoveExtraSections && EmptyLineCount >= 2)
|
|
{
|
|
// If we get two empty or punctuation/separator lines in a row, cut off the string if requested
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EmptyLineCount = 0;
|
|
}
|
|
|
|
bCurrentLineIsEmpty = true;
|
|
}
|
|
}
|
|
|
|
// Trim string to last content character, this strips trailing whitespace as well as extra sections if needed
|
|
if (LastContentIndex >= 0 && LastContentIndex != ToolTipLength - 1)
|
|
{
|
|
ToolTipString.RemoveAt(LastContentIndex + 1, ToolTipLength - (LastContentIndex + 1));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines if the property has any metadata associated with the key
|
|
*
|
|
* @param Key The key to lookup in the metadata
|
|
* @return true if there is a (possibly blank) value associated with this key
|
|
*/
|
|
const FString* UField::FindMetaData(const TCHAR* Key) const
|
|
{
|
|
UPackage* Package = GetOutermost();
|
|
check(Package);
|
|
|
|
UMetaData* MetaData = Package->GetMetaData();
|
|
check(MetaData);
|
|
|
|
return MetaData->FindValue(this, Key);
|
|
}
|
|
|
|
const FString* UField::FindMetaData(const FName& Key) const
|
|
{
|
|
UPackage* Package = GetOutermost();
|
|
check(Package);
|
|
|
|
UMetaData* MetaData = Package->GetMetaData();
|
|
check(MetaData);
|
|
|
|
return MetaData->FindValue(this, Key);
|
|
}
|
|
|
|
/**
|
|
* Find the metadata value associated with the key
|
|
*
|
|
* @param Key The key to lookup in the metadata
|
|
* @return The value associated with the key
|
|
*/
|
|
const FString& UField::GetMetaData(const TCHAR* Key) const
|
|
{
|
|
UPackage* Package = GetOutermost();
|
|
check(Package);
|
|
|
|
UMetaData* MetaData = Package->GetMetaData();
|
|
check(MetaData);
|
|
|
|
const FString& MetaDataString = MetaData->GetValue(this, Key);
|
|
|
|
return MetaDataString;
|
|
}
|
|
|
|
const FString& UField::GetMetaData(const FName& Key) const
|
|
{
|
|
UPackage* Package = GetOutermost();
|
|
check(Package);
|
|
|
|
UMetaData* MetaData = Package->GetMetaData();
|
|
check(MetaData);
|
|
|
|
const FString& MetaDataString = MetaData->GetValue(this, Key);
|
|
|
|
return MetaDataString;
|
|
}
|
|
|
|
FText UField::GetMetaDataText(const TCHAR* MetaDataKey, const FString LocalizationNamespace, const FString LocalizationKey) const
|
|
{
|
|
FString DefaultMetaData;
|
|
|
|
if (const FString* FoundMetaData = FindMetaData(MetaDataKey))
|
|
{
|
|
DefaultMetaData = *FoundMetaData;
|
|
}
|
|
|
|
// If attempting to grab the DisplayName metadata, we must correct the source string and output it as a DisplayString for lookup
|
|
if (DefaultMetaData.IsEmpty() && FCString::Stricmp(MetaDataKey, TEXT("DisplayName")) == 0)
|
|
{
|
|
DefaultMetaData = FName::NameToDisplayString(GetName(), false);
|
|
}
|
|
|
|
FText LocalizedMetaData;
|
|
if (!(FText::FindText(LocalizationNamespace, LocalizationKey, /*OUT*/ LocalizedMetaData, &DefaultMetaData)))
|
|
{
|
|
if (!DefaultMetaData.IsEmpty())
|
|
{
|
|
LocalizedMetaData = FText::AsCultureInvariant(DefaultMetaData);
|
|
}
|
|
}
|
|
|
|
return LocalizedMetaData;
|
|
}
|
|
|
|
FText UField::GetMetaDataText(const FName& MetaDataKey, const FString LocalizationNamespace, const FString LocalizationKey) const
|
|
{
|
|
FString DefaultMetaData;
|
|
|
|
if (const FString* FoundMetaData = FindMetaData(MetaDataKey))
|
|
{
|
|
DefaultMetaData = *FoundMetaData;
|
|
}
|
|
|
|
// If attempting to grab the DisplayName metadata, we must correct the source string and output it as a DisplayString for lookup
|
|
if (DefaultMetaData.IsEmpty() && MetaDataKey == TEXT("DisplayName"))
|
|
{
|
|
DefaultMetaData = FName::NameToDisplayString(GetName(), false);
|
|
}
|
|
|
|
FText LocalizedMetaData;
|
|
if (!(FText::FindText(LocalizationNamespace, LocalizationKey, /*OUT*/ LocalizedMetaData, &DefaultMetaData)))
|
|
{
|
|
if (!DefaultMetaData.IsEmpty())
|
|
{
|
|
LocalizedMetaData = FText::AsCultureInvariant(DefaultMetaData);
|
|
}
|
|
}
|
|
|
|
return LocalizedMetaData;
|
|
}
|
|
|
|
/**
|
|
* Sets the metadata value associated with the key
|
|
*
|
|
* @param Key The key to lookup in the metadata
|
|
* @return The value associated with the key
|
|
*/
|
|
void UField::SetMetaData(const TCHAR* Key, const TCHAR* InValue)
|
|
{
|
|
UPackage* Package = GetOutermost();
|
|
check(Package);
|
|
|
|
Package->GetMetaData()->SetValue(this, Key, InValue);
|
|
}
|
|
|
|
void UField::SetMetaData(const FName& Key, const TCHAR* InValue)
|
|
{
|
|
UPackage* Package = GetOutermost();
|
|
check(Package);
|
|
|
|
Package->GetMetaData()->SetValue(this, Key, InValue);
|
|
}
|
|
|
|
UClass* UField::GetClassMetaData(const TCHAR* Key) const
|
|
{
|
|
const FString& ClassName = GetMetaData(Key);
|
|
UClass* const FoundObject = FindObject<UClass>(ANY_PACKAGE, *ClassName);
|
|
return FoundObject;
|
|
}
|
|
|
|
UClass* UField::GetClassMetaData(const FName& Key) const
|
|
{
|
|
const FString& ClassName = GetMetaData(Key);
|
|
UClass* const FoundObject = FindObject<UClass>(ANY_PACKAGE, *ClassName);
|
|
return FoundObject;
|
|
}
|
|
|
|
void UField::RemoveMetaData(const TCHAR* Key)
|
|
{
|
|
UPackage* Package = GetOutermost();
|
|
check(Package);
|
|
return Package->GetMetaData()->RemoveValue(this, Key);
|
|
}
|
|
|
|
void UField::RemoveMetaData(const FName& Key)
|
|
{
|
|
UPackage* Package = GetOutermost();
|
|
check(Package);
|
|
return Package->GetMetaData()->RemoveValue(this, Key);
|
|
}
|
|
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
bool UField::HasAnyCastFlags(const uint64 InCastFlags) const
|
|
{
|
|
return !!(GetClass()->ClassCastFlags & InCastFlags);
|
|
}
|
|
|
|
bool UField::HasAllCastFlags(const uint64 InCastFlags) const
|
|
{
|
|
return (GetClass()->ClassCastFlags & InCastFlags) == InCastFlags;
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
FField* UField::GetAssociatedFField()
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
void UField::SetAssociatedFField(FField* InField)
|
|
{
|
|
check(false); // unsupported for this type
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
IMPLEMENT_CORE_INTRINSIC_CLASS(UField, UObject,
|
|
{
|
|
Class->EmitObjectReference(STRUCT_OFFSET(UField, Next), TEXT("Next"));
|
|
});
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
UStruct implementation.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/** Simple reference processor and collector for collecting all UObjects referenced by FProperties */
|
|
class FPropertyReferenceCollector: public FReferenceCollector
|
|
{
|
|
/** The owner object for properties we collect references for */
|
|
UObject* Owner;
|
|
|
|
public:
|
|
FPropertyReferenceCollector(UObject* InOwner)
|
|
: Owner(InOwner)
|
|
{
|
|
}
|
|
|
|
TSet<UObject*> UniqueReferences;
|
|
|
|
virtual bool IsIgnoringArchetypeRef() const override { return false; }
|
|
virtual bool IsIgnoringTransient() const override { return false; }
|
|
virtual void HandleObjectReference(UObject*& InObject, const UObject* InReferencingObject, const FProperty* InReferencingProperty) override
|
|
{
|
|
// Skip nulls and the owner object
|
|
if (InObject && InObject != Owner)
|
|
{
|
|
// Don't collect objects that will never be GC'd anyway
|
|
if (!InObject->HasAnyInternalFlags(EInternalObjectFlags::Native) && !GUObjectArray.IsDisregardForGC(InObject))
|
|
{
|
|
UniqueReferences.Add(InObject);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
static int32 GetNextFieldPathSerialNumber()
|
|
{
|
|
static FThreadSafeCounter GlobalSerialNumberCounter;
|
|
return GlobalSerialNumberCounter.Increment();
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
//
|
|
// Constructors.
|
|
//
|
|
UStruct::UStruct(EStaticConstructor, int32 InSize, int32 InMinAlignment, EObjectFlags InFlags)
|
|
: UField(EC_StaticConstructor, InFlags), SuperStruct(nullptr), Children(nullptr), ChildProperties(nullptr), PropertiesSize(InSize), MinAlignment(InMinAlignment), PropertyLink(nullptr), RefLink(nullptr), DestructorLink(nullptr), PostConstructLink(nullptr), UnresolvedScriptProperties(nullptr)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
FieldPathSerialNumber = GetNextFieldPathSerialNumber();
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
UStruct::UStruct(UStruct* InSuperStruct, SIZE_T ParamsSize, SIZE_T Alignment)
|
|
: UField(FObjectInitializer::Get()), SuperStruct(InSuperStruct), Children(nullptr), ChildProperties(nullptr), PropertiesSize(ParamsSize ? ParamsSize : (InSuperStruct ? InSuperStruct->GetPropertiesSize() : 0)), MinAlignment(Alignment ? Alignment : (FMath::Max(InSuperStruct ? InSuperStruct->GetMinAlignment() : 1, 1))), PropertyLink(nullptr), RefLink(nullptr), DestructorLink(nullptr), PostConstructLink(nullptr), UnresolvedScriptProperties(nullptr)
|
|
{
|
|
#if USTRUCT_FAST_ISCHILDOF_IMPL == USTRUCT_ISCHILDOF_STRUCTARRAY
|
|
this->ReinitializeBaseChainArray();
|
|
#endif
|
|
#if WITH_EDITORONLY_DATA
|
|
FieldPathSerialNumber = GetNextFieldPathSerialNumber();
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
UStruct::UStruct(const FObjectInitializer& ObjectInitializer, UStruct* InSuperStruct, SIZE_T ParamsSize, SIZE_T Alignment)
|
|
: UField(ObjectInitializer), SuperStruct(InSuperStruct), Children(nullptr), ChildProperties(nullptr), PropertiesSize(ParamsSize ? ParamsSize : (InSuperStruct ? InSuperStruct->GetPropertiesSize() : 0)), MinAlignment(Alignment ? Alignment : (FMath::Max(InSuperStruct ? InSuperStruct->GetMinAlignment() : 1, 1))), PropertyLink(nullptr), RefLink(nullptr), DestructorLink(nullptr), PostConstructLink(nullptr), UnresolvedScriptProperties(nullptr)
|
|
{
|
|
#if USTRUCT_FAST_ISCHILDOF_IMPL == USTRUCT_ISCHILDOF_STRUCTARRAY
|
|
this->ReinitializeBaseChainArray();
|
|
#endif
|
|
#if WITH_EDITORONLY_DATA
|
|
FieldPathSerialNumber = GetNextFieldPathSerialNumber();
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
/**
|
|
* Force any base classes to be registered first, then call BaseRegister
|
|
*/
|
|
void UStruct::RegisterDependencies()
|
|
{
|
|
Super::RegisterDependencies();
|
|
if (SuperStruct != NULL)
|
|
{
|
|
SuperStruct->RegisterDependencies();
|
|
}
|
|
}
|
|
|
|
void UStruct::AddCppProperty(FProperty* Property)
|
|
{
|
|
Property->Next = ChildProperties;
|
|
ChildProperties = Property;
|
|
}
|
|
|
|
void UStruct::StaticLink(bool bRelinkExistingProperties)
|
|
{
|
|
FArchive ArDummy;
|
|
Link(ArDummy, bRelinkExistingProperties);
|
|
}
|
|
|
|
void UStruct::GetPreloadDependencies(TArray<UObject*>& OutDeps)
|
|
{
|
|
Super::GetPreloadDependencies(OutDeps);
|
|
OutDeps.Add(SuperStruct);
|
|
|
|
for (UField* Field = Children; Field; Field = Field->Next)
|
|
{
|
|
if (!Cast<UFunction>(Field))
|
|
{
|
|
OutDeps.Add(Field);
|
|
}
|
|
}
|
|
|
|
for (FField* Field = ChildProperties; Field; Field = Field->Next)
|
|
{
|
|
Field->GetPreloadDependencies(OutDeps);
|
|
}
|
|
}
|
|
|
|
void UStruct::CollectBytecodeReferencedObjects(TArray<UObject*>& OutReferencedObjects)
|
|
{
|
|
FArchiveScriptReferenceCollector ObjRefCollector(OutReferencedObjects);
|
|
|
|
int32 BytecodeIndex = 0;
|
|
while (BytecodeIndex < Script.Num())
|
|
{
|
|
SerializeExpr(BytecodeIndex, ObjRefCollector);
|
|
}
|
|
}
|
|
|
|
void UStruct::CollectPropertyReferencedObjects(TArray<UObject*>& OutReferencedObjects)
|
|
{
|
|
FPropertyReferenceCollector PropertyReferenceCollector(this);
|
|
for (FField* CurrentField = ChildProperties; CurrentField; CurrentField = CurrentField->Next)
|
|
{
|
|
CurrentField->AddReferencedObjects(PropertyReferenceCollector);
|
|
}
|
|
OutReferencedObjects.Append(PropertyReferenceCollector.UniqueReferences.Array());
|
|
}
|
|
|
|
void UStruct::CollectBytecodeAndPropertyReferencedObjects()
|
|
{
|
|
ScriptAndPropertyObjectReferences.Empty();
|
|
CollectBytecodeReferencedObjects(ScriptAndPropertyObjectReferences);
|
|
CollectPropertyReferencedObjects(ScriptAndPropertyObjectReferences);
|
|
}
|
|
|
|
void UStruct::Link(FArchive& Ar, bool bRelinkExistingProperties)
|
|
{
|
|
if (bRelinkExistingProperties)
|
|
{
|
|
// Preload everything before we calculate size, as the preload may end up recursively linking things
|
|
UStruct* InheritanceSuper = GetInheritanceSuper();
|
|
if (Ar.IsLoading())
|
|
{
|
|
if (InheritanceSuper)
|
|
{
|
|
Ar.Preload(InheritanceSuper);
|
|
}
|
|
|
|
for (UField* Field = Children; Field; Field = Field->Next)
|
|
{
|
|
if (!GEventDrivenLoaderEnabled || !Cast<UFunction>(Field))
|
|
{
|
|
Ar.Preload(Field);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
ConvertUFieldsToFFields();
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
int32 LoopNum = 1;
|
|
for (int32 LoopIter = 0; LoopIter < LoopNum; LoopIter++)
|
|
{
|
|
PropertiesSize = 0;
|
|
MinAlignment = 1;
|
|
|
|
if (InheritanceSuper)
|
|
{
|
|
PropertiesSize = InheritanceSuper->GetPropertiesSize();
|
|
MinAlignment = InheritanceSuper->GetMinAlignment();
|
|
}
|
|
|
|
for (FField* Field = ChildProperties; Field; Field = Field->Next)
|
|
{
|
|
if (Field->GetOwner<UObject>() != this)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (FProperty* Property = CastField<FProperty>(Field))
|
|
{
|
|
#if !WITH_EDITORONLY_DATA
|
|
// If we don't have the editor, make sure we aren't trying to link properties that are editor only.
|
|
check(!Property->IsEditorOnlyProperty());
|
|
#endif // WITH_EDITORONLY_DATA
|
|
ensureMsgf(Property->GetOwner<UObject>() == this, TEXT("Linking '%s'. Property '%s' has outer '%s'"),
|
|
*GetFullName(), *Property->GetName(), *Property->GetOwnerVariant().GetFullName());
|
|
|
|
// Linking a property can cause a recompilation of the struct.
|
|
// When the property was changed, the struct should be relinked again, to be sure, the PropertiesSize is actual.
|
|
const bool bPropertyIsTransient = Property->HasAllFlags(RF_Transient);
|
|
const FName PropertyName = Property->GetFName();
|
|
|
|
PropertiesSize = Property->Link(Ar);
|
|
|
|
if ((bPropertyIsTransient != Property->HasAllFlags(RF_Transient)) || (PropertyName != Property->GetFName()))
|
|
{
|
|
LoopNum++;
|
|
const int32 MaxLoopLimit = 64;
|
|
ensure(LoopNum < MaxLoopLimit);
|
|
break;
|
|
}
|
|
|
|
MinAlignment = FMath::Max(MinAlignment, Property->GetMinAlignment());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bHandledWithCppStructOps = false;
|
|
if (GetClass()->IsChildOf(UScriptStruct::StaticClass()))
|
|
{
|
|
// check for internal struct recursion via arrays
|
|
for (FField* Field = ChildProperties; Field; Field = Field->Next)
|
|
{
|
|
FArrayProperty* ArrayProp = CastField<FArrayProperty>(Field);
|
|
if (ArrayProp != NULL)
|
|
{
|
|
FStructProperty* StructProp = CastField<FStructProperty>(ArrayProp->Inner);
|
|
if (StructProp != NULL && StructProp->Struct == this)
|
|
{
|
|
// we won't support this, too complicated
|
|
#if HACK_HEADER_GENERATOR
|
|
FError::Throwf(TEXT("'Struct recursion via arrays is unsupported for properties."));
|
|
#else
|
|
UE_LOG(LogClass, Fatal, TEXT("'Struct recursion via arrays is unsupported for properties."));
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
UScriptStruct& ScriptStruct = dynamic_cast<UScriptStruct&>(*this);
|
|
ScriptStruct.PrepareCppStructOps();
|
|
|
|
if (UScriptStruct::ICppStructOps* CppStructOps = ScriptStruct.GetCppStructOps())
|
|
{
|
|
MinAlignment = CppStructOps->GetAlignment();
|
|
PropertiesSize = CppStructOps->GetSize();
|
|
bHandledWithCppStructOps = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (FField* Field = ChildProperties; (Field != NULL) && (Field->GetOwner<UObject>() == this); Field = Field->Next)
|
|
{
|
|
if (FProperty* Property = CastField<FProperty>(Field))
|
|
{
|
|
Property->LinkWithoutChangingOffset(Ar);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GetOutermost()->GetFName() == GLongCoreUObjectPackageName)
|
|
{
|
|
FName ToTest = GetFName();
|
|
if (ToTest == NAME_Matrix)
|
|
{
|
|
check(MinAlignment == alignof(FMatrix));
|
|
check(PropertiesSize == sizeof(FMatrix));
|
|
}
|
|
else if (ToTest == NAME_Plane)
|
|
{
|
|
check(MinAlignment == alignof(FPlane));
|
|
check(PropertiesSize == sizeof(FPlane));
|
|
}
|
|
else if (ToTest == NAME_Vector4)
|
|
{
|
|
check(MinAlignment == alignof(FVector4));
|
|
check(PropertiesSize == sizeof(FVector4));
|
|
}
|
|
else if (ToTest == NAME_Quat)
|
|
{
|
|
check(MinAlignment == alignof(FQuat));
|
|
check(PropertiesSize == sizeof(FQuat));
|
|
}
|
|
else if (ToTest == NAME_Double)
|
|
{
|
|
check(MinAlignment == alignof(double));
|
|
check(PropertiesSize == sizeof(double));
|
|
}
|
|
else if (ToTest == NAME_Color)
|
|
{
|
|
check(MinAlignment == alignof(FColor));
|
|
check(PropertiesSize == sizeof(FColor));
|
|
#if !PLATFORM_LITTLE_ENDIAN
|
|
// Object.h declares FColor as BGRA which doesn't match up with what we'd like to use on
|
|
// Xenon to match up directly with the D3D representation of D3DCOLOR. We manually fiddle
|
|
// with the property offsets to get everything to line up.
|
|
// In any case, on big-endian systems we want to byte-swap this.
|
|
//@todo cooking: this should be moved into the data cooking step.
|
|
{
|
|
FProperty* ColorComponentEntries[4];
|
|
uint32 ColorComponentIndex = 0;
|
|
|
|
for (UField* Field = Children; Field && Field->GetOuter() == this; Field = Field->Next)
|
|
{
|
|
FProperty* Property = CastFieldChecked<FProperty>(Field);
|
|
ColorComponentEntries[ColorComponentIndex++] = Property;
|
|
}
|
|
check(ColorComponentIndex == 4);
|
|
|
|
Exchange(ColorComponentEntries[0]->Offset, ColorComponentEntries[3]->Offset);
|
|
Exchange(ColorComponentEntries[1]->Offset, ColorComponentEntries[2]->Offset);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Link the references, structs, and arrays for optimized cleanup.
|
|
// Note: Could optimize further by adding FProperty::NeedsDynamicRefCleanup, excluding things like arrays of ints.
|
|
FProperty** PropertyLinkPtr = &PropertyLink;
|
|
FProperty** DestructorLinkPtr = &DestructorLink;
|
|
FProperty** RefLinkPtr = (FProperty**)&RefLink;
|
|
FProperty** PostConstructLinkPtr = &PostConstructLink;
|
|
|
|
TArray<const FStructProperty*> EncounteredStructProps;
|
|
for (TFieldIterator<FProperty> It(this); It; ++It)
|
|
{
|
|
FProperty* Property = *It;
|
|
|
|
if (Property->ContainsObjectReference(EncounteredStructProps) || Property->ContainsWeakObjectReference())
|
|
{
|
|
*RefLinkPtr = Property;
|
|
RefLinkPtr = &(*RefLinkPtr)->NextRef;
|
|
}
|
|
|
|
const UClass* OwnerClass = Property->GetOwnerClass();
|
|
bool bOwnedByNativeClass = OwnerClass && OwnerClass->HasAnyClassFlags(CLASS_Native | CLASS_Intrinsic);
|
|
|
|
if (!Property->HasAnyPropertyFlags(CPF_IsPlainOldData | CPF_NoDestructor) &&
|
|
!bOwnedByNativeClass) // these would be covered by the native destructor
|
|
{
|
|
// things in a struct that need a destructor will still be in here, even though in many cases they will also be destroyed by a native destructor on the whole struct
|
|
*DestructorLinkPtr = Property;
|
|
DestructorLinkPtr = &(*DestructorLinkPtr)->DestructorLinkNext;
|
|
}
|
|
|
|
// Link references to properties that require their values to be initialized and/or copied from CDO post-construction. Note that this includes all non-native-class-owned properties.
|
|
if (OwnerClass && (!bOwnedByNativeClass || (Property->HasAnyPropertyFlags(CPF_Config) && !OwnerClass->HasAnyClassFlags(CLASS_PerObjectConfig))))
|
|
{
|
|
*PostConstructLinkPtr = Property;
|
|
PostConstructLinkPtr = &(*PostConstructLinkPtr)->PostConstructLinkNext;
|
|
}
|
|
|
|
*PropertyLinkPtr = Property;
|
|
PropertyLinkPtr = &(*PropertyLinkPtr)->PropertyLinkNext;
|
|
}
|
|
|
|
*PropertyLinkPtr = nullptr;
|
|
*DestructorLinkPtr = nullptr;
|
|
*RefLinkPtr = nullptr;
|
|
*PostConstructLinkPtr = nullptr;
|
|
|
|
{
|
|
// Now collect all references from FProperties to UObjects and store them in GC-exposed array for fast access
|
|
CollectPropertyReferencedObjects(ScriptAndPropertyObjectReferences);
|
|
|
|
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
|
|
// The old (non-EDL) FLinkerLoad code paths create placeholder objects
|
|
// for classes and functions. We have to babysit these, just as we do
|
|
// for bytecode references (reusing the AddReferencingScriptExpr fn).
|
|
// Long term we should not use placeholder objects like this:
|
|
for (int32 ReferenceIndex = ScriptAndPropertyObjectReferences.Num() - 1; ReferenceIndex >= 0; --ReferenceIndex)
|
|
{
|
|
if (ScriptAndPropertyObjectReferences[ReferenceIndex])
|
|
{
|
|
if (ULinkerPlaceholderClass* PlaceholderObj = Cast<ULinkerPlaceholderClass>(ScriptAndPropertyObjectReferences[ReferenceIndex]))
|
|
{
|
|
// let the placeholder track the reference to it:
|
|
PlaceholderObj->AddReferencingScriptExpr(reinterpret_cast<UClass**>(&ScriptAndPropertyObjectReferences[ReferenceIndex]));
|
|
}
|
|
// I don't currently see how placeholder functions could be present in this list, but that's
|
|
// a dangerous assumption.
|
|
ensure(!(ScriptAndPropertyObjectReferences[ReferenceIndex]->IsA<ULinkerPlaceholderFunction>()));
|
|
}
|
|
else
|
|
{
|
|
// It's possible that in the process of recompilation one of the refernces got GC'd leaving a null ptr in the array
|
|
ScriptAndPropertyObjectReferences.RemoveAt(ReferenceIndex);
|
|
}
|
|
}
|
|
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
// Discard old wrapper objects used by property grids
|
|
for (UPropertyWrapper* Wrapper: PropertyWrappers)
|
|
{
|
|
Wrapper->Rename(nullptr, GetTransientPackage(), REN_ForceNoResetLoaders | REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional);
|
|
Wrapper->RemoveFromRoot();
|
|
}
|
|
PropertyWrappers.Empty();
|
|
#endif
|
|
}
|
|
|
|
void UStruct::InitializeStruct(void* InDest, int32 ArrayDim /* = 1*/) const
|
|
{
|
|
uint8* Dest = (uint8*)InDest;
|
|
check(Dest);
|
|
|
|
int32 Stride = GetStructureSize();
|
|
|
|
//@todo UE4 optimize
|
|
FMemory::Memzero(Dest, 1 * Stride);
|
|
|
|
for (FProperty* Property = PropertyLink; Property; Property = Property->PropertyLinkNext)
|
|
{
|
|
if (ensure(Property->IsInContainer(Stride)))
|
|
{
|
|
for (int32 ArrayIndex = 0; ArrayIndex < ArrayDim; ArrayIndex++)
|
|
{
|
|
Property->InitializeValue_InContainer(Dest + ArrayIndex * Stride);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UStruct::DestroyStruct(void* Dest, int32 ArrayDim) const
|
|
{
|
|
uint8* Data = (uint8*)Dest;
|
|
int32 Stride = GetStructureSize();
|
|
|
|
bool bHitBase = false;
|
|
for (FProperty* P = DestructorLink; P && !bHitBase; P = P->DestructorLinkNext)
|
|
{
|
|
if (!P->HasAnyPropertyFlags(CPF_NoDestructor))
|
|
{
|
|
if (P->IsInContainer(Stride))
|
|
{
|
|
for (int32 ArrayIndex = 0; ArrayIndex < ArrayDim; ArrayIndex++)
|
|
{
|
|
P->DestroyValue_InContainer(Data + ArrayIndex * Stride);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bHitBase = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Serialize all of the class's data that belongs in a particular
|
|
// bin and resides in Data.
|
|
//
|
|
void UStruct::SerializeBin(FStructuredArchive::FSlot Slot, void* Data) const
|
|
{
|
|
FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive();
|
|
|
|
FStructuredArchive::FStream PropertyStream = Slot.EnterStream();
|
|
|
|
if (UnderlyingArchive.IsObjectReferenceCollector())
|
|
{
|
|
for (FProperty* RefLinkProperty = RefLink; RefLinkProperty != NULL; RefLinkProperty = RefLinkProperty->NextRef)
|
|
{
|
|
RefLinkProperty->SerializeBinProperty(PropertyStream.EnterElement(), Data);
|
|
}
|
|
}
|
|
else if (UnderlyingArchive.ArUseCustomPropertyList)
|
|
{
|
|
const FCustomPropertyListNode* CustomPropertyList = UnderlyingArchive.ArCustomPropertyList;
|
|
for (auto PropertyNode = CustomPropertyList; PropertyNode; PropertyNode = PropertyNode->PropertyListNext)
|
|
{
|
|
FProperty* Property = PropertyNode->Property;
|
|
if (Property)
|
|
{
|
|
// Temporarily set to the sub property list, in case we're serializing a UStruct property.
|
|
UnderlyingArchive.ArCustomPropertyList = PropertyNode->SubPropertyList;
|
|
|
|
Property->SerializeBinProperty(PropertyStream.EnterElement(), Data, PropertyNode->ArrayIndex);
|
|
|
|
// Restore the original property list.
|
|
UnderlyingArchive.ArCustomPropertyList = CustomPropertyList;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (FProperty* Property = PropertyLink; Property != NULL; Property = Property->PropertyLinkNext)
|
|
{
|
|
Property->SerializeBinProperty(PropertyStream.EnterElement(), Data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UStruct::SerializeBinEx(FStructuredArchive::FSlot Slot, void* Data, void const* DefaultData, UStruct* DefaultStruct) const
|
|
{
|
|
if (!DefaultData || !DefaultStruct)
|
|
{
|
|
SerializeBin(Slot, Data);
|
|
return;
|
|
}
|
|
|
|
for (TFieldIterator<FProperty> It(this); It; ++It)
|
|
{
|
|
It->SerializeNonMatchingBinProperty(Slot, Data, DefaultData, DefaultStruct);
|
|
}
|
|
}
|
|
|
|
void UStruct::LoadTaggedPropertiesFromText(FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct, uint8* Defaults, const UObject* BreakRecursionIfFullyLoad) const
|
|
{
|
|
FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive();
|
|
const bool bUseRedirects = !FPlatformProperties::RequiresCookedData() || UnderlyingArchive.IsSaveGame();
|
|
int32 NumProperties = 0;
|
|
FStructuredArchiveMap PropertiesMap = Slot.EnterMap(NumProperties);
|
|
|
|
for (int32 PropertyIndex = 0; PropertyIndex < NumProperties; ++PropertyIndex)
|
|
{
|
|
FString PropertyNameString;
|
|
FStructuredArchiveSlot PropertySlot = PropertiesMap.EnterElement(PropertyNameString);
|
|
FName PropertyName = *PropertyNameString;
|
|
|
|
// If this property has a guid attached then we need to resolve it to the right name before we start loading
|
|
TOptional<FStructuredArchiveSlot> PropertyGuidSlot = PropertySlot.TryEnterAttribute(SA_FIELD_NAME(TEXT("PropertyGuid")), false);
|
|
if (PropertyGuidSlot.IsSet())
|
|
{
|
|
FGuid PropertyGuid;
|
|
PropertyGuidSlot.GetValue() << PropertyGuid;
|
|
if (PropertyGuid.IsValid())
|
|
{
|
|
FName NewName = FindPropertyNameFromGuid(PropertyGuid);
|
|
if (NewName != NAME_None)
|
|
{
|
|
PropertyName = NewName;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Resolve any redirects if necessary
|
|
if (bUseRedirects && !UnderlyingArchive.HasAnyPortFlags(PPF_DuplicateForPIE | PPF_Duplicate))
|
|
{
|
|
for (UStruct* CheckStruct = GetOwnerStruct(); CheckStruct; CheckStruct = CheckStruct->GetSuperStruct())
|
|
{
|
|
FName NewTagName = FProperty::FindRedirectedPropertyName(CheckStruct, PropertyName);
|
|
if (!NewTagName.IsNone())
|
|
{
|
|
PropertyName = NewTagName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now we know what the property name is, we can try and load it
|
|
FProperty* Property = FindPropertyByName(PropertyName);
|
|
|
|
if (Property == nullptr)
|
|
{
|
|
Property = CustomFindProperty(PropertyName);
|
|
}
|
|
|
|
if (Property && Property->ShouldSerializeValue(UnderlyingArchive))
|
|
{
|
|
FName PropID = Property->GetID();
|
|
|
|
// Static arrays of tagged properties are special cases where the slot is always an array with no tag data attached. We currently have no TryEnterArray we can't
|
|
// react based on what is in the file (yet) so we'll just have to assume that nobody converts a property from an array to a single value and go with whatever
|
|
// the code property tells us.
|
|
TOptional<FStructuredArchiveArray> SlotArray;
|
|
int32 NumItems = Property->ArrayDim;
|
|
if (Property->ArrayDim > 1)
|
|
{
|
|
int32 NumAvailableItems = 0;
|
|
SlotArray.Emplace(PropertySlot.EnterArray(NumAvailableItems));
|
|
NumItems = FMath::Min(Property->ArrayDim, NumAvailableItems);
|
|
}
|
|
|
|
for (int32 ItemIndex = 0; ItemIndex < NumItems; ++ItemIndex)
|
|
{
|
|
TOptional<FStructuredArchiveSlot> ItemSlot;
|
|
if (SlotArray.IsSet())
|
|
{
|
|
ItemSlot.Emplace(SlotArray->EnterElement());
|
|
}
|
|
else
|
|
{
|
|
ItemSlot.Emplace(PropertySlot);
|
|
}
|
|
|
|
FPropertyTag Tag;
|
|
ItemSlot.GetValue() << Tag;
|
|
Tag.ArrayIndex = ItemIndex;
|
|
Tag.Name = PropertyName;
|
|
|
|
if (bUseRedirects)
|
|
{
|
|
if (Tag.Type == NAME_StructProperty && PropID == NAME_StructProperty)
|
|
{
|
|
const FName NewName = FLinkerLoad::FindNewNameForStruct(Tag.StructName);
|
|
const FName StructName = CastFieldChecked<FStructProperty>(Property)->Struct->GetFName();
|
|
if (NewName == StructName)
|
|
{
|
|
Tag.StructName = NewName;
|
|
}
|
|
}
|
|
else if ((PropID == NAME_EnumProperty) && ((Tag.Type == NAME_EnumProperty) || (Tag.Type == NAME_ByteProperty)))
|
|
{
|
|
const FName NewName = FLinkerLoad::FindNewNameForEnum(Tag.EnumName);
|
|
if (!NewName.IsNone())
|
|
{
|
|
Tag.EnumName = NewName;
|
|
}
|
|
}
|
|
|
|
if (!(BreakRecursionIfFullyLoad && BreakRecursionIfFullyLoad->HasAllFlags(RF_LoadCompleted)))
|
|
{
|
|
switch (Property->ConvertFromType(Tag, ItemSlot.GetValue(), Data, DefaultsStruct))
|
|
{
|
|
case EConvertFromTypeResult::Converted:
|
|
break;
|
|
|
|
case EConvertFromTypeResult::UseSerializeItem:
|
|
if (Tag.Type != PropID)
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("Type mismatch in %s of %s - Previous (%s) Current(%s) for package: %s"), *Tag.Name.ToString(), *GetName(), *Tag.Type.ToString(), *PropID.ToString(), *UnderlyingArchive.GetArchiveName());
|
|
}
|
|
else
|
|
{
|
|
uint8* DestAddress = Property->ContainerPtrToValuePtr<uint8>(Data, Tag.ArrayIndex);
|
|
uint8* DefaultsFromParent = Property->ContainerPtrToValuePtrForDefaults<uint8>(DefaultsStruct, Defaults, Tag.ArrayIndex);
|
|
|
|
// This property is ok.
|
|
Tag.SerializeTaggedProperty(ItemSlot.GetValue(), Property, DestAddress, DefaultsFromParent);
|
|
}
|
|
break;
|
|
|
|
case EConvertFromTypeResult::CannotConvert:
|
|
break;
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UStruct::SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct, uint8* Defaults, const UObject* BreakRecursionIfFullyLoad) const
|
|
{
|
|
if (Slot.GetArchiveState().UseUnversionedPropertySerialization())
|
|
{
|
|
SerializeUnversionedProperties(this, Slot, Data, DefaultsStruct, Defaults);
|
|
}
|
|
else
|
|
{
|
|
SerializeVersionedTaggedProperties(Slot, Data, DefaultsStruct, Defaults, BreakRecursionIfFullyLoad);
|
|
}
|
|
}
|
|
|
|
void UStruct::SerializeVersionedTaggedProperties(FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct, uint8* Defaults, const UObject* BreakRecursionIfFullyLoad) const
|
|
{
|
|
FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive();
|
|
// SCOPED_LOADTIMER(SerializeTaggedPropertiesTime);
|
|
|
|
// Determine if this struct supports optional property guid's (UBlueprintGeneratedClasses Only)
|
|
const bool bArePropertyGuidsAvailable = (UnderlyingArchive.UE4Ver() >= VER_UE4_PROPERTY_GUID_IN_PROPERTY_TAG) && !FPlatformProperties::RequiresCookedData() && ArePropertyGuidsAvailable();
|
|
const bool bUseRedirects = (!FPlatformProperties::RequiresCookedData() || UnderlyingArchive.IsSaveGame()) && !UnderlyingArchive.IsUsingEventDrivenLoader();
|
|
|
|
if (UnderlyingArchive.IsLoading())
|
|
{
|
|
#if WITH_TEXT_ARCHIVE_SUPPORT
|
|
if (UnderlyingArchive.IsTextFormat())
|
|
{
|
|
LoadTaggedPropertiesFromText(Slot, Data, DefaultsStruct, Defaults, BreakRecursionIfFullyLoad);
|
|
}
|
|
else
|
|
#endif // WITH_TEXT_ARCHIVE_SUPPORT
|
|
{
|
|
// Load tagged properties.
|
|
FStructuredArchive::FStream PropertiesStream = Slot.EnterStream();
|
|
|
|
// This code assumes that properties are loaded in the same order they are saved in. This removes a n^2 search
|
|
// and makes it an O(n) when properties are saved in the same order as they are loaded (default case). In the
|
|
// case that a property was reordered the code falls back to a slower search.
|
|
FProperty* Property = PropertyLink;
|
|
bool bAdvanceProperty = false;
|
|
int32 RemainingArrayDim = Property ? Property->ArrayDim : 0;
|
|
|
|
// Load all stored properties, potentially skipping unknown ones.
|
|
while (true)
|
|
{
|
|
FStructuredArchive::FRecord PropertyRecord = PropertiesStream.EnterElement().EnterRecord();
|
|
|
|
FPropertyTag Tag;
|
|
PropertyRecord << SA_VALUE(TEXT("Tag"), Tag);
|
|
|
|
if (Tag.Name.IsNone())
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Move to the next property to be serialized
|
|
if (bAdvanceProperty && --RemainingArrayDim <= 0)
|
|
{
|
|
Property = Property->PropertyLinkNext;
|
|
// Skip over properties that don't need to be serialized.
|
|
while (Property && !Property->ShouldSerializeValue(UnderlyingArchive))
|
|
{
|
|
Property = Property->PropertyLinkNext;
|
|
}
|
|
RemainingArrayDim = Property ? Property->ArrayDim : 0;
|
|
}
|
|
bAdvanceProperty = false;
|
|
|
|
// Optionally resolve properties using Guid Property tags in non cooked builds that support it.
|
|
if (bArePropertyGuidsAvailable && Tag.HasPropertyGuid)
|
|
{
|
|
// Use property guids from blueprint generated classes to redirect serialised data.
|
|
FName Result = FindPropertyNameFromGuid(Tag.PropertyGuid);
|
|
if (Result != NAME_None && Tag.Name != Result)
|
|
{
|
|
Tag.Name = Result;
|
|
}
|
|
}
|
|
// If this property is not the one we expect (e.g. skipped as it matches the default value), do the brute force search.
|
|
if (Property == nullptr || Property->GetFName() != Tag.Name)
|
|
{
|
|
// No need to check redirects on platforms where everything is cooked. Always check for save games
|
|
if (bUseRedirects && !UnderlyingArchive.HasAnyPortFlags(PPF_DuplicateForPIE | PPF_Duplicate))
|
|
{
|
|
for (UStruct* CheckStruct = GetOwnerStruct(); CheckStruct; CheckStruct = CheckStruct->GetSuperStruct())
|
|
{
|
|
FName NewTagName = FProperty::FindRedirectedPropertyName(CheckStruct, Tag.Name);
|
|
if (!NewTagName.IsNone())
|
|
{
|
|
Tag.Name = NewTagName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
FProperty* CurrentProperty = Property;
|
|
// Search forward...
|
|
for (; Property; Property = Property->PropertyLinkNext)
|
|
{
|
|
if (Property->GetFName() == Tag.Name)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// ... and then search from the beginning till we reach the current property if it's not found.
|
|
if (Property == nullptr)
|
|
{
|
|
for (Property = PropertyLink; Property && Property != CurrentProperty; Property = Property->PropertyLinkNext)
|
|
{
|
|
if (Property->GetFName() == Tag.Name)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Property == CurrentProperty)
|
|
{
|
|
// Property wasn't found.
|
|
Property = nullptr;
|
|
}
|
|
}
|
|
|
|
RemainingArrayDim = Property ? Property->ArrayDim : 0;
|
|
}
|
|
|
|
const int64 StartOfProperty = UnderlyingArchive.Tell();
|
|
|
|
if (!Property)
|
|
{
|
|
Property = CustomFindProperty(Tag.Name);
|
|
}
|
|
|
|
if (Property)
|
|
{
|
|
FName PropID = Property->GetID();
|
|
|
|
// Check if this is a struct property and we have a redirector
|
|
// No need to check redirects on platforms where everything is cooked. Always check for save games
|
|
if (bUseRedirects)
|
|
{
|
|
if (Tag.Type == NAME_StructProperty && PropID == NAME_StructProperty)
|
|
{
|
|
const FName NewName = FLinkerLoad::FindNewNameForStruct(Tag.StructName);
|
|
const FName StructName = CastFieldChecked<FStructProperty>(Property)->Struct->GetFName();
|
|
if (NewName == StructName)
|
|
{
|
|
Tag.StructName = NewName;
|
|
}
|
|
}
|
|
else if ((PropID == NAME_EnumProperty) && ((Tag.Type == NAME_EnumProperty) || (Tag.Type == NAME_ByteProperty)))
|
|
{
|
|
const FName NewName = FLinkerLoad::FindNewNameForEnum(Tag.EnumName);
|
|
if (!NewName.IsNone())
|
|
{
|
|
Tag.EnumName = NewName;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (BreakRecursionIfFullyLoad && BreakRecursionIfFullyLoad->HasAllFlags(RF_LoadCompleted))
|
|
{
|
|
}
|
|
else
|
|
#endif // WITH_EDITOR
|
|
// editoronly properties should be skipped if we are NOT the editor, or we are
|
|
// the editor but are cooking for console (editoronly implies notforconsole)
|
|
if ((Property->PropertyFlags & CPF_EditorOnly) && ((!FPlatformProperties::HasEditorOnlyData() && !GForceLoadEditorOnly) || UnderlyingArchive.IsUsingEventDrivenLoader()))
|
|
{
|
|
}
|
|
// check for valid array index
|
|
else if (Tag.ArrayIndex >= Property->ArrayDim || Tag.ArrayIndex < 0)
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("Array bound exceeded (var %s=%d, exceeds %s [0-%d] in package: %s"),
|
|
*Tag.Name.ToString(), Tag.ArrayIndex, *GetName(), Property->ArrayDim - 1, *UnderlyingArchive.GetArchiveName());
|
|
}
|
|
else if (!Property->ShouldSerializeValue(UnderlyingArchive))
|
|
{
|
|
UE_CLOG((UnderlyingArchive.IsPersistent() && FPlatformProperties::RequiresCookedData()), LogClass, Warning, TEXT("Skipping saved property %s of %s since it is no longer serializable for asset: %s. (Maybe resave asset?)"), *Tag.Name.ToString(), *GetName(), *UnderlyingArchive.GetArchiveName());
|
|
}
|
|
else
|
|
{
|
|
FStructuredArchive::FSlot ValueSlot = PropertyRecord.EnterField(SA_FIELD_NAME(TEXT("Value")));
|
|
|
|
switch (Property->ConvertFromType(Tag, ValueSlot, Data, DefaultsStruct))
|
|
{
|
|
case EConvertFromTypeResult::Converted:
|
|
bAdvanceProperty = true;
|
|
break;
|
|
|
|
case EConvertFromTypeResult::UseSerializeItem:
|
|
if (Tag.Type != PropID)
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("Type mismatch in %s of %s - Previous (%s) Current(%s) for package: %s"), *Tag.Name.ToString(), *GetName(), *Tag.Type.ToString(), *PropID.ToString(), *UnderlyingArchive.GetArchiveName());
|
|
}
|
|
else
|
|
{
|
|
uint8* DestAddress = Property->ContainerPtrToValuePtr<uint8>(Data, Tag.ArrayIndex);
|
|
uint8* DefaultsFromParent = Property->ContainerPtrToValuePtrForDefaults<uint8>(DefaultsStruct, Defaults, Tag.ArrayIndex);
|
|
|
|
// This property is ok.
|
|
Tag.SerializeTaggedProperty(ValueSlot, Property, DestAddress, DefaultsFromParent);
|
|
bAdvanceProperty = !UnderlyingArchive.IsCriticalError();
|
|
}
|
|
break;
|
|
|
|
case EConvertFromTypeResult::CannotConvert:
|
|
break;
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
int64 Loaded = UnderlyingArchive.Tell() - StartOfProperty;
|
|
|
|
if (!bAdvanceProperty)
|
|
{
|
|
UnderlyingArchive.Seek(StartOfProperty + Tag.Size);
|
|
}
|
|
else
|
|
{
|
|
check(Tag.Size == Loaded);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FUnversionedPropertyTestCollector TestCollector;
|
|
|
|
FStructuredArchive::FRecord PropertiesRecord = Slot.EnterRecord();
|
|
|
|
check(UnderlyingArchive.IsSaving() || UnderlyingArchive.IsCountingMemory());
|
|
checkf(!UnderlyingArchive.ArUseCustomPropertyList,
|
|
TEXT("Custom property lists only work with binary serialization, not tagged property serialization. "
|
|
"Attempted for struct '%s' and archive '%s'. "),
|
|
*GetFName().ToString(), *UnderlyingArchive.GetArchiveName());
|
|
|
|
UScriptStruct* DefaultsScriptStruct = dynamic_cast<UScriptStruct*>(DefaultsStruct);
|
|
|
|
/** If true, it means that we want to serialize all properties of this struct if any properties differ from defaults */
|
|
bool bUseAtomicSerialization = false;
|
|
if (DefaultsScriptStruct)
|
|
{
|
|
bUseAtomicSerialization = DefaultsScriptStruct->ShouldSerializeAtomically(UnderlyingArchive);
|
|
}
|
|
|
|
// Save tagged properties.
|
|
|
|
// Iterate over properties in the order they were linked and serialize them.
|
|
const FCustomPropertyListNode* CustomPropertyNode = UnderlyingArchive.ArUseCustomPropertyList ? UnderlyingArchive.ArCustomPropertyList : nullptr;
|
|
for (FProperty* Property = UnderlyingArchive.ArUseCustomPropertyList ? (CustomPropertyNode ? CustomPropertyNode->Property : nullptr) : PropertyLink;
|
|
Property;
|
|
Property = UnderlyingArchive.ArUseCustomPropertyList ? FCustomPropertyListNode::GetNextPropertyAndAdvance(CustomPropertyNode) : Property->PropertyLinkNext)
|
|
{
|
|
if (Property->ShouldSerializeValue(UnderlyingArchive))
|
|
{
|
|
const int32 LoopMin = CustomPropertyNode ? CustomPropertyNode->ArrayIndex : 0;
|
|
const int32 LoopMax = CustomPropertyNode ? LoopMin + 1 : Property->ArrayDim;
|
|
|
|
TOptional<FStructuredArchive::FArray> StaticArrayContainer;
|
|
if (((LoopMax - 1) > LoopMin) && UnderlyingArchive.IsTextFormat())
|
|
{
|
|
int32 NumItems = LoopMax - LoopMin;
|
|
StaticArrayContainer.Emplace(PropertiesRecord.EnterArray(SA_FIELD_NAME((*Property->GetName())), NumItems));
|
|
}
|
|
|
|
for (int32 Idx = LoopMin; Idx < LoopMax; Idx++)
|
|
{
|
|
uint8* DataPtr = Property->ContainerPtrToValuePtr<uint8>(Data, Idx);
|
|
uint8* DefaultValue = Property->ContainerPtrToValuePtrForDefaults<uint8>(DefaultsStruct, Defaults, Idx);
|
|
if (StaticArrayContainer.IsSet() || CustomPropertyNode || !UnderlyingArchive.DoDelta() || UnderlyingArchive.IsTransacting() || (!Defaults && !dynamic_cast<const UClass*>(this)) || !Property->Identical(DataPtr, DefaultValue, UnderlyingArchive.GetPortFlags()))
|
|
{
|
|
if (bUseAtomicSerialization)
|
|
{
|
|
DefaultValue = NULL;
|
|
}
|
|
#if WITH_EDITOR
|
|
static const FName NAME_PropertySerialize = FName(TEXT("PropertySerialize"));
|
|
FArchive::FScopeAddDebugData P(UnderlyingArchive, NAME_PropertySerialize);
|
|
FArchive::FScopeAddDebugData S(UnderlyingArchive, Property->GetFName());
|
|
#endif
|
|
TestCollector.RecordSavedProperty(Property);
|
|
|
|
FPropertyTag Tag(UnderlyingArchive, Property, Idx, DataPtr, DefaultValue);
|
|
// If available use the property guid from BlueprintGeneratedClasses, provided we aren't cooking data.
|
|
if (bArePropertyGuidsAvailable && !UnderlyingArchive.IsCooking())
|
|
{
|
|
const FGuid PropertyGuid = FindPropertyGuidFromName(Tag.Name);
|
|
Tag.SetPropertyGuid(PropertyGuid);
|
|
}
|
|
|
|
TStringBuilder<256> TagName;
|
|
Tag.Name.ToString(TagName);
|
|
FStructuredArchive::FSlot PropertySlot = StaticArrayContainer.IsSet() ? StaticArrayContainer->EnterElement() : PropertiesRecord.EnterField(SA_FIELD_NAME(TagName.ToString()));
|
|
|
|
PropertySlot << Tag;
|
|
|
|
// need to know how much data this call to SerializeTaggedProperty consumes, so mark where we are
|
|
int64 DataOffset = UnderlyingArchive.Tell();
|
|
|
|
// if using it, save the current custom property list and switch to its sub property list (in case of UStruct serialization)
|
|
const FCustomPropertyListNode* SavedCustomPropertyList = nullptr;
|
|
if (UnderlyingArchive.ArUseCustomPropertyList && CustomPropertyNode)
|
|
{
|
|
SavedCustomPropertyList = UnderlyingArchive.ArCustomPropertyList;
|
|
UnderlyingArchive.ArCustomPropertyList = CustomPropertyNode->SubPropertyList;
|
|
}
|
|
|
|
Tag.SerializeTaggedProperty(PropertySlot, Property, DataPtr, DefaultValue);
|
|
|
|
// restore the original custom property list after serializing
|
|
if (SavedCustomPropertyList)
|
|
{
|
|
UnderlyingArchive.ArCustomPropertyList = SavedCustomPropertyList;
|
|
}
|
|
|
|
// set the tag's size
|
|
Tag.Size = UnderlyingArchive.Tell() - DataOffset;
|
|
|
|
if (Tag.Size > 0 && !UnderlyingArchive.IsTextFormat())
|
|
{
|
|
// mark our current location
|
|
DataOffset = UnderlyingArchive.Tell();
|
|
|
|
// go back and re-serialize the size now that we know it
|
|
UnderlyingArchive.Seek(Tag.SizeOffset);
|
|
UnderlyingArchive << Tag.Size;
|
|
|
|
// return to the current location
|
|
UnderlyingArchive.Seek(DataOffset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!UnderlyingArchive.IsTextFormat())
|
|
{
|
|
// Add an empty FName that serves as a null-terminator
|
|
FName NoneTerminator;
|
|
UnderlyingArchive << NoneTerminator;
|
|
}
|
|
}
|
|
}
|
|
void UStruct::FinishDestroy()
|
|
{
|
|
DestroyUnversionedSchema(this);
|
|
Script.Empty();
|
|
Super::FinishDestroy();
|
|
}
|
|
|
|
/** Helper function that destroys properties from the privided linked list and nulls the list head pointer */
|
|
inline void DestroyPropertyLinkedList(FField*& PropertiesToDestroy)
|
|
{
|
|
for (FField* FieldToDestroy = PropertiesToDestroy; FieldToDestroy;)
|
|
{
|
|
FField* NextField = FieldToDestroy->Next;
|
|
delete FieldToDestroy;
|
|
FieldToDestroy = NextField;
|
|
}
|
|
PropertiesToDestroy = nullptr;
|
|
}
|
|
|
|
void UStruct::DestroyChildPropertiesAndResetPropertyLinks()
|
|
{
|
|
DestroyPropertyLinkedList(ChildProperties);
|
|
PropertyLink = nullptr;
|
|
RefLink = nullptr;
|
|
DestructorLink = nullptr;
|
|
PostConstructLink = nullptr;
|
|
#if WITH_EDITORONLY_DATA
|
|
FieldPathSerialNumber = GetNextFieldPathSerialNumber();
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
UStruct::~UStruct()
|
|
{
|
|
// Destroy all properties owned by this struct
|
|
// This needs to happen after FinishDestroy which calls DestroyNonNativeProperties
|
|
// Also, Blueprint generated classes can have DestroyNonNativeProperties called on them after their FinishDestroy has been called
|
|
// so properties can only be deleted in the destructor
|
|
DestroyPropertyLinkedList(ChildProperties);
|
|
DeleteUnresolvedScriptProperties();
|
|
}
|
|
|
|
IMPLEMENT_FSTRUCTUREDARCHIVE_SERIALIZER(UStruct);
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
void UStruct::ConvertUFieldsToFFields()
|
|
{
|
|
TArray<FField*> NewChildProperties;
|
|
UField* OldField = Children;
|
|
UField* PreviousUnconvertedField = nullptr;
|
|
|
|
// First convert all properties and store them in a temp array
|
|
while (OldField)
|
|
{
|
|
if (OldField->IsA<UProperty>())
|
|
{
|
|
FField* NewField = OldField->GetAssociatedFField();
|
|
if (!NewField)
|
|
{
|
|
NewField = FField::CreateFromUField(OldField);
|
|
OldField->SetAssociatedFField(NewField);
|
|
check(NewField);
|
|
}
|
|
NewChildProperties.Add(NewField);
|
|
// Remove this field from the linked list
|
|
if (PreviousUnconvertedField)
|
|
{
|
|
PreviousUnconvertedField->Next = OldField->Next;
|
|
}
|
|
else
|
|
{
|
|
Children = OldField->Next;
|
|
}
|
|
// Move the old UProperty to the transient package and rename it to something unique
|
|
OldField->Rename(*MakeUniqueObjectName(GetTransientPackage(), OldField->GetClass()).ToString(), GetTransientPackage(), REN_ForceNoResetLoaders | REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional);
|
|
OldField->RemoveFromRoot();
|
|
}
|
|
else
|
|
{
|
|
// Update the previous unconverted field
|
|
if (PreviousUnconvertedField)
|
|
{
|
|
PreviousUnconvertedField->Next = OldField;
|
|
}
|
|
else
|
|
{
|
|
Children = OldField;
|
|
}
|
|
PreviousUnconvertedField = OldField;
|
|
}
|
|
OldField = OldField->Next;
|
|
}
|
|
// Now add them to the linked list in the reverse order to preserve their actual order (adding to the list reverses the order)
|
|
for (int32 ChildPropertyIndex = NewChildProperties.Num() - 1; ChildPropertyIndex >= 0; --ChildPropertyIndex)
|
|
{
|
|
FField* NewField = NewChildProperties[ChildPropertyIndex];
|
|
check(NewField->Next == nullptr);
|
|
NewField->Next = ChildProperties;
|
|
ChildProperties = NewField;
|
|
}
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
void UStruct::SerializeProperties(FArchive& Ar)
|
|
{
|
|
int32 PropertyCount = 0;
|
|
|
|
if (Ar.IsSaving())
|
|
{
|
|
// Count properties
|
|
for (FField* Field = ChildProperties; Field; Field = Field->Next)
|
|
{
|
|
bool bSaveProperty = true;
|
|
#if WITH_EDITORONLY_DATA
|
|
FProperty* Property = CastField<FProperty>(Field);
|
|
if (Property)
|
|
{
|
|
bSaveProperty = !(Ar.IsFilterEditorOnly() && Property->IsEditorOnlyProperty());
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
if (bSaveProperty)
|
|
{
|
|
PropertyCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ar << PropertyCount;
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
// Not using SerializeSingleField here to avoid unnecessary checks for each property
|
|
TArray<FField*> LoadedProperties;
|
|
LoadedProperties.Reserve(PropertyCount);
|
|
for (int32 PropertyIndex = 0; PropertyIndex < PropertyCount; ++PropertyIndex)
|
|
{
|
|
FName PropertyTypeName;
|
|
Ar << PropertyTypeName;
|
|
FField* Prop = FField::Construct(PropertyTypeName, this, NAME_None, RF_NoFlags);
|
|
check(Prop);
|
|
Prop->Serialize(Ar);
|
|
LoadedProperties.Add(Prop);
|
|
}
|
|
for (int32 PropertyIndex = LoadedProperties.Num() - 1; PropertyIndex >= 0; --PropertyIndex)
|
|
{
|
|
FField* Prop = LoadedProperties[PropertyIndex];
|
|
Prop->Next = ChildProperties;
|
|
ChildProperties = Prop;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 VerifySerializedFieldsCount = 0;
|
|
for (FField* Field = ChildProperties; Field; Field = Field->Next)
|
|
{
|
|
bool bSaveProperty = true;
|
|
#if WITH_EDITORONLY_DATA
|
|
FProperty* Property = CastField<FProperty>(Field);
|
|
if (Property)
|
|
{
|
|
bSaveProperty = !(Ar.IsFilterEditorOnly() && Property->IsEditorOnlyProperty());
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
if (bSaveProperty)
|
|
{
|
|
FName PropertyTypeName = Field->GetClass()->GetFName();
|
|
Ar << PropertyTypeName;
|
|
Field->Serialize(Ar);
|
|
VerifySerializedFieldsCount++;
|
|
}
|
|
}
|
|
check(!Ar.IsSaving() || VerifySerializedFieldsCount == PropertyCount);
|
|
}
|
|
}
|
|
|
|
void UStruct::Serialize(FArchive& Ar)
|
|
{
|
|
Super::Serialize(Ar);
|
|
if (IsRunningCommandlet() && GIsCookerLoadingPackage && GetFName().ToString().EndsWith(TEXT("_C")))
|
|
{
|
|
UE_LOG(LogScriptSerialization, Display, TEXT("Serialize: %s"), *GetFName().ToString());
|
|
}
|
|
|
|
#if USTRUCT_FAST_ISCHILDOF_IMPL == USTRUCT_ISCHILDOF_STRUCTARRAY
|
|
UStruct* SuperStructBefore = GetSuperStruct();
|
|
#endif
|
|
|
|
Ar << SuperStruct;
|
|
|
|
#if USTRUCT_FAST_ISCHILDOF_IMPL == USTRUCT_ISCHILDOF_STRUCTARRAY
|
|
if (Ar.IsLoading())
|
|
{
|
|
this->ReinitializeBaseChainArray();
|
|
}
|
|
// Handle that fact that FArchive takes UObject*s by reference, and archives can just blat
|
|
// over our SuperStruct with impunity.
|
|
else if (SuperStructBefore)
|
|
{
|
|
UStruct* SuperStructAfter = GetSuperStruct();
|
|
if (SuperStructBefore != SuperStructAfter)
|
|
{
|
|
this->ReinitializeBaseChainArray();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Ar.UsingCustomVersion(FFrameworkObjectVersion::GUID);
|
|
Ar.UsingCustomVersion(FCoreObjectVersion::GUID);
|
|
|
|
if (Ar.CustomVer(FFrameworkObjectVersion::GUID) < FFrameworkObjectVersion::RemoveUField_Next)
|
|
{
|
|
Ar << Children;
|
|
}
|
|
else
|
|
{
|
|
TArray<UField*> ChildArray;
|
|
if (Ar.IsLoading())
|
|
{
|
|
Ar << ChildArray;
|
|
#if WITH_EDITOR
|
|
for (int32 Index = 0; Index < ChildArray.Num(); Index++)
|
|
{
|
|
if (ChildArray[Index] == nullptr)
|
|
{
|
|
UE_LOG(LogTemp, Fatal, TEXT("%s 序列化失败,请检查!"), *GetName());
|
|
}
|
|
}
|
|
#endif
|
|
if (ChildArray.Num())
|
|
{
|
|
for (int32 Index = 0; Index + 1 < ChildArray.Num(); Index++)
|
|
{
|
|
ChildArray[Index]->Next = ChildArray[Index + 1];
|
|
}
|
|
Children = ChildArray[0];
|
|
ChildArray[ChildArray.Num() - 1]->Next = nullptr;
|
|
}
|
|
else
|
|
{
|
|
Children = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UField* Child = Children;
|
|
while (Child)
|
|
{
|
|
ChildArray.Add(Child);
|
|
Child = Child->Next;
|
|
}
|
|
Ar << ChildArray;
|
|
}
|
|
}
|
|
|
|
if (Ar.CustomVer(FCoreObjectVersion::GUID) >= FCoreObjectVersion::FProperties)
|
|
{
|
|
SerializeProperties(Ar);
|
|
}
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
FStructScriptLoader ScriptLoadHelper(/*TargetScriptContainer =*/this, Ar);
|
|
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
|
|
bool const bAllowDeferredScriptSerialization = true;
|
|
#else // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
|
|
bool const bAllowDeferredScriptSerialization = false;
|
|
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
|
|
|
|
// NOTE: if bAllowDeferredScriptSerialization is set to true, then this
|
|
// could temporarily skip script serialization (as it could
|
|
// introduce unwanted dependency loads at this time)
|
|
ScriptLoadHelper.LoadStructWithScript(this, Ar, bAllowDeferredScriptSerialization);
|
|
|
|
if (!dynamic_cast<UClass*>(this) && !(Ar.GetPortFlags() & PPF_Duplicate)) // classes are linked in the UClass serializer, which just called me
|
|
{
|
|
// Link the properties.
|
|
Link(Ar, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 ScriptBytecodeSize = Script.Num();
|
|
int64 ScriptStorageSizeOffset = INDEX_NONE;
|
|
|
|
if (Ar.IsSaving())
|
|
{
|
|
FArchive::FScopeSetDebugSerializationFlags S(Ar, DSF_IgnoreDiff);
|
|
|
|
Ar << ScriptBytecodeSize;
|
|
|
|
int32 ScriptStorageSize = 0;
|
|
// drop a zero here. will seek back later and re-write it when we know it
|
|
ScriptStorageSizeOffset = Ar.Tell();
|
|
Ar << ScriptStorageSize;
|
|
}
|
|
|
|
// Skip serialization if we're duplicating classes for reinstancing, since we only need the memory layout
|
|
if (!GIsDuplicatingClassForReinstancing)
|
|
{
|
|
|
|
// no bytecode patch for this struct - serialize normally [i.e. from disk]
|
|
int32 iCode = 0;
|
|
int64 const BytecodeStartOffset = Ar.Tell();
|
|
|
|
if (Ar.IsPersistent() && Ar.GetLinker())
|
|
{
|
|
// make sure this is a ULinkerSave
|
|
FLinkerSave* LinkerSave = CastChecked<FLinkerSave>(Ar.GetLinker());
|
|
|
|
// remember how we were saving
|
|
FArchive* SavedSaver = LinkerSave->Saver;
|
|
|
|
// force writing to a buffer
|
|
TArray<uint8> TempScript;
|
|
FMemoryWriter MemWriter(TempScript, Ar.IsPersistent());
|
|
LinkerSave->Saver = &MemWriter;
|
|
|
|
{
|
|
FPropertyProxyArchive PropertyAr(Ar, iCode, this);
|
|
// now, use the linker to save the byte code, but writing to memory
|
|
while (iCode < ScriptBytecodeSize)
|
|
{
|
|
SerializeExpr(iCode, PropertyAr);
|
|
}
|
|
}
|
|
|
|
// restore the saver
|
|
LinkerSave->Saver = SavedSaver;
|
|
|
|
// now write out the memory bytes
|
|
Ar.Serialize(TempScript.GetData(), TempScript.Num());
|
|
|
|
// and update the SHA (does nothing if not currently calculating SHA)
|
|
LinkerSave->UpdateScriptSHAKey(TempScript);
|
|
}
|
|
else
|
|
{
|
|
FPropertyProxyArchive PropertyAr(Ar, iCode, this);
|
|
while (iCode < ScriptBytecodeSize)
|
|
{
|
|
SerializeExpr(iCode, PropertyAr);
|
|
}
|
|
}
|
|
|
|
if (iCode != ScriptBytecodeSize)
|
|
{
|
|
UE_LOG(LogClass, Fatal, TEXT("Script serialization mismatch: Got %i, expected %i"), iCode, ScriptBytecodeSize);
|
|
}
|
|
|
|
if (Ar.IsSaving())
|
|
{
|
|
FArchive::FScopeSetDebugSerializationFlags S(Ar, DSF_IgnoreDiff);
|
|
|
|
int64 const BytecodeEndOffset = Ar.Tell();
|
|
|
|
// go back and write on-disk size
|
|
Ar.Seek(ScriptStorageSizeOffset);
|
|
int32 ScriptStorageSize = BytecodeEndOffset - BytecodeStartOffset;
|
|
Ar << ScriptStorageSize;
|
|
|
|
// back to where we were
|
|
Ar.Seek(BytecodeEndOffset);
|
|
}
|
|
} // if !GIsDuplicatingClassForReinstancing
|
|
}
|
|
}
|
|
|
|
void UStruct::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
// Finally try to resolve all script properties that couldn't be resolved at load time
|
|
if (UnresolvedScriptProperties)
|
|
{
|
|
for (TPair<TFieldPath<FField>, int32>& MissingProperty: *UnresolvedScriptProperties)
|
|
{
|
|
FField* ResolvedProperty = MissingProperty.Key.Get(this);
|
|
if (ResolvedProperty)
|
|
{
|
|
check((int32)Script.Num() >= (int32)(MissingProperty.Value + sizeof(FField*)));
|
|
FField** TargetScriptPropertyPtr = (FField**)(Script.GetData() + MissingProperty.Value);
|
|
*TargetScriptPropertyPtr = ResolvedProperty;
|
|
}
|
|
else if (!MissingProperty.Key.IsPathToFieldEmpty())
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("Failed to resolve bytecode referenced field from path: %s when loading %s"), *MissingProperty.Key.ToString(), *GetFullName());
|
|
}
|
|
}
|
|
DeleteUnresolvedScriptProperties();
|
|
}
|
|
}
|
|
|
|
void UStruct::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
|
|
{
|
|
UStruct* This = CastChecked<UStruct>(InThis);
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
// Required by the unified GC when running in the editor
|
|
Collector.AddReferencedObject(This->SuperStruct, This);
|
|
Collector.AddReferencedObject(This->Children, This);
|
|
Collector.AddReferencedObjects(This->ScriptAndPropertyObjectReferences, This);
|
|
}
|
|
#endif
|
|
#if WITH_EDITORONLY_DATA
|
|
Collector.AddReferencedObjects(This->PropertyWrappers, This);
|
|
#endif
|
|
Super::AddReferencedObjects(This, Collector);
|
|
}
|
|
|
|
void UStruct::SetSuperStruct(UStruct* NewSuperStruct)
|
|
{
|
|
SuperStruct = NewSuperStruct;
|
|
#if USTRUCT_FAST_ISCHILDOF_IMPL == USTRUCT_ISCHILDOF_STRUCTARRAY
|
|
this->ReinitializeBaseChainArray();
|
|
#endif
|
|
}
|
|
|
|
FString UStruct::PropertyNameToDisplayName(FName InName) const
|
|
{
|
|
FFieldVariant FoundField = FindUFieldOrFProperty(this, InName);
|
|
if (FoundField.IsUObject())
|
|
{
|
|
return GetAuthoredNameForField(FoundField.Get<UField>());
|
|
}
|
|
else
|
|
{
|
|
return GetAuthoredNameForField(FoundField.Get<FField>());
|
|
}
|
|
}
|
|
|
|
FString UStruct::GetAuthoredNameForField(const UField* Field) const
|
|
{
|
|
if (Field)
|
|
{
|
|
return Field->GetName();
|
|
}
|
|
return FString();
|
|
}
|
|
|
|
FString UStruct::GetAuthoredNameForField(const FField* Field) const
|
|
{
|
|
if (Field)
|
|
{
|
|
return Field->GetName();
|
|
}
|
|
return FString();
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
bool UStruct::GetBoolMetaDataHierarchical(const FName& Key) const
|
|
{
|
|
bool bResult = false;
|
|
const UStruct* TestStruct = this;
|
|
while (TestStruct)
|
|
{
|
|
if (TestStruct->HasMetaData(Key))
|
|
{
|
|
bResult = TestStruct->GetBoolMetaData(Key);
|
|
break;
|
|
}
|
|
|
|
TestStruct = TestStruct->SuperStruct;
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
bool UStruct::GetStringMetaDataHierarchical(const FName& Key, FString* OutValue) const
|
|
{
|
|
for (const UStruct* TestStruct = this; TestStruct != nullptr; TestStruct = TestStruct->GetSuperStruct())
|
|
{
|
|
if (const FString* FoundMetaData = TestStruct->FindMetaData(Key))
|
|
{
|
|
if (OutValue != nullptr)
|
|
{
|
|
*OutValue = *FoundMetaData;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const UStruct* UStruct::HasMetaDataHierarchical(const FName& Key) const
|
|
{
|
|
for (const UStruct* TestStruct = this; TestStruct != nullptr; TestStruct = TestStruct->GetSuperStruct())
|
|
{
|
|
if (TestStruct->HasMetaData(Key))
|
|
{
|
|
return TestStruct;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
|
|
/**
|
|
* If we're loading, then the value of the script's UObject* expression
|
|
* could be pointing at a ULinkerPlaceholderClass (used by the linker to
|
|
* fight cyclic dependency issues on load). So here, if that's the case, we
|
|
* have the placeholder track this ref (so it'll replace it once the real
|
|
* class is loaded).
|
|
*
|
|
* @param ScriptPtr Reference to the point in the bytecode buffer, where a UObject* has been stored (for us to check).
|
|
*/
|
|
static void HandlePlaceholderScriptRef(void* ScriptPtr)
|
|
{
|
|
ScriptPointerType Temp = FPlatformMemory::ReadUnaligned<ScriptPointerType>(ScriptPtr);
|
|
UObject*& ExprPtrRef = (UObject*&)Temp;
|
|
if (ULinkerPlaceholderClass* PlaceholderObj = Cast<ULinkerPlaceholderClass>(ExprPtrRef))
|
|
{
|
|
PlaceholderObj->AddReferencingScriptExpr((UClass**)(&ExprPtrRef));
|
|
}
|
|
else if (ULinkerPlaceholderFunction* PlaceholderFunc = Cast<ULinkerPlaceholderFunction>(ExprPtrRef))
|
|
{
|
|
PlaceholderFunc->AddReferencingScriptExpr((UFunction**)(&ExprPtrRef));
|
|
}
|
|
}
|
|
|
|
#define FIXUP_EXPR_OBJECT_POINTER(Type) \
|
|
{ \
|
|
if (!Ar.IsSaving()) \
|
|
{ \
|
|
int32 const ExprIndex = iCode - sizeof(ScriptPointerType); \
|
|
HandlePlaceholderScriptRef(&Script[ExprIndex]); \
|
|
} \
|
|
}
|
|
#endif // #if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
|
|
|
|
EExprToken UStruct::SerializeExpr(int32& iCode, FArchive& Ar)
|
|
{
|
|
#define SERIALIZEEXPR_INC
|
|
#define SERIALIZEEXPR_AUTO_UNDEF_XFER_MACROS
|
|
#include "UObject/ScriptSerialization.h"
|
|
return Expr;
|
|
#undef SERIALIZEEXPR_INC
|
|
#undef SERIALIZEEXPR_AUTO_UNDEF_XFER_MACROS
|
|
}
|
|
|
|
void UStruct::InstanceSubobjectTemplates(void* Data, void const* DefaultData, UStruct* DefaultStruct, UObject* Owner, FObjectInstancingGraph* InstanceGraph)
|
|
{
|
|
checkSlow(Data);
|
|
checkSlow(Owner);
|
|
|
|
for (FProperty* Property = RefLink; Property != NULL; Property = Property->NextRef)
|
|
{
|
|
if (Property->ContainsInstancedObjectProperty())
|
|
{
|
|
Property->InstanceSubobjects(Property->ContainerPtrToValuePtr<uint8>(Data), (uint8*)Property->ContainerPtrToValuePtrForDefaults<uint8>(DefaultStruct, DefaultData), Owner, InstanceGraph);
|
|
}
|
|
}
|
|
}
|
|
|
|
IMPLEMENT_CORE_INTRINSIC_CLASS(UStruct, UField,
|
|
{
|
|
Class->ClassAddReferencedObjects = &UStruct::AddReferencedObjects;
|
|
Class->EmitObjectReference(STRUCT_OFFSET(UStruct, SuperStruct), TEXT("SuperStruct"));
|
|
Class->EmitObjectReference(STRUCT_OFFSET(UStruct, Children), TEXT("Children"));
|
|
|
|
// Note: None of the *Link members need to be emitted, as they only contain properties
|
|
// that are in the Children chain or SuperStruct->Children chains.
|
|
|
|
Class->EmitObjectArrayReference(STRUCT_OFFSET(UStruct, ScriptAndPropertyObjectReferences), TEXT("ScriptAndPropertyObjectReferences"));
|
|
});
|
|
|
|
void UStruct::TagSubobjects(EObjectFlags NewFlags)
|
|
{
|
|
Super::TagSubobjects(NewFlags);
|
|
|
|
// Tag our properties
|
|
for (TFieldIterator<FProperty> It(this, EFieldIteratorFlags::ExcludeSuper); It; ++It)
|
|
{
|
|
FProperty* Property = *It;
|
|
if (Property && !Property->HasAnyFlags(GARBAGE_COLLECTION_KEEPFLAGS) && !Property->IsRooted())
|
|
{
|
|
Property->SetFlags(NewFlags);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return true if this object is of the specified type.
|
|
*/
|
|
#if USTRUCT_FAST_ISCHILDOF_COMPARE_WITH_OUTERWALK || USTRUCT_FAST_ISCHILDOF_IMPL == USTRUCT_ISCHILDOF_OUTERWALK
|
|
bool UStruct::IsChildOf(const UStruct* SomeBase) const
|
|
{
|
|
if (SomeBase == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bOldResult = false;
|
|
for (const UStruct* TempStruct = this; TempStruct; TempStruct = TempStruct->GetSuperStruct())
|
|
{
|
|
if (TempStruct == SomeBase)
|
|
{
|
|
bOldResult = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if USTRUCT_FAST_ISCHILDOF_IMPL == USTRUCT_ISCHILDOF_STRUCTARRAY
|
|
const bool bNewResult = IsChildOfUsingStructArray(*SomeBase);
|
|
#endif
|
|
|
|
#if USTRUCT_FAST_ISCHILDOF_COMPARE_WITH_OUTERWALK
|
|
ensureMsgf(bOldResult == bNewResult, TEXT("New cast code failed"));
|
|
#endif
|
|
|
|
return bOldResult;
|
|
}
|
|
#endif
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
UScriptStruct.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
// sample of how to customize structs
|
|
#if 0
|
|
USTRUCT()
|
|
struct ENGINE_API FTestStruct
|
|
{
|
|
GENERATED_USTRUCT_BODY()
|
|
|
|
UObject* RawObjectPtr = nullptr;
|
|
TMap<int32, double> Doubles;
|
|
FTestStruct()
|
|
{
|
|
Doubles.Add(1, 1.5);
|
|
Doubles.Add(2, 2.5);
|
|
}
|
|
void AddStructReferencedObjects(class FReferenceCollector& Collector)
|
|
{
|
|
Collector.AddReferencedObject(RawObjectPtr);
|
|
}
|
|
bool Serialize(FArchive& Ar)
|
|
{
|
|
Ar << Doubles;
|
|
return true;
|
|
}
|
|
bool operator==(FTestStruct const& Other) const
|
|
{
|
|
if (Doubles.Num() != Other.Doubles.Num())
|
|
{
|
|
return false;
|
|
}
|
|
for (TMap<int32, double>::TConstIterator It(Doubles); It; ++It)
|
|
{
|
|
double const* OtherVal = Other.Doubles.Find(It.Key());
|
|
if (!OtherVal || *OtherVal != It.Value() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
bool Identical(FTestStruct const& Other, uint32 PortFlags) const
|
|
{
|
|
return (*this) == Other;
|
|
}
|
|
void operator=(FTestStruct const& Other)
|
|
{
|
|
Doubles.Empty(Other.Doubles.Num());
|
|
for (TMap<int32, double>::TConstIterator It(Other.Doubles); It; ++It)
|
|
{
|
|
Doubles.Add(It.Key(), It.Value());
|
|
}
|
|
}
|
|
bool ExportTextItem(FString& ValueStr, FTestStruct const& DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const
|
|
{
|
|
ValueStr += TEXT("(");
|
|
for (TMap<int32, double>::TConstIterator It(Doubles); It; ++It)
|
|
{
|
|
ValueStr += FString::Printf( TEXT("(%d,%f)"),It.Key(), It.Value());
|
|
}
|
|
ValueStr += TEXT(")");
|
|
return true;
|
|
}
|
|
bool ImportTextItem( const TCHAR*& Buffer, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText )
|
|
{
|
|
check(*Buffer == TEXT('('));
|
|
Buffer++;
|
|
Doubles.Empty();
|
|
while (1)
|
|
{
|
|
const TCHAR* Start = Buffer;
|
|
while (*Buffer && *Buffer != TEXT(','))
|
|
{
|
|
if (*Buffer == TEXT(')'))
|
|
{
|
|
break;
|
|
}
|
|
Buffer++;
|
|
}
|
|
if (*Buffer == TEXT(')'))
|
|
{
|
|
break;
|
|
}
|
|
int32 Key = FCString::Atoi(Start);
|
|
if (*Buffer)
|
|
{
|
|
Buffer++;
|
|
}
|
|
Start = Buffer;
|
|
while (*Buffer && *Buffer != TEXT(')'))
|
|
{
|
|
Buffer++;
|
|
}
|
|
double Value = FCString::Atod(Start);
|
|
|
|
if (*Buffer)
|
|
{
|
|
Buffer++;
|
|
}
|
|
Doubles.Add(Key, Value);
|
|
}
|
|
if (*Buffer)
|
|
{
|
|
Buffer++;
|
|
}
|
|
return true;
|
|
}
|
|
bool SerializeFromMismatchedTag(struct FPropertyTag const& Tag, FArchive& Ar)
|
|
{
|
|
// no example of this provided, doesn't make sense
|
|
return false;
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct TStructOpsTypeTraits<FTestStruct> : public TStructOpsTypeTraitsBase2<FTestStruct>
|
|
{
|
|
enum
|
|
{
|
|
WithZeroConstructor = true,
|
|
WithSerializer = true,
|
|
WithPostSerialize = true,
|
|
WithCopy = true,
|
|
WithIdenticalViaEquality = true,
|
|
//WithIdentical = true,
|
|
WithExportTextItem = true,
|
|
WithImportTextItem = true,
|
|
WithAddStructReferencedObjects = true,
|
|
WithSerializeFromMismatchedTag = true,
|
|
};
|
|
};
|
|
|
|
#endif
|
|
|
|
/** Used to hold virtual methods to construct, destruct, etc native structs in a generic and dynamic fashion
|
|
* singleton-style to avoid issues with static constructor order
|
|
**/
|
|
static TMap<FName, UScriptStruct::ICppStructOps*>& GetDeferredCppStructOps()
|
|
{
|
|
static struct TMapWithAutoCleanup: public TMap<FName, UScriptStruct::ICppStructOps*>
|
|
{
|
|
~TMapWithAutoCleanup()
|
|
{
|
|
for (ElementSetType::TConstIterator It(Pairs); It; ++It)
|
|
{
|
|
delete It->Value;
|
|
}
|
|
}
|
|
} DeferredCppStructOps;
|
|
return DeferredCppStructOps;
|
|
}
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
bool FindConstructorUninitialized(UStruct* BaseClass, uint8* Data, uint8* Defaults)
|
|
{
|
|
bool bAnyProblem = false;
|
|
static TSet<FString> PrintedWarnings;
|
|
for (FProperty* P = BaseClass->PropertyLink; P; P = P->PropertyLinkNext)
|
|
{
|
|
int32 Size = P->GetSize();
|
|
bool bProblem = false;
|
|
check(Size);
|
|
FBoolProperty* PB = CastField<FBoolProperty>(P);
|
|
FStructProperty* PS = CastField<FStructProperty>(P);
|
|
FStrProperty* PStr = CastField<FStrProperty>(P);
|
|
FArrayProperty* PArray = CastField<FArrayProperty>(P);
|
|
if (PStr)
|
|
{
|
|
// string that actually have data would be false positives, since they would point to the same string, but actually be different pointers
|
|
// string is known to have a good default constructor
|
|
}
|
|
else if (PB)
|
|
{
|
|
check(Size == PB->ElementSize);
|
|
if (PB->GetPropertyValue_InContainer(Data) && !PB->GetPropertyValue_InContainer(Defaults))
|
|
{
|
|
bProblem = true;
|
|
}
|
|
}
|
|
else if (PS)
|
|
{
|
|
// these are legitimate exceptions
|
|
if (PS->Struct->GetName() != TEXT("BitArray") && PS->Struct->GetName() != TEXT("SparseArray") && PS->Struct->GetName() != TEXT("Set") && PS->Struct->GetName() != TEXT("Map") && PS->Struct->GetName() != TEXT("MultiMap") && PS->Struct->GetName() != TEXT("ShowFlags_Mirror") && PS->Struct->GetName() != TEXT("Pointer"))
|
|
{
|
|
bProblem = FindConstructorUninitialized(PS->Struct, P->ContainerPtrToValuePtr<uint8>(Data), P->ContainerPtrToValuePtr<uint8>(Defaults));
|
|
}
|
|
}
|
|
else if (PArray)
|
|
{
|
|
bProblem = !PArray->Identical_InContainer(Data, Defaults);
|
|
}
|
|
else
|
|
{
|
|
if (FMemory::Memcmp(P->ContainerPtrToValuePtr<uint8>(Data), P->ContainerPtrToValuePtr<uint8>(Defaults), Size) != 0)
|
|
{
|
|
// UE_LOG(LogClass, Warning,TEXT("Mismatch %d %d"),(int32)*(Data + P->Offset), (int32)*(Defaults + P->Offset));
|
|
bProblem = true;
|
|
}
|
|
}
|
|
if (bProblem)
|
|
{
|
|
FString Issue;
|
|
if (PS)
|
|
{
|
|
Issue = TEXT(" From ");
|
|
Issue += P->GetFullName();
|
|
}
|
|
else
|
|
{
|
|
Issue = BaseClass->GetPathName() + TEXT(",") + P->GetFullName();
|
|
}
|
|
if (!PrintedWarnings.Contains(Issue))
|
|
{
|
|
bAnyProblem = true;
|
|
PrintedWarnings.Add(Issue);
|
|
if (PS)
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("%s"), *Issue);
|
|
// OutputDebugStringW(*FString::Printf(TEXT("%s\n"),*Issue));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("Native constructor does not initialize all properties %s (may need to recompile excutable with new headers)"), *Issue);
|
|
// OutputDebugStringW(*FString::Printf(TEXT("Native contructor does not initialize all properties %s\n"),*Issue));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bAnyProblem;
|
|
}
|
|
#endif
|
|
|
|
UScriptStruct::UScriptStruct(EStaticConstructor, int32 InSize, int32 InAlignment, EObjectFlags InFlags)
|
|
: UStruct(EC_StaticConstructor, InSize, InAlignment, InFlags), StructFlags(STRUCT_NoFlags)
|
|
#if HACK_HEADER_GENERATOR
|
|
,
|
|
StructMacroDeclaredLineNumber(INDEX_NONE)
|
|
#endif
|
|
,
|
|
bPrepareCppStructOpsCompleted(false),
|
|
CppStructOps(NULL)
|
|
{
|
|
}
|
|
|
|
UScriptStruct::UScriptStruct(const FObjectInitializer& ObjectInitializer, UScriptStruct* InSuperStruct, ICppStructOps* InCppStructOps, EStructFlags InStructFlags, SIZE_T ExplicitSize, SIZE_T ExplicitAlignment)
|
|
: UStruct(ObjectInitializer, InSuperStruct, InCppStructOps ? InCppStructOps->GetSize() : ExplicitSize, InCppStructOps ? InCppStructOps->GetAlignment() : ExplicitAlignment), StructFlags(EStructFlags(InStructFlags | (InCppStructOps ? STRUCT_Native : STRUCT_NoFlags)))
|
|
#if HACK_HEADER_GENERATOR
|
|
,
|
|
StructMacroDeclaredLineNumber(INDEX_NONE)
|
|
#endif
|
|
,
|
|
bPrepareCppStructOpsCompleted(false),
|
|
CppStructOps(InCppStructOps)
|
|
{
|
|
PrepareCppStructOps(); // propgate flags, etc
|
|
}
|
|
|
|
UScriptStruct::UScriptStruct(const FObjectInitializer& ObjectInitializer)
|
|
: UStruct(ObjectInitializer), StructFlags(STRUCT_NoFlags)
|
|
#if HACK_HEADER_GENERATOR
|
|
,
|
|
StructMacroDeclaredLineNumber(INDEX_NONE)
|
|
#endif
|
|
,
|
|
bPrepareCppStructOpsCompleted(false),
|
|
CppStructOps(NULL)
|
|
{
|
|
}
|
|
|
|
/** Stash a CppStructOps for future use
|
|
* @param Target Name of the struct
|
|
* @param InCppStructOps Cpp ops for this struct
|
|
**/
|
|
void UScriptStruct::DeferCppStructOps(FName Target, ICppStructOps* InCppStructOps)
|
|
{
|
|
TMap<FName, UScriptStruct::ICppStructOps*>& DeferredStructOps = GetDeferredCppStructOps();
|
|
|
|
if (UScriptStruct::ICppStructOps* ExistingOps = DeferredStructOps.FindRef(Target))
|
|
{
|
|
#if WITH_HOT_RELOAD
|
|
if (!GIsHotReload) // in hot reload, we will just leak these...they may be in use.
|
|
#endif
|
|
{
|
|
check(ExistingOps != InCppStructOps); // if it was equal, then we would be re-adding a now stale pointer to the map
|
|
delete ExistingOps;
|
|
}
|
|
}
|
|
DeferredStructOps.Add(Target, InCppStructOps);
|
|
}
|
|
|
|
/** Look for the CppStructOps if we don't already have it and set the property size **/
|
|
void UScriptStruct::PrepareCppStructOps()
|
|
{
|
|
if (bPrepareCppStructOpsCompleted)
|
|
{
|
|
return;
|
|
}
|
|
if (!CppStructOps)
|
|
{
|
|
CppStructOps = GetDeferredCppStructOps().FindRef(GetFName());
|
|
if (!CppStructOps)
|
|
{
|
|
if (!GIsUCCMakeStandaloneHeaderGenerator && (StructFlags & STRUCT_Native))
|
|
{
|
|
UE_LOG(LogClass, Fatal, TEXT("Couldn't bind to native struct %s. Headers need to be rebuilt, or a noexport class is missing a IMPLEMENT_STRUCT."), *GetName());
|
|
}
|
|
check(!bPrepareCppStructOpsCompleted); // recursion is unacceptable
|
|
bPrepareCppStructOpsCompleted = true;
|
|
return;
|
|
}
|
|
#if !HACK_HEADER_GENERATOR
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_Native);
|
|
#endif
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
// test that the constructor is initializing everything
|
|
if (!CppStructOps->HasZeroConstructor()
|
|
#if WITH_HOT_RELOAD
|
|
&& !GIsHotReload // in hot reload, these produce bogus warnings
|
|
#endif
|
|
)
|
|
{
|
|
int32 Size = CppStructOps->GetSize();
|
|
uint8* TestData00 = (uint8*)FMemory::Malloc(Size);
|
|
FMemory::Memzero(TestData00, Size);
|
|
CppStructOps->Construct(TestData00);
|
|
uint8* TestDataFF = (uint8*)FMemory::Malloc(Size);
|
|
FMemory::Memset(TestDataFF, 0xff, Size);
|
|
CppStructOps->Construct(TestDataFF);
|
|
|
|
if (FMemory::Memcmp(TestData00, TestDataFF, Size) != 0)
|
|
{
|
|
FindConstructorUninitialized(this, TestData00, TestDataFF);
|
|
}
|
|
if (CppStructOps->HasDestructor())
|
|
{
|
|
CppStructOps->Destruct(TestData00);
|
|
CppStructOps->Destruct(TestDataFF);
|
|
}
|
|
FMemory::Free(TestData00);
|
|
FMemory::Free(TestDataFF);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
check(!(StructFlags & STRUCT_ComputedFlags));
|
|
if (CppStructOps->HasSerializer() || CppStructOps->HasStructuredSerializer())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has a custom serializer."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_SerializeNative);
|
|
}
|
|
if (CppStructOps->HasPostSerialize())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s wants post serialize."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_PostSerializeNative);
|
|
}
|
|
if (CppStructOps->HasPostScriptConstruct())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s wants post script construct."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_PostScriptConstruct);
|
|
}
|
|
if (CppStructOps->HasNetSerializer())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has a custom net serializer."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_NetSerializeNative);
|
|
|
|
if (CppStructOps->HasNetSharedSerialization())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s can share net serialization."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_NetSharedSerialization);
|
|
}
|
|
}
|
|
if (CppStructOps->HasNetDeltaSerializer())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has a custom net delta serializer."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_NetDeltaSerializeNative);
|
|
}
|
|
|
|
if (CppStructOps->IsPlainOldData())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s is plain old data."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_IsPlainOldData | STRUCT_NoDestructor);
|
|
}
|
|
else
|
|
{
|
|
if (CppStructOps->HasCopy())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has a native copy."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_CopyNative);
|
|
}
|
|
if (!CppStructOps->HasDestructor())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has no destructor."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_NoDestructor);
|
|
}
|
|
}
|
|
if (CppStructOps->HasZeroConstructor())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has zero construction."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_ZeroConstructor);
|
|
}
|
|
if (CppStructOps->IsPlainOldData() && !CppStructOps->HasZeroConstructor())
|
|
{
|
|
// hmm, it is safe to see if this can be zero constructed, lets try
|
|
int32 Size = CppStructOps->GetSize();
|
|
uint8* TestData00 = (uint8*)FMemory::Malloc(Size);
|
|
FMemory::Memzero(TestData00, Size);
|
|
CppStructOps->Construct(TestData00);
|
|
CppStructOps->Construct(TestData00); // slightly more like to catch "internal counters" if we do this twice
|
|
bool IsZeroConstruct = true;
|
|
for (int32 Index = 0; Index < Size && IsZeroConstruct; Index++)
|
|
{
|
|
if (TestData00[Index])
|
|
{
|
|
IsZeroConstruct = false;
|
|
}
|
|
}
|
|
FMemory::Free(TestData00);
|
|
if (IsZeroConstruct)
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has DISCOVERED zero construction. Size = %d"), *GetName(), Size);
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_ZeroConstructor);
|
|
}
|
|
}
|
|
if (CppStructOps->HasIdentical())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has native identical."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_IdenticalNative);
|
|
}
|
|
if (CppStructOps->HasAddStructReferencedObjects())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has native AddStructReferencedObjects."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_AddStructReferencedObjects);
|
|
}
|
|
if (CppStructOps->HasExportTextItem())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has native ExportTextItem."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_ExportTextItemNative);
|
|
}
|
|
if (CppStructOps->HasImportTextItem())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has native ImportTextItem."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_ImportTextItemNative);
|
|
}
|
|
if (CppStructOps->HasSerializeFromMismatchedTag() || CppStructOps->HasStructuredSerializeFromMismatchedTag())
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Native struct %s has native SerializeFromMismatchedTag."), *GetName());
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_SerializeFromMismatchedTag);
|
|
}
|
|
|
|
check(!bPrepareCppStructOpsCompleted); // recursion is unacceptable
|
|
bPrepareCppStructOpsCompleted = true;
|
|
}
|
|
|
|
IMPLEMENT_FSTRUCTUREDARCHIVE_SERIALIZER(UScriptStruct);
|
|
|
|
void UScriptStruct::Serialize(FArchive& Ar)
|
|
{
|
|
Super::Serialize(Ar);
|
|
|
|
// serialize the struct's flags
|
|
Ar << (uint32&)StructFlags;
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
ClearCppStructOps(); // we want to be sure to do this from scratch
|
|
PrepareCppStructOps();
|
|
}
|
|
}
|
|
|
|
bool UScriptStruct::UseBinarySerialization(const FArchive& Ar) const
|
|
{
|
|
return !(Ar.IsLoading() || Ar.IsSaving()) || Ar.WantBinaryPropertySerialization() || (0 != (StructFlags & STRUCT_Immutable));
|
|
}
|
|
|
|
void UScriptStruct::SerializeItem(FArchive& Ar, void* Value, void const* Defaults)
|
|
{
|
|
SerializeItem(FStructuredArchiveFromArchive(Ar).GetSlot(), Value, Defaults);
|
|
}
|
|
|
|
void UScriptStruct::SerializeItem(FStructuredArchive::FSlot Slot, void* Value, void const* Defaults)
|
|
{
|
|
FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive();
|
|
|
|
const bool bUseBinarySerialization = UseBinarySerialization(UnderlyingArchive);
|
|
const bool bUseNativeSerialization = UseNativeSerialization();
|
|
|
|
// Preload struct before serialization tracking to not double count time.
|
|
if (bUseBinarySerialization || bUseNativeSerialization)
|
|
{
|
|
UnderlyingArchive.Preload(this);
|
|
}
|
|
|
|
bool bItemSerialized = false;
|
|
if (bUseNativeSerialization)
|
|
{
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
|
|
check(TheCppStructOps); // else should not have STRUCT_SerializeNative
|
|
|
|
if (TheCppStructOps->HasStructuredSerializer())
|
|
{
|
|
bItemSerialized = TheCppStructOps->Serialize(Slot, Value);
|
|
}
|
|
else
|
|
{
|
|
#if WITH_TEXT_ARCHIVE_SUPPORT
|
|
FArchiveUObjectFromStructuredArchive Adapter(Slot);
|
|
FArchive& Ar = Adapter.GetArchive();
|
|
bItemSerialized = TheCppStructOps->Serialize(Ar, Value);
|
|
if (bItemSerialized && !Slot.IsFilled())
|
|
{
|
|
// The struct said that serialization succeeded but it didn't actually write anything.
|
|
Slot.EnterRecord();
|
|
}
|
|
Adapter.Close();
|
|
#else
|
|
bItemSerialized = TheCppStructOps->Serialize(Slot.GetUnderlyingArchive(), Value);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (!bItemSerialized)
|
|
{
|
|
if (bUseBinarySerialization)
|
|
{
|
|
// Struct is already preloaded above.
|
|
if (!UnderlyingArchive.IsPersistent() && UnderlyingArchive.GetPortFlags() != 0 && !ShouldSerializeAtomically(UnderlyingArchive) && !UnderlyingArchive.ArUseCustomPropertyList)
|
|
{
|
|
SerializeBinEx(Slot, Value, Defaults, this);
|
|
}
|
|
else
|
|
{
|
|
SerializeBin(Slot, Value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SerializeTaggedProperties(Slot, (uint8*)Value, this, (uint8*)Defaults);
|
|
}
|
|
}
|
|
|
|
if (StructFlags & STRUCT_PostSerializeNative)
|
|
{
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
|
|
check(TheCppStructOps); // else should not have STRUCT_PostSerializeNative
|
|
TheCppStructOps->PostSerialize(UnderlyingArchive, Value);
|
|
}
|
|
}
|
|
|
|
const TCHAR* UScriptStruct::ImportText(const TCHAR* InBuffer, void* Value, UObject* OwnerObject, int32 PortFlags, FOutputDevice* ErrorText, const FString& StructName, bool bAllowNativeOverride)
|
|
{
|
|
return ImportText(InBuffer, Value, OwnerObject, PortFlags, ErrorText, [&StructName]()
|
|
{
|
|
return StructName;
|
|
},
|
|
bAllowNativeOverride);
|
|
}
|
|
|
|
const TCHAR* UScriptStruct::ImportText(const TCHAR* InBuffer, void* Value, UObject* OwnerObject, int32 PortFlags, FOutputDevice* ErrorText, const TFunctionRef<FString()>& StructNameGetter, bool bAllowNativeOverride)
|
|
{
|
|
if (bAllowNativeOverride && StructFlags & STRUCT_ImportTextItemNative)
|
|
{
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
|
|
check(TheCppStructOps); // else should not have STRUCT_ImportTextItemNative
|
|
if (TheCppStructOps->ImportTextItem(InBuffer, Value, PortFlags, OwnerObject, ErrorText))
|
|
{
|
|
return InBuffer;
|
|
}
|
|
}
|
|
|
|
TArray<FDefinedProperty> DefinedProperties;
|
|
// this keeps track of the number of errors we've logged, so that we can add new lines when logging more than one error
|
|
int32 ErrorCount = 0;
|
|
const TCHAR* Buffer = InBuffer;
|
|
if (*Buffer++ == TCHAR('('))
|
|
{
|
|
// Parse all properties.
|
|
while (*Buffer != TCHAR(')'))
|
|
{
|
|
// parse and import the value
|
|
Buffer = FProperty::ImportSingleProperty(Buffer, Value, this, OwnerObject, PortFlags | PPF_Delimited, ErrorText, DefinedProperties);
|
|
|
|
// skip any remaining text before the next property value
|
|
SkipWhitespace(Buffer);
|
|
int32 SubCount = 0;
|
|
while (*Buffer && *Buffer != TCHAR('\r') && *Buffer != TCHAR('\n') &&
|
|
(SubCount > 0 || *Buffer != TCHAR(')')) && (SubCount > 0 || *Buffer != TCHAR(',')))
|
|
{
|
|
SkipWhitespace(Buffer);
|
|
if (*Buffer == TCHAR('\"'))
|
|
{
|
|
do
|
|
{
|
|
Buffer++;
|
|
} while (*Buffer && *Buffer != TCHAR('\"') && *Buffer != TCHAR('\n') && *Buffer != TCHAR('\r'));
|
|
|
|
if (*Buffer != TCHAR('\"'))
|
|
{
|
|
ErrorText->Logf(TEXT("%sImportText (%s): Bad quoted string at: %s"), ErrorCount++ > 0 ? LINE_TERMINATOR : TEXT(""), *StructNameGetter(), Buffer);
|
|
return nullptr;
|
|
}
|
|
}
|
|
else if (*Buffer == TCHAR('('))
|
|
{
|
|
SubCount++;
|
|
}
|
|
else if (*Buffer == TCHAR(')'))
|
|
{
|
|
SubCount--;
|
|
if (SubCount < 0)
|
|
{
|
|
ErrorText->Logf(TEXT("%sImportText (%s): Too many closing parenthesis in: %s"), ErrorCount++ > 0 ? LINE_TERMINATOR : TEXT(""), *StructNameGetter(), InBuffer);
|
|
return nullptr;
|
|
}
|
|
}
|
|
Buffer++;
|
|
}
|
|
if (SubCount > 0)
|
|
{
|
|
ErrorText->Logf(TEXT("%sImportText(%s): Not enough closing parenthesis in: %s"), ErrorCount++ > 0 ? LINE_TERMINATOR : TEXT(""), *StructNameGetter(), InBuffer);
|
|
return nullptr;
|
|
}
|
|
|
|
// Skip comma.
|
|
if (*Buffer == TCHAR(','))
|
|
{
|
|
// Skip comma.
|
|
Buffer++;
|
|
}
|
|
else if (*Buffer != TCHAR(')'))
|
|
{
|
|
ErrorText->Logf(TEXT("%sImportText (%s): Missing closing parenthesis: %s"), ErrorCount++ > 0 ? LINE_TERMINATOR : TEXT(""), *StructNameGetter(), InBuffer);
|
|
return nullptr;
|
|
}
|
|
|
|
SkipWhitespace(Buffer);
|
|
}
|
|
|
|
// Skip trailing ')'.
|
|
Buffer++;
|
|
}
|
|
else
|
|
{
|
|
ErrorText->Logf(TEXT("%sImportText (%s): Missing opening parenthesis: %s"), ErrorCount++ > 0 ? LINE_TERMINATOR : TEXT(""), *StructNameGetter(), InBuffer); //-V547
|
|
return nullptr;
|
|
}
|
|
return Buffer;
|
|
}
|
|
|
|
void UScriptStruct::ExportText(FString& ValueStr, const void* Value, const void* Defaults, UObject* OwnerObject, int32 PortFlags, UObject* ExportRootScope, bool bAllowNativeOverride) const
|
|
{
|
|
if (bAllowNativeOverride && StructFlags & STRUCT_ExportTextItemNative)
|
|
{
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
|
|
check(TheCppStructOps); // else should not have STRUCT_ExportTextItemNative
|
|
if (TheCppStructOps->ExportTextItem(ValueStr, Value, Defaults, OwnerObject, PortFlags, ExportRootScope))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (0 != (PortFlags & PPF_ExportCpp))
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 Count = 0;
|
|
|
|
// if this struct is configured to be serialized as a unit, it must be exported as a unit as well.
|
|
if ((StructFlags & STRUCT_Atomic) != 0)
|
|
{
|
|
// change Defaults to match Value so that ExportText always exports this item
|
|
Defaults = Value;
|
|
}
|
|
|
|
for (TFieldIterator<FProperty> It(this); It; ++It)
|
|
{
|
|
if (It->ShouldPort(PortFlags))
|
|
{
|
|
for (int32 Index = 0; Index < It->ArrayDim; Index++)
|
|
{
|
|
FString InnerValue;
|
|
if (It->ExportText_InContainer(Index, InnerValue, Value, Defaults, OwnerObject, PPF_Delimited | PortFlags, ExportRootScope))
|
|
{
|
|
Count++;
|
|
if (Count == 1)
|
|
{
|
|
ValueStr += TCHAR('(');
|
|
}
|
|
else if ((PortFlags & PPF_BlueprintDebugView) == 0)
|
|
{
|
|
ValueStr += TCHAR(',');
|
|
}
|
|
else
|
|
{
|
|
ValueStr += TEXT(",\n");
|
|
}
|
|
|
|
const FString PropertyName = (PortFlags & (PPF_ExternalEditor | PPF_BlueprintDebugView)) != 0 ? It->GetAuthoredName() : It->GetName();
|
|
|
|
if (It->ArrayDim == 1)
|
|
{
|
|
ValueStr += FString::Printf(TEXT("%s="), *PropertyName);
|
|
}
|
|
else
|
|
{
|
|
ValueStr += FString::Printf(TEXT("%s[%i]="), *PropertyName, Index);
|
|
}
|
|
ValueStr += InnerValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Count > 0)
|
|
{
|
|
ValueStr += TEXT(")");
|
|
}
|
|
else
|
|
{
|
|
ValueStr += TEXT("()");
|
|
}
|
|
}
|
|
|
|
void UScriptStruct::Link(FArchive& Ar, bool bRelinkExistingProperties)
|
|
{
|
|
Super::Link(Ar, bRelinkExistingProperties);
|
|
SetStructTrashed(false);
|
|
if (!HasDefaults()) // if you have CppStructOps, then that is authoritative, otherwise we look at the properties
|
|
{
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_ZeroConstructor | STRUCT_NoDestructor | STRUCT_IsPlainOldData);
|
|
for (FProperty* Property = PropertyLink; Property; Property = Property->PropertyLinkNext)
|
|
{
|
|
if (!Property->HasAnyPropertyFlags(CPF_ZeroConstructor))
|
|
{
|
|
StructFlags = EStructFlags(StructFlags & ~STRUCT_ZeroConstructor);
|
|
}
|
|
if (!Property->HasAnyPropertyFlags(CPF_NoDestructor))
|
|
{
|
|
StructFlags = EStructFlags(StructFlags & ~STRUCT_NoDestructor);
|
|
}
|
|
if (!Property->HasAnyPropertyFlags(CPF_IsPlainOldData))
|
|
{
|
|
StructFlags = EStructFlags(StructFlags & ~STRUCT_IsPlainOldData);
|
|
}
|
|
}
|
|
if (StructFlags & STRUCT_IsPlainOldData)
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Non-Native struct %s is plain old data."), *GetName());
|
|
}
|
|
if (StructFlags & STRUCT_NoDestructor)
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Non-Native struct %s has no destructor."), *GetName());
|
|
}
|
|
if (StructFlags & STRUCT_ZeroConstructor)
|
|
{
|
|
UE_LOG(LogClass, Verbose, TEXT("Non-Native struct %s has zero construction."), *GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UScriptStruct::CompareScriptStruct(const void* A, const void* B, uint32 PortFlags) const
|
|
{
|
|
check(A);
|
|
|
|
if (nullptr == B) // if the comparand is NULL, we just call this no-match
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StructFlags & STRUCT_IdenticalNative)
|
|
{
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
|
|
check(TheCppStructOps);
|
|
bool bResult = false;
|
|
if (TheCppStructOps->Identical(A, B, PortFlags, bResult))
|
|
{
|
|
return bResult;
|
|
}
|
|
}
|
|
|
|
for (TFieldIterator<FProperty> It(this); It; ++It)
|
|
{
|
|
for (int32 i = 0; i < It->ArrayDim; i++)
|
|
{
|
|
if (!It->Identical_InContainer(A, B, i, PortFlags))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void UScriptStruct::CopyScriptStruct(void* InDest, void const* InSrc, int32 ArrayDim) const
|
|
{
|
|
uint8* Dest = (uint8*)InDest;
|
|
check(Dest);
|
|
uint8 const* Src = (uint8 const*)InSrc;
|
|
check(Src);
|
|
|
|
int32 Stride = GetStructureSize();
|
|
|
|
if (StructFlags & STRUCT_CopyNative)
|
|
{
|
|
check(!(StructFlags & STRUCT_IsPlainOldData)); // should not have both
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
|
|
check(TheCppStructOps);
|
|
check(Stride == TheCppStructOps->GetSize() && PropertiesSize == Stride);
|
|
if (TheCppStructOps->Copy(Dest, Src, ArrayDim))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if (StructFlags & STRUCT_IsPlainOldData)
|
|
{
|
|
FMemory::Memcpy(Dest, Src, ArrayDim * Stride);
|
|
}
|
|
else
|
|
{
|
|
for (TFieldIterator<FProperty> It(this); It; ++It)
|
|
{
|
|
for (int32 Index = 0; Index < ArrayDim; Index++)
|
|
{
|
|
It->CopyCompleteValue_InContainer((uint8*)Dest + Index * Stride, (uint8*)Src + Index * Stride);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 UScriptStruct::GetStructTypeHash(const void* Src) const
|
|
{
|
|
// Calling GetStructTypeHash on struct types that doesn't provide a native
|
|
// GetTypeHash implementation is an error that neither the C++ compiler nor the BP
|
|
// compiler permit. Still, old reflection data could be loaded that invalidly uses
|
|
// unhashable types.
|
|
|
|
// If any the ensure or check in this function fires the fix is to implement GetTypeHash
|
|
// or erase the data. USetProperties and UMapProperties that are loaded from disk
|
|
// will clear themselves when they detect this error (see FSetProperty and
|
|
// FMapProperty::ConvertFromType).
|
|
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
|
|
return TheCppStructOps->GetStructTypeHash(Src);
|
|
}
|
|
|
|
void UScriptStruct::InitializeStruct(void* InDest, int32 ArrayDim) const
|
|
{
|
|
uint8* Dest = (uint8*)InDest;
|
|
check(Dest);
|
|
|
|
int32 Stride = GetStructureSize();
|
|
|
|
//@todo UE4 optimize
|
|
FMemory::Memzero(Dest, ArrayDim * Stride);
|
|
|
|
int32 InitializedSize = 0;
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
|
|
if (TheCppStructOps != NULL)
|
|
{
|
|
if (!TheCppStructOps->HasZeroConstructor())
|
|
{
|
|
for (int32 ArrayIndex = 0; ArrayIndex < ArrayDim; ArrayIndex++)
|
|
{
|
|
void* PropertyDest = Dest + ArrayIndex * Stride;
|
|
checkf(IsAligned(PropertyDest, TheCppStructOps->GetAlignment()),
|
|
TEXT("Destination address for property does not match requirement of %d byte alignment for %s"),
|
|
TheCppStructOps->GetAlignment(),
|
|
*GetPathNameSafe(this));
|
|
TheCppStructOps->Construct(PropertyDest);
|
|
}
|
|
}
|
|
|
|
InitializedSize = TheCppStructOps->GetSize();
|
|
// here we want to make sure C++ and the property system agree on the size
|
|
check(Stride == InitializedSize && PropertiesSize == InitializedSize);
|
|
}
|
|
|
|
if (PropertiesSize > InitializedSize)
|
|
{
|
|
bool bHitBase = false;
|
|
for (FProperty* Property = PropertyLink; Property && !bHitBase; Property = Property->PropertyLinkNext)
|
|
{
|
|
if (!Property->IsInContainer(InitializedSize))
|
|
{
|
|
for (int32 ArrayIndex = 0; ArrayIndex < ArrayDim; ArrayIndex++)
|
|
{
|
|
Property->InitializeValue_InContainer(Dest + ArrayIndex * Stride);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bHitBase = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UScriptStruct::InitializeDefaultValue(uint8* InStructData) const
|
|
{
|
|
InitializeStruct(InStructData);
|
|
}
|
|
|
|
void UScriptStruct::ClearScriptStruct(void* Dest, int32 ArrayDim) const
|
|
{
|
|
uint8* Data = (uint8*)Dest;
|
|
int32 Stride = GetStructureSize();
|
|
|
|
int32 ClearedSize = 0;
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
|
|
if (TheCppStructOps)
|
|
{
|
|
for (int32 ArrayIndex = 0; ArrayIndex < ArrayDim; ArrayIndex++)
|
|
{
|
|
uint8* PropertyData = Data + ArrayIndex * Stride;
|
|
if (TheCppStructOps->HasDestructor())
|
|
{
|
|
TheCppStructOps->Destruct(PropertyData);
|
|
}
|
|
if (TheCppStructOps->HasZeroConstructor())
|
|
{
|
|
FMemory::Memzero(PropertyData, Stride);
|
|
}
|
|
else
|
|
{
|
|
TheCppStructOps->Construct(PropertyData);
|
|
}
|
|
}
|
|
ClearedSize = TheCppStructOps->GetSize();
|
|
// here we want to make sure C++ and the property system agree on the size
|
|
check(Stride == ClearedSize && PropertiesSize == ClearedSize);
|
|
}
|
|
if (PropertiesSize > ClearedSize)
|
|
{
|
|
bool bHitBase = false;
|
|
for (FProperty* Property = PropertyLink; Property && !bHitBase; Property = Property->PropertyLinkNext)
|
|
{
|
|
if (!Property->IsInContainer(ClearedSize))
|
|
{
|
|
for (int32 ArrayIndex = 0; ArrayIndex < ArrayDim; ArrayIndex++)
|
|
{
|
|
for (int32 PropArrayIndex = 0; PropArrayIndex < Property->ArrayDim; PropArrayIndex++)
|
|
{
|
|
Property->ClearValue_InContainer(Data + ArrayIndex * Stride, PropArrayIndex);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bHitBase = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UScriptStruct::DestroyStruct(void* Dest, int32 ArrayDim) const
|
|
{
|
|
if (StructFlags & (STRUCT_IsPlainOldData | STRUCT_NoDestructor))
|
|
{
|
|
return; // POD types don't need destructors
|
|
}
|
|
uint8* Data = (uint8*)Dest;
|
|
int32 Stride = GetStructureSize();
|
|
int32 ClearedSize = 0;
|
|
|
|
UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
|
|
if (TheCppStructOps)
|
|
{
|
|
if (TheCppStructOps->HasDestructor())
|
|
{
|
|
for (int32 ArrayIndex = 0; ArrayIndex < ArrayDim; ArrayIndex++)
|
|
{
|
|
uint8* PropertyData = (uint8*)Dest + ArrayIndex * Stride;
|
|
TheCppStructOps->Destruct(PropertyData);
|
|
}
|
|
}
|
|
ClearedSize = TheCppStructOps->GetSize();
|
|
// here we want to make sure C++ and the property system agree on the size
|
|
check(Stride == ClearedSize && PropertiesSize == ClearedSize);
|
|
}
|
|
|
|
if (PropertiesSize > ClearedSize)
|
|
{
|
|
bool bHitBase = false;
|
|
for (FProperty* P = DestructorLink; P && !bHitBase; P = P->DestructorLinkNext)
|
|
{
|
|
if (!P->IsInContainer(ClearedSize))
|
|
{
|
|
if (!P->HasAnyPropertyFlags(CPF_NoDestructor))
|
|
{
|
|
for (int32 ArrayIndex = 0; ArrayIndex < ArrayDim; ArrayIndex++)
|
|
{
|
|
P->DestroyValue_InContainer(Data + ArrayIndex * Stride);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bHitBase = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UScriptStruct::IsStructTrashed() const
|
|
{
|
|
return !!(StructFlags & STRUCT_Trashed);
|
|
}
|
|
|
|
void UScriptStruct::SetStructTrashed(bool bIsTrash)
|
|
{
|
|
if (bIsTrash)
|
|
{
|
|
StructFlags = EStructFlags(StructFlags | STRUCT_Trashed);
|
|
}
|
|
else
|
|
{
|
|
StructFlags = EStructFlags(StructFlags & ~STRUCT_Trashed);
|
|
}
|
|
}
|
|
|
|
void UScriptStruct::RecursivelyPreload() {}
|
|
|
|
FGuid UScriptStruct::GetCustomGuid() const
|
|
{
|
|
return FGuid();
|
|
}
|
|
|
|
FString UScriptStruct::GetStructCPPName() const
|
|
{
|
|
return FString::Printf(TEXT("F%s"), *GetName());
|
|
}
|
|
|
|
#if !(UE_BUILD_TEST || UE_BUILD_SHIPPING)
|
|
|
|
enum class EScriptStructTestCtorSyntax
|
|
{
|
|
NoInit = 0,
|
|
CompilerZeroed = 1
|
|
};
|
|
|
|
struct FScriptStructTestWrapper
|
|
{
|
|
public:
|
|
FScriptStructTestWrapper(UScriptStruct* InStruct, uint8 InitValue = 0xFD, EScriptStructTestCtorSyntax ConstrutorSyntax = EScriptStructTestCtorSyntax::NoInit)
|
|
: ScriptStruct(InStruct), TempBuffer(nullptr)
|
|
{
|
|
if (ScriptStruct->IsNative())
|
|
{
|
|
UScriptStruct::ICppStructOps* StructOps = ScriptStruct->GetCppStructOps();
|
|
|
|
// Make one
|
|
if ((StructOps != nullptr) && StructOps->HasZeroConstructor())
|
|
{
|
|
// These structs have basically promised to be used safely, not going to audit them
|
|
}
|
|
else
|
|
{
|
|
// Allocate space for the struct
|
|
const int32 RequiredAllocSize = ScriptStruct->GetStructureSize();
|
|
TempBuffer = (uint8*)FMemory::Malloc(RequiredAllocSize, ScriptStruct->GetMinAlignment());
|
|
|
|
// The following section is a partial duplication of ScriptStruct->InitializeStruct, except we initialize with 0xFD instead of 0x00
|
|
FMemory::Memset(TempBuffer, InitValue, RequiredAllocSize);
|
|
|
|
int32 InitializedSize = 0;
|
|
if (StructOps != nullptr)
|
|
{
|
|
if (ConstrutorSyntax == EScriptStructTestCtorSyntax::NoInit)
|
|
{
|
|
StructOps->ConstructForTests(TempBuffer);
|
|
}
|
|
else
|
|
{
|
|
StructOps->Construct(TempBuffer);
|
|
}
|
|
InitializedSize = StructOps->GetSize();
|
|
}
|
|
|
|
if (ScriptStruct->PropertiesSize > InitializedSize)
|
|
{
|
|
bool bHitBase = false;
|
|
for (FProperty* Property = ScriptStruct->PropertyLink; Property && !bHitBase; Property = Property->PropertyLinkNext)
|
|
{
|
|
if (!Property->IsInContainer(InitializedSize))
|
|
{
|
|
Property->InitializeValue_InContainer(TempBuffer);
|
|
}
|
|
else
|
|
{
|
|
bHitBase = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ScriptStruct->StructFlags & STRUCT_PostScriptConstruct)
|
|
{
|
|
check(StructOps);
|
|
StructOps->PostScriptConstruct(TempBuffer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
~FScriptStructTestWrapper()
|
|
{
|
|
if (TempBuffer != nullptr)
|
|
{
|
|
// Destroy it
|
|
ScriptStruct->DestroyStruct(TempBuffer);
|
|
FMemory::Free(TempBuffer);
|
|
}
|
|
}
|
|
|
|
static bool CanRunTests(UScriptStruct* Struct)
|
|
{
|
|
return (Struct != nullptr) && Struct->IsNative() && (!Struct->GetCppStructOps() || !Struct->GetCppStructOps()->HasZeroConstructor());
|
|
}
|
|
|
|
uint8* GetData() { return TempBuffer; }
|
|
|
|
private:
|
|
UScriptStruct* ScriptStruct;
|
|
uint8* TempBuffer;
|
|
};
|
|
|
|
static void FindUninitializedScriptStructMembers(UScriptStruct* ScriptStruct, EScriptStructTestCtorSyntax ConstructorSyntax, TSet<const FProperty*>& OutUninitializedProperties)
|
|
{
|
|
FScriptStructTestWrapper WrapperFF(ScriptStruct, 0xFF, ConstructorSyntax);
|
|
FScriptStructTestWrapper Wrapper00(ScriptStruct, 0x00, ConstructorSyntax);
|
|
FScriptStructTestWrapper WrapperAA(ScriptStruct, 0xAA, ConstructorSyntax);
|
|
FScriptStructTestWrapper Wrapper55(ScriptStruct, 0x55, ConstructorSyntax);
|
|
|
|
const void* BadPointer = (void*)0xFFFFFFFFFFFFFFFFull;
|
|
|
|
for (const FProperty* Property: TFieldRange<FProperty>(ScriptStruct, EFieldIteratorFlags::ExcludeSuper))
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
static const FName NAME_IgnoreForMemberInitializationTest(TEXT("IgnoreForMemberInitializationTest"));
|
|
if (Property->HasMetaData(NAME_IgnoreForMemberInitializationTest))
|
|
{
|
|
continue;
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
if (const FObjectPropertyBase* ObjectProperty = CastField<const FObjectPropertyBase>(Property))
|
|
{
|
|
// Check any reflected pointer properties to make sure they got initialized
|
|
const UObject* PropValue = ObjectProperty->GetObjectPropertyValue_InContainer(WrapperFF.GetData());
|
|
if (PropValue == BadPointer)
|
|
{
|
|
OutUninitializedProperties.Add(Property);
|
|
}
|
|
}
|
|
else if (const FBoolProperty* BoolProperty = CastField<const FBoolProperty>(Property))
|
|
{
|
|
// Check for uninitialized boolean properties (done separately to deal with byte-wide booleans that would evaluate to true with either 0x55 or 0xAA)
|
|
const bool bValue0 = BoolProperty->GetPropertyValue_InContainer(Wrapper00.GetData());
|
|
const bool bValue1 = BoolProperty->GetPropertyValue_InContainer(WrapperFF.GetData());
|
|
|
|
if (bValue0 != bValue1)
|
|
{
|
|
OutUninitializedProperties.Add(Property);
|
|
}
|
|
}
|
|
else if (Property->IsA(FNameProperty::StaticClass()))
|
|
{
|
|
// Skip some other types that will crash in equality with garbage data
|
|
//@TODO: Shouldn't need to skip FName, it's got a default ctor that initializes correctly...
|
|
}
|
|
else
|
|
{
|
|
bool bShouldInspect = true;
|
|
if (Property->IsA(FStructProperty::StaticClass()))
|
|
{
|
|
// Skip user defined structs since we will consider those structs directly.
|
|
// Calling again here will just result in false positives
|
|
const FStructProperty* StructProperty = CastField<FStructProperty>(Property);
|
|
bShouldInspect = (StructProperty->Struct->StructFlags & STRUCT_NoExport) != 0;
|
|
}
|
|
|
|
if (bShouldInspect)
|
|
{
|
|
// Catch all remaining properties
|
|
|
|
// Uncomment the following line to aid finding crash sources encountered while running this test. A crash usually indicates an uninitialized pointer
|
|
// UE_LOG(LogClass, Log, TEXT("Testing %s%s::%s for proper initialization"), ScriptStruct->GetPrefixCPP(), *ScriptStruct->GetName(), *Property->GetNameCPP());
|
|
if (!Property->Identical_InContainer(WrapperAA.GetData(), Wrapper55.GetData()))
|
|
{
|
|
OutUninitializedProperties.Add(Property);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 FStructUtils::AttemptToFindUninitializedScriptStructMembers()
|
|
{
|
|
auto GetStructLocation = [](const UScriptStruct* ScriptStruct) -> FString
|
|
{
|
|
check(ScriptStruct);
|
|
UPackage* ScriptPackage = ScriptStruct->GetOutermost();
|
|
FString StructLocation = FString::Printf(TEXT(" Module:%s"), *FPackageName::GetShortName(ScriptPackage->GetName()));
|
|
#if WITH_EDITORONLY_DATA
|
|
static const FName NAME_ModuleRelativePath(TEXT("ModuleRelativePath"));
|
|
const FString& ModuleRelativeIncludePath = ScriptStruct->GetMetaData(NAME_ModuleRelativePath);
|
|
if (!ModuleRelativeIncludePath.IsEmpty())
|
|
{
|
|
StructLocation += FString::Printf(TEXT(" File:%s"), *ModuleRelativeIncludePath);
|
|
}
|
|
#endif
|
|
return StructLocation;
|
|
};
|
|
|
|
int32 UninitializedScriptStructMemberCount = 0;
|
|
int32 UninitializedObjectPropertyCount = 0;
|
|
UScriptStruct* TestUninitializedScriptStructMembersTestStruct = TBaseStructure<FTestUninitializedScriptStructMembersTest>::Get();
|
|
check(TestUninitializedScriptStructMembersTestStruct != nullptr);
|
|
|
|
{
|
|
const void* BadPointer = (void*)0xFFFFFFFFFFFFFFFFull;
|
|
|
|
// First test if the tests aren't broken
|
|
FScriptStructTestWrapper WrapperFF(TestUninitializedScriptStructMembersTestStruct, 0xFF);
|
|
const FObjectPropertyBase* UninitializedProperty = CastFieldChecked<const FObjectPropertyBase>(TestUninitializedScriptStructMembersTestStruct->FindPropertyByName(TEXT("UninitializedObjectReference")));
|
|
const FObjectPropertyBase* InitializedProperty = CastFieldChecked<const FObjectPropertyBase>(TestUninitializedScriptStructMembersTestStruct->FindPropertyByName(TEXT("InitializedObjectReference")));
|
|
|
|
const UObject* UninitializedPropValue = UninitializedProperty->GetObjectPropertyValue_InContainer(WrapperFF.GetData());
|
|
if (UninitializedPropValue != BadPointer)
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("ObjectProperty %s%s::%s seems to be initialized properly but it shouldn't be. Verify that AttemptToFindUninitializedScriptStructMembers() is working properly"),
|
|
TestUninitializedScriptStructMembersTestStruct->GetPrefixCPP(), *TestUninitializedScriptStructMembersTestStruct->GetName(), *UninitializedProperty->GetNameCPP());
|
|
}
|
|
const UObject* InitializedPropValue = InitializedProperty->GetObjectPropertyValue_InContainer(WrapperFF.GetData());
|
|
if (InitializedPropValue != nullptr)
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("ObjectProperty %s%s::%s seems to be not initialized properly but it should be. Verify that AttemptToFindUninitializedScriptStructMembers() is working properly"),
|
|
TestUninitializedScriptStructMembersTestStruct->GetPrefixCPP(), *TestUninitializedScriptStructMembersTestStruct->GetName(), *InitializedProperty->GetNameCPP());
|
|
}
|
|
}
|
|
|
|
TSet<const FProperty*> UninitializedPropertiesNoInit;
|
|
TSet<const FProperty*> UninitializedPropertiesZeroed;
|
|
for (TObjectIterator<UScriptStruct> ScriptIt; ScriptIt; ++ScriptIt)
|
|
{
|
|
UScriptStruct* ScriptStruct = *ScriptIt;
|
|
|
|
if (FScriptStructTestWrapper::CanRunTests(ScriptStruct) && ScriptStruct != TestUninitializedScriptStructMembersTestStruct)
|
|
{
|
|
UninitializedPropertiesNoInit.Reset();
|
|
UninitializedPropertiesZeroed.Reset();
|
|
|
|
// Test the struct by constructing it with 'new FMyStruct();' syntax first. The compiler should zero all members in this case if the
|
|
// struct doesn't have a custom default constructor defined
|
|
FindUninitializedScriptStructMembers(ScriptStruct, EScriptStructTestCtorSyntax::CompilerZeroed, UninitializedPropertiesZeroed);
|
|
// Test the struct by constructing it with 'new FStruct;' syntax in which case the compiler doesn't zero the properties automatically
|
|
FindUninitializedScriptStructMembers(ScriptStruct, EScriptStructTestCtorSyntax::NoInit, UninitializedPropertiesNoInit);
|
|
|
|
for (const FProperty* Property: UninitializedPropertiesZeroed)
|
|
{
|
|
++UninitializedScriptStructMemberCount;
|
|
if (Property->IsA<FObjectPropertyBase>())
|
|
{
|
|
++UninitializedObjectPropertyCount;
|
|
}
|
|
UE_LOG(LogClass, Warning, TEXT("%s %s%s::%s is not initialized properly even though its struct probably has a custom default constructor.%s"), *Property->GetClass()->GetName(), ScriptStruct->GetPrefixCPP(), *ScriptStruct->GetName(), *Property->GetNameCPP(), *GetStructLocation(ScriptStruct));
|
|
}
|
|
for (const FProperty* Property: UninitializedPropertiesNoInit)
|
|
{
|
|
if (!UninitializedPropertiesZeroed.Contains(Property))
|
|
{
|
|
++UninitializedScriptStructMemberCount;
|
|
if (Property->IsA<FObjectPropertyBase>())
|
|
{
|
|
++UninitializedObjectPropertyCount;
|
|
UE_LOG(LogClass, Warning, TEXT("%s %s%s::%s is not initialized properly.%s"), *Property->GetClass()->GetName(), ScriptStruct->GetPrefixCPP(), *ScriptStruct->GetName(), *Property->GetNameCPP(), *GetStructLocation(ScriptStruct));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogClass, Display, TEXT("%s %s%s::%s is not initialized properly.%s"), *Property->GetClass()->GetName(), ScriptStruct->GetPrefixCPP(), *ScriptStruct->GetName(), *Property->GetNameCPP(), *GetStructLocation(ScriptStruct));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (UninitializedScriptStructMemberCount > 0)
|
|
{
|
|
UE_LOG(LogClass, Display, TEXT("%i Uninitialized script struct members found including %d object properties"), UninitializedScriptStructMemberCount, UninitializedObjectPropertyCount);
|
|
}
|
|
|
|
return UninitializedScriptStructMemberCount;
|
|
}
|
|
|
|
#include "HAL/IConsoleManager.h"
|
|
|
|
FAutoConsoleCommandWithWorldAndArgs GCmdListBadScriptStructs(
|
|
TEXT("CoreUObject.AttemptToFindUninitializedScriptStructMembers"),
|
|
TEXT("Finds USTRUCT() structs that fail to initialize reflected member variables"),
|
|
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(
|
|
[](const TArray<FString>& Params, UWorld* World)
|
|
{
|
|
FStructUtils::AttemptToFindUninitializedScriptStructMembers();
|
|
}));
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FAutomationTestAttemptToFindUninitializedScriptStructMembers, "UObject.Class AttemptToFindUninitializedScriptStructMembers", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ServerContext | EAutomationTestFlags::SmokeFilter)
|
|
bool FAutomationTestAttemptToFindUninitializedScriptStructMembers::RunTest(const FString& Parameters)
|
|
{
|
|
return FStructUtils::AttemptToFindUninitializedScriptStructMembers() == 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
IMPLEMENT_CORE_INTRINSIC_CLASS(UScriptStruct, UStruct,
|
|
{});
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
UClass implementation.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/** Default C++ class type information, used for all new UClass objects. */
|
|
static const FCppClassTypeInfoStatic DefaultCppClassTypeInfoStatic = {false};
|
|
|
|
void UClass::PostInitProperties()
|
|
{
|
|
Super::PostInitProperties();
|
|
if (HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
if (ClassAddReferencedObjects == NULL)
|
|
{
|
|
// Default__Class uses its own AddReferencedObjects function.
|
|
ClassAddReferencedObjects = &UClass::AddReferencedObjects;
|
|
}
|
|
}
|
|
}
|
|
|
|
UObject* UClass::GetDefaultSubobjectByName(FName ToFind)
|
|
{
|
|
UObject* DefaultObj = GetDefaultObject();
|
|
UObject* DefaultSubobject = nullptr;
|
|
if (DefaultObj)
|
|
{
|
|
DefaultSubobject = DefaultObj->GetDefaultSubobjectByName(ToFind);
|
|
}
|
|
return DefaultSubobject;
|
|
}
|
|
|
|
void UClass::GetDefaultObjectSubobjects(TArray<UObject*>& OutDefaultSubobjects)
|
|
{
|
|
UObject* DefaultObj = GetDefaultObject();
|
|
if (DefaultObj)
|
|
{
|
|
DefaultObj->GetDefaultSubobjects(OutDefaultSubobjects);
|
|
}
|
|
else
|
|
{
|
|
OutDefaultSubobjects.Empty();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Callback used to allow an object to register its direct object references that are not already covered by
|
|
* the token stream.
|
|
*
|
|
* @param ObjectArray array to add referenced objects to via AddReferencedObject
|
|
*/
|
|
void UClass::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
|
|
{
|
|
UClass* This = CastChecked<UClass>(InThis);
|
|
for (auto& Inter: This->Interfaces)
|
|
{
|
|
Collector.AddReferencedObject(Inter.Class, This);
|
|
}
|
|
|
|
for (auto& Func: This->FuncMap)
|
|
{
|
|
Collector.AddReferencedObject(Func.Value, This);
|
|
}
|
|
|
|
Collector.AddReferencedObject(This->ClassWithin, This);
|
|
Collector.AddReferencedObject(This->ClassGeneratedBy, This);
|
|
|
|
if (!Collector.IsIgnoringArchetypeRef())
|
|
{
|
|
Collector.AddReferencedObject(This->ClassDefaultObject, This);
|
|
}
|
|
else if (This->ClassDefaultObject != NULL)
|
|
{
|
|
// Get the ARO function pointer from the CDO class (virtual functions using static function pointers).
|
|
This->CallAddReferencedObjects(This->ClassDefaultObject, Collector);
|
|
}
|
|
|
|
Super::AddReferencedObjects(This, Collector);
|
|
}
|
|
|
|
/**
|
|
* Helper class used to save and restore information across a StaticAllocateObject over the top of an existing UClass.
|
|
*/
|
|
class FRestoreClassInfo: public FRestoreForUObjectOverwrite
|
|
{
|
|
/** Keep a copy of the pointer, which isn't supposed to change **/
|
|
UClass* Target;
|
|
/** Saved ClassWithin **/
|
|
UClass* Within;
|
|
/** Saved ClassGeneratedBy */
|
|
UObject* GeneratedBy;
|
|
/** Saved ClassDefaultObject **/
|
|
UObject* DefaultObject;
|
|
/** Saved ClassFlags **/
|
|
EClassFlags Flags;
|
|
/** Saved ClassCastFlags **/
|
|
EClassCastFlags CastFlags;
|
|
/** Saved ClassConstructor **/
|
|
UClass::ClassConstructorType Constructor;
|
|
/** Saved ClassVTableHelperCtorCaller **/
|
|
UClass::ClassVTableHelperCtorCallerType ClassVTableHelperCtorCaller;
|
|
/** Saved ClassConstructor **/
|
|
UClass::ClassAddReferencedObjectsType AddReferencedObjects;
|
|
/** Saved NativeFunctionLookupTable. */
|
|
TArray<FNativeFunctionLookup> NativeFunctionLookupTable;
|
|
|
|
public:
|
|
/**
|
|
* Constructor: remember the info for the class so that we can restore it after we've called
|
|
* FMemory::Memzero() on the object's memory address, which results in the non-intrinsic classes losing
|
|
* this data
|
|
*/
|
|
FRestoreClassInfo(UClass* Save): Target(Save),
|
|
Within(Save->ClassWithin),
|
|
GeneratedBy(Save->ClassGeneratedBy),
|
|
DefaultObject(Save->GetDefaultsCount() ? Save->GetDefaultObject() : NULL),
|
|
Flags(Save->ClassFlags & CLASS_Abstract),
|
|
CastFlags(Save->ClassCastFlags),
|
|
Constructor(Save->ClassConstructor),
|
|
ClassVTableHelperCtorCaller(Save->ClassVTableHelperCtorCaller),
|
|
AddReferencedObjects(Save->ClassAddReferencedObjects),
|
|
NativeFunctionLookupTable(Save->NativeFunctionLookupTable)
|
|
{
|
|
}
|
|
/** Called once the new object has been reinitialized
|
|
**/
|
|
virtual void Restore() const
|
|
{
|
|
Target->ClassWithin = Within;
|
|
Target->ClassGeneratedBy = GeneratedBy;
|
|
Target->ClassDefaultObject = DefaultObject;
|
|
Target->ClassFlags |= Flags;
|
|
Target->ClassCastFlags |= CastFlags;
|
|
Target->ClassConstructor = Constructor;
|
|
Target->ClassVTableHelperCtorCaller = ClassVTableHelperCtorCaller;
|
|
Target->ClassAddReferencedObjects = AddReferencedObjects;
|
|
Target->NativeFunctionLookupTable = NativeFunctionLookupTable;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Save information for StaticAllocateObject in the case of overwriting an existing object.
|
|
* StaticAllocateObject will call delete on the result after calling Restore()
|
|
*
|
|
* @return An FRestoreForUObjectOverwrite that can restore the object or NULL if this is not necessary.
|
|
*/
|
|
FRestoreForUObjectOverwrite* UClass::GetRestoreForUObjectOverwrite()
|
|
{
|
|
return new FRestoreClassInfo(this);
|
|
}
|
|
|
|
/**
|
|
* Get the default object from the class, creating it if missing, if requested or under a few other circumstances
|
|
* @return the CDO for this class
|
|
**/
|
|
UObject* UClass::CreateDefaultObject()
|
|
{
|
|
if (ClassDefaultObject == NULL)
|
|
{
|
|
ensureMsgf(!HasAnyClassFlags(CLASS_LayoutChanging), TEXT("Class named %s creating its CDO while changing its layout"), *GetName());
|
|
|
|
UClass* ParentClass = GetSuperClass();
|
|
UObject* ParentDefaultObject = NULL;
|
|
if (ParentClass != NULL)
|
|
{
|
|
UObjectForceRegistration(ParentClass);
|
|
ParentDefaultObject = ParentClass->GetDefaultObject(); // Force the default object to be constructed if it isn't already
|
|
check(GConfig);
|
|
if (GEventDrivenLoaderEnabled && EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME)
|
|
{
|
|
check(ParentDefaultObject && !ParentDefaultObject->HasAnyFlags(RF_NeedLoad));
|
|
}
|
|
}
|
|
|
|
if ((ParentDefaultObject != NULL) || (this == UObject::StaticClass()))
|
|
{
|
|
// If this is a class that can be regenerated, it is potentially not completely loaded. Preload and Link here to ensure we properly zero memory and read in properties for the CDO
|
|
if (HasAnyClassFlags(CLASS_CompiledFromBlueprint) && (PropertyLink == NULL) && !GIsDuplicatingClassForReinstancing)
|
|
{
|
|
auto ClassLinker = GetLinker();
|
|
if (ClassLinker && !ClassLinker->bDynamicClassLinker)
|
|
{
|
|
if (!GEventDrivenLoaderEnabled)
|
|
{
|
|
UField* FieldIt = Children;
|
|
while (FieldIt && (FieldIt->GetOuter() == this))
|
|
{
|
|
// If we've had cyclic dependencies between classes here, we might need to preload to ensure that we load the rest of the property chain
|
|
if (FieldIt->HasAnyFlags(RF_NeedLoad))
|
|
{
|
|
ClassLinker->Preload(FieldIt);
|
|
}
|
|
FieldIt = FieldIt->Next;
|
|
}
|
|
}
|
|
|
|
StaticLink(true);
|
|
}
|
|
}
|
|
|
|
// in the case of cyclic dependencies, the above Preload() calls could end up
|
|
// invoking this method themselves... that means that once we're done with
|
|
// all the Preload() calls we have to make sure ClassDefaultObject is still
|
|
// NULL (so we don't invalidate one that has already been setup)
|
|
if (ClassDefaultObject == NULL)
|
|
{
|
|
FString PackageName;
|
|
FString CDOName;
|
|
bool bDoNotify = false;
|
|
if (GIsInitialLoad && GetOutermost()->HasAnyPackageFlags(PKG_CompiledIn) && !GetOutermost()->HasAnyPackageFlags(PKG_RuntimeGenerated))
|
|
{
|
|
PackageName = GetOutermost()->GetFName().ToString();
|
|
CDOName = GetDefaultObjectName().ToString();
|
|
NotifyRegistrationEvent(*PackageName, *CDOName, ENotifyRegistrationType::NRT_ClassCDO, ENotifyRegistrationPhase::NRP_Started);
|
|
bDoNotify = true;
|
|
}
|
|
|
|
// RF_ArchetypeObject flag is often redundant to RF_ClassDefaultObject, but we need to tag
|
|
// the CDO as RF_ArchetypeObject in order to propagate that flag to any default sub objects.
|
|
ClassDefaultObject = StaticAllocateObject(this, GetOuter(), NAME_None, EObjectFlags(RF_Public | RF_ClassDefaultObject | RF_ArchetypeObject));
|
|
check(ClassDefaultObject);
|
|
// Blueprint CDOs have their properties always initialized.
|
|
const bool bShouldInitializeProperties = !HasAnyClassFlags(CLASS_Native | CLASS_Intrinsic);
|
|
// Register the offsets of any sparse delegates this class introduces with the sparse delegate storage
|
|
for (TFieldIterator<FMulticastSparseDelegateProperty> SparseDelegateIt(this, EFieldIteratorFlags::ExcludeSuper, EFieldIteratorFlags::ExcludeDeprecated); SparseDelegateIt; ++SparseDelegateIt)
|
|
{
|
|
const FSparseDelegate& SparseDelegate = SparseDelegateIt->GetPropertyValue_InContainer(ClassDefaultObject);
|
|
USparseDelegateFunction* SparseDelegateFunction = CastChecked<USparseDelegateFunction>(SparseDelegateIt->SignatureFunction);
|
|
FSparseDelegateStorage::RegisterDelegateOffset(ClassDefaultObject, SparseDelegateFunction->DelegateName, (size_t)&SparseDelegate - (size_t)ClassDefaultObject);
|
|
}
|
|
if (HasAnyClassFlags(CLASS_CompiledFromBlueprint))
|
|
{
|
|
if (UDynamicClass* DynamicClass = Cast<UDynamicClass>(this))
|
|
{
|
|
(*(DynamicClass->DynamicClassInitializer))(DynamicClass);
|
|
}
|
|
}
|
|
(*ClassConstructor)(FObjectInitializer(ClassDefaultObject, ParentDefaultObject, false, bShouldInitializeProperties));
|
|
if (bDoNotify)
|
|
{
|
|
NotifyRegistrationEvent(*PackageName, *CDOName, ENotifyRegistrationType::NRT_ClassCDO, ENotifyRegistrationPhase::NRP_Finished);
|
|
}
|
|
ClassDefaultObject->PostCDOContruct();
|
|
}
|
|
}
|
|
}
|
|
return ClassDefaultObject;
|
|
}
|
|
|
|
/**
|
|
* Feedback context implementation for windows.
|
|
*/
|
|
class FFeedbackContextImportDefaults: public FFeedbackContext
|
|
{
|
|
/** Context information for warning and error messages */
|
|
FContextSupplier* Context;
|
|
|
|
public:
|
|
// Constructor.
|
|
FFeedbackContextImportDefaults()
|
|
: Context(NULL)
|
|
{
|
|
TreatWarningsAsErrors = true;
|
|
}
|
|
void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category)
|
|
{
|
|
if (Verbosity == ELogVerbosity::Error || Verbosity == ELogVerbosity::Warning)
|
|
{
|
|
if (TreatWarningsAsErrors && Verbosity == ELogVerbosity::Warning)
|
|
{
|
|
Verbosity = ELogVerbosity::Error;
|
|
}
|
|
|
|
FString Prefix;
|
|
if (Context)
|
|
{
|
|
Prefix = Context->GetContext() + TEXT(" : ");
|
|
}
|
|
FString Format = Prefix + FOutputDeviceHelper::FormatLogLine(Verbosity, Category, V);
|
|
|
|
if (Verbosity == ELogVerbosity::Error)
|
|
{
|
|
AddError(Format);
|
|
}
|
|
else
|
|
{
|
|
AddWarning(Format);
|
|
}
|
|
}
|
|
|
|
if (GLogConsole)
|
|
{
|
|
GLogConsole->Serialize(V, Verbosity, Category);
|
|
}
|
|
if (!GLog->IsRedirectingTo(this))
|
|
{
|
|
GLog->Serialize(V, Verbosity, Category);
|
|
}
|
|
}
|
|
|
|
FContextSupplier* GetContext() const
|
|
{
|
|
return Context;
|
|
}
|
|
void SetContext(FContextSupplier* InSupplier)
|
|
{
|
|
Context = InSupplier;
|
|
}
|
|
};
|
|
|
|
FFeedbackContext& UClass::GetDefaultPropertiesFeedbackContext()
|
|
{
|
|
static FFeedbackContextImportDefaults FeedbackContextImportDefaults;
|
|
return FeedbackContextImportDefaults;
|
|
}
|
|
|
|
/**
|
|
* Get the name of the CDO for the this class
|
|
* @return The name of the CDO
|
|
*/
|
|
FName UClass::GetDefaultObjectName() const
|
|
{
|
|
FString DefaultName;
|
|
DefaultName.Reserve(NAME_SIZE);
|
|
DefaultName += DEFAULT_OBJECT_PREFIX;
|
|
AppendName(DefaultName);
|
|
return FName(*DefaultName);
|
|
}
|
|
|
|
//
|
|
// Register the native class.
|
|
//
|
|
void UClass::DeferredRegister(UClass* UClassStaticClass, const TCHAR* PackageName, const TCHAR* Name)
|
|
{
|
|
Super::DeferredRegister(UClassStaticClass, PackageName, Name);
|
|
|
|
// Get stashed registration info.
|
|
|
|
// PVS-Studio justifiably complains about this cast, but we expect this to work because we 'know' that
|
|
// we're coming from the UClass constructor that is used when 'statically linked'. V580 disables
|
|
// a warning that indicates this is an 'odd explicit type casting'.
|
|
const TCHAR* InClassConfigName = *(TCHAR**)&ClassConfigName; //-V580 //-V641
|
|
ClassConfigName = InClassConfigName;
|
|
|
|
// Propagate inherited flags.
|
|
UClass* SuperClass = GetSuperClass();
|
|
if (SuperClass != NULL)
|
|
{
|
|
ClassFlags |= (SuperClass->ClassFlags & CLASS_Inherit);
|
|
ClassCastFlags |= SuperClass->ClassCastFlags;
|
|
}
|
|
}
|
|
|
|
bool UClass::Rename(const TCHAR* InName, UObject* NewOuter, ERenameFlags Flags)
|
|
{
|
|
bool bSuccess = Super::Rename(InName, NewOuter, Flags);
|
|
|
|
// If we have a default object, rename that to the same package as the class, and rename so it still matches the class name (Default__ClassName)
|
|
if (bSuccess && (ClassDefaultObject != NULL))
|
|
{
|
|
ClassDefaultObject->Rename(*GetDefaultObjectName().ToString(), NewOuter, Flags);
|
|
}
|
|
|
|
// Now actually rename the class
|
|
return bSuccess;
|
|
}
|
|
|
|
void UClass::TagSubobjects(EObjectFlags NewFlags)
|
|
{
|
|
Super::TagSubobjects(NewFlags);
|
|
|
|
if (ClassDefaultObject && !ClassDefaultObject->HasAnyFlags(GARBAGE_COLLECTION_KEEPFLAGS) && !ClassDefaultObject->IsRooted())
|
|
{
|
|
ClassDefaultObject->SetFlags(NewFlags);
|
|
ClassDefaultObject->TagSubobjects(NewFlags);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find the class's native constructor.
|
|
*/
|
|
void UClass::Bind()
|
|
{
|
|
UStruct::Bind();
|
|
|
|
if (!GIsUCCMakeStandaloneHeaderGenerator && !ClassConstructor && IsNative())
|
|
{
|
|
UE_LOG(LogClass, Fatal, TEXT("Can't bind to native class %s"), *GetPathName());
|
|
}
|
|
|
|
UClass* SuperClass = GetSuperClass();
|
|
if (SuperClass && (ClassConstructor == nullptr || ClassAddReferencedObjects == nullptr || ClassVTableHelperCtorCaller == nullptr))
|
|
{
|
|
// Chase down constructor in parent class.
|
|
SuperClass->Bind();
|
|
if (!ClassConstructor)
|
|
{
|
|
ClassConstructor = SuperClass->ClassConstructor;
|
|
}
|
|
if (!ClassVTableHelperCtorCaller)
|
|
{
|
|
ClassVTableHelperCtorCaller = SuperClass->ClassVTableHelperCtorCaller;
|
|
}
|
|
if (!ClassAddReferencedObjects)
|
|
{
|
|
ClassAddReferencedObjects = SuperClass->ClassAddReferencedObjects;
|
|
}
|
|
|
|
// propagate flags.
|
|
// we don't propagate the inherit flags, that is more of a header generator thing
|
|
ClassCastFlags |= SuperClass->ClassCastFlags;
|
|
}
|
|
// if( !Class && SuperClass )
|
|
//{
|
|
// }
|
|
if (!ClassConstructor)
|
|
{
|
|
UE_LOG(LogClass, Fatal, TEXT("Can't find ClassConstructor for class %s"), *GetPathName());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the struct/ class prefix used for the C++ declaration of this struct/ class.
|
|
* Classes deriving from AActor have an 'A' prefix and other UObject classes an 'U' prefix.
|
|
*
|
|
* @return Prefix character used for C++ declaration of this struct/ class.
|
|
*/
|
|
const TCHAR* UClass::GetPrefixCPP() const
|
|
{
|
|
const UClass* TheClass = this;
|
|
bool bIsActorClass = false;
|
|
bool bIsDeprecated = TheClass->HasAnyClassFlags(CLASS_Deprecated);
|
|
while (TheClass && !bIsActorClass)
|
|
{
|
|
bIsActorClass = TheClass->GetFName() == NAME_Actor;
|
|
TheClass = TheClass->GetSuperClass();
|
|
}
|
|
|
|
if (bIsActorClass)
|
|
{
|
|
if (bIsDeprecated)
|
|
{
|
|
return TEXT("ADEPRECATED_");
|
|
}
|
|
else
|
|
{
|
|
return TEXT("A");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bIsDeprecated)
|
|
{
|
|
return TEXT("UDEPRECATED_");
|
|
}
|
|
else
|
|
{
|
|
return TEXT("U");
|
|
}
|
|
}
|
|
}
|
|
|
|
FString UClass::GetDescription() const
|
|
{
|
|
FString Description;
|
|
|
|
#if WITH_EDITOR
|
|
// See if display name meta data has been specified
|
|
Description = GetDisplayNameText().ToString();
|
|
if (Description.Len())
|
|
{
|
|
return Description;
|
|
}
|
|
#endif
|
|
|
|
// Look up the the classes name in the legacy int file and return the class name if there is no match.
|
|
// Description = Localize( TEXT("Objects"), *GetName(), *(FInternationalization::Get().GetCurrentCulture()->GetName()), true );
|
|
// if (Description.Len())
|
|
//{
|
|
// return Description;
|
|
//}
|
|
|
|
// Otherwise just return the class name
|
|
return FString(GetName());
|
|
}
|
|
|
|
// UClass UObject implementation.
|
|
|
|
void UClass::FinishDestroy()
|
|
{
|
|
// Empty arrays.
|
|
// warning: Must be emptied explicitly in order for intrinsic classes
|
|
// to not show memory leakage on exit.
|
|
NetFields.Empty();
|
|
ClassReps.Empty();
|
|
|
|
ClassDefaultObject = nullptr;
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
// If for whatever reason there's still properties that have not been destroyed in PurgeClass, destroy them now
|
|
DestroyPropertiesPendingDestruction();
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
Super::FinishDestroy();
|
|
}
|
|
|
|
void UClass::PostLoad()
|
|
{
|
|
check(ClassWithin);
|
|
Super::PostLoad();
|
|
|
|
// Postload super.
|
|
if (GetSuperClass())
|
|
{
|
|
GetSuperClass()->ConditionalPostLoad();
|
|
}
|
|
|
|
if (!HasAnyClassFlags(CLASS_Native))
|
|
{
|
|
ClassFlags &= ~CLASS_ReplicationDataIsSetUp;
|
|
}
|
|
}
|
|
|
|
FString UClass::GetDesc()
|
|
{
|
|
return GetName();
|
|
}
|
|
|
|
void UClass::GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const
|
|
{
|
|
Super::GetAssetRegistryTags(OutTags);
|
|
|
|
#if WITH_EDITOR
|
|
static const FName ParentClassFName = "ParentClass";
|
|
const UClass* const ParentClass = GetSuperClass();
|
|
OutTags.Add(FAssetRegistryTag(ParentClassFName, ((ParentClass) ? ParentClass->GetFName() : NAME_None).ToString(), FAssetRegistryTag::TT_Alphabetical));
|
|
|
|
static const FName ModuleNameFName = "ModuleName";
|
|
const UPackage* const ClassPackage = GetOuterUPackage();
|
|
OutTags.Add(FAssetRegistryTag(ModuleNameFName, ((ClassPackage) ? FPackageName::GetShortFName(ClassPackage->GetFName()) : NAME_None).ToString(), FAssetRegistryTag::TT_Alphabetical));
|
|
|
|
static const FName ModuleRelativePathFName = "ModuleRelativePath";
|
|
const FString& ClassModuleRelativeIncludePath = GetMetaData(ModuleRelativePathFName);
|
|
OutTags.Add(FAssetRegistryTag(ModuleRelativePathFName, ClassModuleRelativeIncludePath, FAssetRegistryTag::TT_Alphabetical));
|
|
#endif
|
|
}
|
|
|
|
void UClass::Link(FArchive& Ar, bool bRelinkExistingProperties)
|
|
{
|
|
check(!bRelinkExistingProperties || !(ClassFlags & CLASS_Intrinsic));
|
|
Super::Link(Ar, bRelinkExistingProperties);
|
|
}
|
|
|
|
#if (UE_BUILD_SHIPPING)
|
|
static int32 GValidateReplicatedProperties = 0;
|
|
#else
|
|
static int32 GValidateReplicatedProperties = 1;
|
|
#endif
|
|
|
|
static FAutoConsoleVariable CVarValidateReplicatedPropertyRegistration(TEXT("net.ValidateReplicatedPropertyRegistration"), GValidateReplicatedProperties, TEXT("Warns if replicated properties were not registered in GetLifetimeReplicatedProps."));
|
|
|
|
#if HACK_HEADER_GENERATOR
|
|
void UClass::SetUpUhtReplicationData()
|
|
{
|
|
if (!HasAnyClassFlags(CLASS_ReplicationDataIsSetUp) && PropertyLink != NULL)
|
|
{
|
|
ClassReps.Empty();
|
|
if (UClass* SuperClass = GetSuperClass())
|
|
{
|
|
SuperClass->SetUpUhtReplicationData();
|
|
ClassReps = SuperClass->ClassReps;
|
|
FirstOwnedClassRep = ClassReps.Num();
|
|
}
|
|
else
|
|
{
|
|
FirstOwnedClassRep = 0;
|
|
}
|
|
|
|
for (TFieldIterator<FProperty> It(this, EFieldIteratorFlags::ExcludeSuper); It; ++It)
|
|
{
|
|
if (It->PropertyFlags & CPF_Net)
|
|
{
|
|
It->RepIndex = ClassReps.Num();
|
|
new (ClassReps) FRepRecord(*It, 0);
|
|
}
|
|
}
|
|
|
|
ClassFlags |= CLASS_ReplicationDataIsSetUp;
|
|
ClassReps.Shrink();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void UClass::SetUpRuntimeReplicationData()
|
|
{
|
|
if (!HasAnyClassFlags(CLASS_ReplicationDataIsSetUp) && PropertyLink != NULL)
|
|
{
|
|
NetFields.Empty();
|
|
|
|
if (UClass* SuperClass = GetSuperClass())
|
|
{
|
|
SuperClass->SetUpRuntimeReplicationData();
|
|
ClassReps = SuperClass->ClassReps;
|
|
FirstOwnedClassRep = ClassReps.Num();
|
|
}
|
|
else
|
|
{
|
|
ClassReps.Empty();
|
|
FirstOwnedClassRep = 0;
|
|
}
|
|
|
|
// Track properties so me can ensure they are sorted by offsets at the end
|
|
TArray<FProperty*> NetProperties;
|
|
for (TFieldIterator<FField> It(this, EFieldIteratorFlags::ExcludeSuper); It; ++It)
|
|
{
|
|
if (FProperty* Prop = CastField<FProperty>(*It))
|
|
{
|
|
if ((Prop->PropertyFlags & CPF_Net) && Prop->GetOwner<UObject>() == this)
|
|
{
|
|
NetProperties.Add(Prop);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TFieldIterator<UField> It(this, EFieldIteratorFlags::ExcludeSuper); It; ++It)
|
|
{
|
|
if (UFunction* Func = Cast<UFunction>(*It))
|
|
{
|
|
// When loading reflection data (e.g. from blueprints), we may have references to placeholder functions, or reflection data
|
|
// in children may be out of date. In that case we cannot enforce this check, but that is ok because reflection data will
|
|
// be regenerated by compile on load anyway:
|
|
const bool bCanCheck = (!GIsEditor && !IsRunningCommandlet()) || !Func->HasAnyFlags(RF_WasLoaded);
|
|
check(!bCanCheck || (!Func->GetSuperFunction() || (Func->GetSuperFunction()->FunctionFlags & FUNC_NetFuncFlags) == (Func->FunctionFlags & FUNC_NetFuncFlags)));
|
|
if ((Func->FunctionFlags & FUNC_Net) && !Func->GetSuperFunction())
|
|
{
|
|
NetFields.Add(Func);
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool bIsNativeClass = HasAnyClassFlags(CLASS_Native);
|
|
if (!bIsNativeClass)
|
|
{
|
|
// Sort NetProperties so that their ClassReps are sorted by memory offset
|
|
struct FComparePropertyOffsets
|
|
{
|
|
FORCEINLINE bool operator()(FProperty& A, FProperty& B) const
|
|
{
|
|
// Ensure stable sort
|
|
if (A.GetOffset_ForGC() == B.GetOffset_ForGC())
|
|
{
|
|
return A.GetName() < B.GetName();
|
|
}
|
|
|
|
return A.GetOffset_ForGC() < B.GetOffset_ForGC();
|
|
}
|
|
};
|
|
|
|
Sort(NetProperties.GetData(), NetProperties.Num(), FComparePropertyOffsets());
|
|
}
|
|
|
|
ClassReps.Reserve(ClassReps.Num() + NetProperties.Num());
|
|
for (int32 i = 0; i < NetProperties.Num(); i++)
|
|
{
|
|
NetProperties[i]->RepIndex = ClassReps.Num();
|
|
for (int32 j = 0; j < NetProperties[i]->ArrayDim; j++)
|
|
{
|
|
new (ClassReps) FRepRecord(NetProperties[i], j);
|
|
}
|
|
}
|
|
|
|
if (bIsNativeClass && GValidateReplicatedProperties)
|
|
{
|
|
GetDefaultObject()->ValidateGeneratedRepEnums(ClassReps);
|
|
}
|
|
|
|
NetFields.Shrink();
|
|
|
|
struct FCompareUFieldNames
|
|
{
|
|
FORCEINLINE bool operator()(UField& A, UField& B) const
|
|
{
|
|
return A.GetName() < B.GetName();
|
|
}
|
|
};
|
|
Sort(NetFields.GetData(), NetFields.Num(), FCompareUFieldNames());
|
|
|
|
ClassFlags |= CLASS_ReplicationDataIsSetUp;
|
|
|
|
if (GValidateReplicatedProperties != 0)
|
|
{
|
|
ValidateRuntimeReplicationData();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UClass::ValidateRuntimeReplicationData()
|
|
{
|
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Class ValidateRuntimeReplicationData"), STAT_Class_ValidateRuntimeReplicationData, STATGROUP_Game);
|
|
|
|
if (HasAnyClassFlags(CLASS_CompiledFromBlueprint | CLASS_LayoutChanging))
|
|
{
|
|
// Blueprint classes don't always generate a GetLifetimeReplicatedProps function.
|
|
// Assume the Blueprint compiler was ok to do this.
|
|
return;
|
|
}
|
|
|
|
if (HasAnyClassFlags(CLASS_ReplicationDataIsSetUp) == false)
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("ValidateRuntimeReplicationData for class %s called before ReplicationData was setup."), *GetName());
|
|
return;
|
|
}
|
|
|
|
// Our replication data was set up, but there are no class reps, so there's nothing to do.
|
|
if (ClassReps.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Let's compare the CDO's registered lifetime properties with the Class's net properties
|
|
TArray<FLifetimeProperty> LifetimeProps;
|
|
LifetimeProps.Reserve(ClassReps.Num());
|
|
|
|
const UObject* Object = GetDefaultObject();
|
|
Object->GetLifetimeReplicatedProps(LifetimeProps);
|
|
|
|
if (LifetimeProps.Num() == ClassReps.Num())
|
|
{
|
|
// All replicated properties were registered for this class
|
|
return;
|
|
}
|
|
|
|
// Find which properties where not registered by the user code
|
|
for (int32 RepIndex = 0; RepIndex < ClassReps.Num(); ++RepIndex)
|
|
{
|
|
const FProperty* RepProp = ClassReps[RepIndex].Property;
|
|
|
|
const FLifetimeProperty* LifetimeProp = LifetimeProps.FindByPredicate([&RepIndex](const FLifetimeProperty& Var)
|
|
{
|
|
return Var.RepIndex == RepIndex;
|
|
});
|
|
|
|
if (LifetimeProp == nullptr)
|
|
{
|
|
// Check if this unregistered property type uses a custom delta serializer
|
|
if (const FStructProperty* StructProperty = CastField<FStructProperty>(RepProp))
|
|
{
|
|
const UScriptStruct* Struct = StructProperty->Struct;
|
|
|
|
if (EnumHasAnyFlags(Struct->StructFlags, STRUCT_NetDeltaSerializeNative))
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("Property %s::%s (SourceClass: %s) with custom net delta serializer was not registered in GetLifetimeReplicatedProps. This property will replicate but you should still register it."),
|
|
*GetName(), *RepProp->GetName(), *RepProp->GetOwnerClass()->GetName());
|
|
continue;
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogClass, Warning, TEXT("Property %s::%s (SourceClass: %s) was not registered in GetLifetimeReplicatedProps. This property will not be replicated. Use DISABLE_REPLICATED_PROPERTY if not replicating was intentional."),
|
|
*GetName(), *RepProp->GetName(), *RepProp->GetOwnerClass()->GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function for determining if the given class is compatible with structured archive serialization
|
|
*/
|
|
bool UClass::IsSafeToSerializeToStructuredArchives(UClass* InClass)
|
|
{
|
|
while (InClass)
|
|
{
|
|
if (!InClass->HasAnyClassFlags(CLASS_MatchedSerializers))
|
|
{
|
|
return false;
|
|
}
|
|
InClass = InClass->GetSuperClass();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if USTRUCT_FAST_ISCHILDOF_IMPL == USTRUCT_ISCHILDOF_STRUCTARRAY
|
|
|
|
FStructBaseChain::FStructBaseChain()
|
|
: StructBaseChainArray(nullptr), NumStructBasesInChainMinusOne(-1)
|
|
{
|
|
}
|
|
|
|
FStructBaseChain::~FStructBaseChain()
|
|
{
|
|
delete[] StructBaseChainArray;
|
|
}
|
|
|
|
void FStructBaseChain::ReinitializeBaseChainArray()
|
|
{
|
|
delete[] StructBaseChainArray;
|
|
|
|
int32 Depth = 0;
|
|
for (UStruct* Ptr = static_cast<UStruct*>(this); Ptr; Ptr = Ptr->GetSuperStruct())
|
|
{
|
|
++Depth;
|
|
}
|
|
|
|
FStructBaseChain** Bases = new FStructBaseChain*[Depth];
|
|
{
|
|
FStructBaseChain** Base = Bases + Depth;
|
|
for (UStruct* Ptr = static_cast<UStruct*>(this); Ptr; Ptr = Ptr->GetSuperStruct())
|
|
{
|
|
*--Base = Ptr;
|
|
}
|
|
}
|
|
|
|
StructBaseChainArray = Bases;
|
|
NumStructBasesInChainMinusOne = Depth - 1;
|
|
}
|
|
|
|
#endif
|
|
|
|
void UClass::SetSuperStruct(UStruct* NewSuperStruct)
|
|
{
|
|
UnhashObject(this);
|
|
ClearFunctionMapsCaches();
|
|
Super::SetSuperStruct(NewSuperStruct);
|
|
|
|
if (!GetSparseClassDataStruct())
|
|
{
|
|
if (UScriptStruct* SparseClassDataStructArchetype = GetSparseClassDataArchetypeStruct())
|
|
{
|
|
SetSparseClassDataStruct(SparseClassDataStructArchetype);
|
|
}
|
|
}
|
|
|
|
HashObject(this);
|
|
}
|
|
|
|
bool UClass::IsStructTrashed() const
|
|
{
|
|
return Children == nullptr && ChildProperties == nullptr && ClassDefaultObject == nullptr;
|
|
}
|
|
|
|
void UClass::Serialize(FArchive& Ar)
|
|
{
|
|
if (Ar.IsLoading() || Ar.IsModifyingWeakAndStrongReferences())
|
|
{
|
|
// Rehash since SuperStruct will be serialized in UStruct::Serialize
|
|
UnhashObject(this);
|
|
}
|
|
|
|
Super::Serialize(Ar);
|
|
|
|
if (Ar.IsLoading() || Ar.IsModifyingWeakAndStrongReferences())
|
|
{
|
|
HashObject(this);
|
|
}
|
|
|
|
Ar.ThisContainsCode();
|
|
|
|
// serialize the function map
|
|
//@TODO: UCREMOVAL: Should we just regenerate the FuncMap post load, instead of serializing it?
|
|
Ar << FuncMap;
|
|
|
|
// Class flags first.
|
|
if (Ar.IsSaving())
|
|
{
|
|
uint32 SavedClassFlags = ClassFlags;
|
|
SavedClassFlags &= ~(CLASS_ShouldNeverBeLoaded | CLASS_TokenStreamAssembled);
|
|
Ar << SavedClassFlags;
|
|
}
|
|
else if (Ar.IsLoading())
|
|
{
|
|
Ar << (uint32&)ClassFlags;
|
|
ClassFlags &= ~(CLASS_ShouldNeverBeLoaded | CLASS_TokenStreamAssembled);
|
|
}
|
|
else
|
|
{
|
|
Ar << (uint32&)ClassFlags;
|
|
}
|
|
if (Ar.UE4Ver() < VER_UE4_CLASS_NOTPLACEABLE_ADDED)
|
|
{
|
|
// We need to invert the CLASS_NotPlaceable flag here because it used to mean CLASS_Placeable
|
|
ClassFlags ^= CLASS_NotPlaceable;
|
|
|
|
// We can't import a class which is placeable and has a not-placeable base, so we need to check for that here.
|
|
if (ensure(HasAnyClassFlags(CLASS_NotPlaceable) || !GetSuperClass()->HasAnyClassFlags(CLASS_NotPlaceable)))
|
|
{
|
|
// It's good!
|
|
}
|
|
else
|
|
{
|
|
// We'll just make it non-placeable to ensure loading works, even if there's an off-chance that it's already been placed
|
|
ClassFlags |= CLASS_NotPlaceable;
|
|
}
|
|
}
|
|
|
|
// Variables.
|
|
Ar << ClassWithin;
|
|
Ar << ClassConfigName;
|
|
|
|
int32 NumInterfaces = 0;
|
|
int64 InterfacesStart = 0L;
|
|
if (Ar.IsLoading())
|
|
{
|
|
// Always start with no interfaces
|
|
Interfaces.Empty();
|
|
|
|
// In older versions, interface classes were serialized before linking. In case of cyclic dependencies, we need to skip over the serialized array and defer the load until after Link() is called below.
|
|
if (Ar.UE4Ver() < VER_UE4_UCLASS_SERIALIZE_INTERFACES_AFTER_LINKING && !GIsDuplicatingClassForReinstancing)
|
|
{
|
|
// Get our current position
|
|
InterfacesStart = Ar.Tell();
|
|
|
|
// Load the length of the Interfaces array
|
|
Ar << NumInterfaces;
|
|
|
|
// Seek past the Interfaces array
|
|
struct FSerializedInterfaceReference
|
|
{
|
|
FPackageIndex Class;
|
|
int32 PointerOffset;
|
|
bool bImplementedByK2;
|
|
};
|
|
Ar.Seek(InterfacesStart + sizeof(NumInterfaces) + NumInterfaces * sizeof(FSerializedInterfaceReference));
|
|
}
|
|
}
|
|
|
|
if (!Ar.IsIgnoringClassGeneratedByRef())
|
|
{
|
|
Ar << ClassGeneratedBy;
|
|
}
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
checkf(!HasAnyClassFlags(CLASS_Native), TEXT("Class %s loaded with CLASS_Native....we should not be loading any native classes."), *GetFullName());
|
|
checkf(!HasAnyClassFlags(CLASS_Intrinsic), TEXT("Class %s loaded with CLASS_Intrinsic....we should not be loading any intrinsic classes."), *GetFullName());
|
|
ClassFlags &= ~(CLASS_ShouldNeverBeLoaded | CLASS_TokenStreamAssembled);
|
|
if (!(Ar.GetPortFlags() & PPF_Duplicate))
|
|
{
|
|
Link(Ar, true);
|
|
}
|
|
}
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
// Save current position
|
|
int64 CurrentOffset = Ar.Tell();
|
|
|
|
// In older versions, we need to seek backwards to the start of the interfaces array
|
|
if (Ar.UE4Ver() < VER_UE4_UCLASS_SERIALIZE_INTERFACES_AFTER_LINKING && !GIsDuplicatingClassForReinstancing)
|
|
{
|
|
Ar.Seek(InterfacesStart);
|
|
}
|
|
|
|
// Load serialized interface classes
|
|
TArray<FImplementedInterface> SerializedInterfaces;
|
|
Ar << SerializedInterfaces;
|
|
|
|
// Apply loaded interfaces only if we have not already set them (i.e. during compile-on-load)
|
|
if (Interfaces.Num() == 0 && SerializedInterfaces.Num() > 0)
|
|
{
|
|
Interfaces = SerializedInterfaces;
|
|
}
|
|
|
|
// In older versions, seek back to our current position after linking
|
|
if (Ar.UE4Ver() < VER_UE4_UCLASS_SERIALIZE_INTERFACES_AFTER_LINKING && !GIsDuplicatingClassForReinstancing)
|
|
{
|
|
Ar.Seek(CurrentOffset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Ar << Interfaces;
|
|
}
|
|
|
|
bool bDeprecatedForceScriptOrder = false;
|
|
Ar << bDeprecatedForceScriptOrder;
|
|
|
|
FName Dummy = NAME_None;
|
|
Ar << Dummy;
|
|
|
|
if (Ar.UE4Ver() >= VER_UE4_ADD_COOKED_TO_UCLASS)
|
|
{
|
|
if (Ar.IsSaving())
|
|
{
|
|
bCooked = Ar.IsCooking();
|
|
}
|
|
bool bCookedAsBool = bCooked;
|
|
Ar << bCookedAsBool;
|
|
if (Ar.IsLoading())
|
|
{
|
|
bCooked = bCookedAsBool;
|
|
}
|
|
}
|
|
|
|
// Defaults.
|
|
|
|
// mark the archive as serializing defaults
|
|
Ar.StartSerializingDefaults();
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
check((Ar.GetPortFlags() & PPF_Duplicate) || (GetStructureSize() >= sizeof(UObject)));
|
|
check(!GetSuperClass() || !GetSuperClass()->HasAnyFlags(RF_NeedLoad));
|
|
|
|
// record the current CDO, as it stands, so we can compare against it
|
|
// after we've serialized in the new CDO (to detect if, as a side-effect
|
|
// of the serialization, a different CDO was generated)
|
|
UObject* const OldCDO = ClassDefaultObject;
|
|
|
|
// serialize in the CDO, but first store it here (in a temporary var) so
|
|
// we can check to see if it should be the authoritative CDO (a newer
|
|
// CDO could be generated as a side-effect of this serialization)
|
|
//
|
|
// @TODO: for USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING, do we need to
|
|
// defer this serialization (should we just save off the tagged
|
|
// serialization data for later use)?
|
|
UObject* PerspectiveNewCDO = NULL;
|
|
Ar << PerspectiveNewCDO;
|
|
|
|
// Blueprint class regeneration could cause the class's CDO to be set.
|
|
// The CDO (<<) serialization call (above) probably will invoke class
|
|
// regeneration, and as a side-effect the CDO could already be set by
|
|
// the time it returns. So we only want to set the CDO here (to what was
|
|
// serialized in) if it hasn't already changed (else, the serialized
|
|
// version could be stale). See: TTP #343166
|
|
if (ClassDefaultObject == OldCDO)
|
|
{
|
|
ClassDefaultObject = PerspectiveNewCDO;
|
|
}
|
|
// if we reach this point, then the CDO was regenerated as a side-effect
|
|
// of the serialization... let's log if the regenerated CDO (what's
|
|
// already been set) is not the same as what was returned from the
|
|
// serialization (could mean the CDO was regenerated multiple times?)
|
|
else if (PerspectiveNewCDO != ClassDefaultObject)
|
|
{
|
|
UE_LOG(LogClass, Log, TEXT("CDO was changed while class serialization.\n\tOld: '%s'\n\tSerialized: '%s'\n\tActual: '%s'"), OldCDO ? *OldCDO->GetFullName() : TEXT("NULL"), PerspectiveNewCDO ? *PerspectiveNewCDO->GetFullName() : TEXT("NULL"), ClassDefaultObject ? *ClassDefaultObject->GetFullName() : TEXT("NULL"));
|
|
}
|
|
ClassUnique = 0;
|
|
}
|
|
else
|
|
{
|
|
check(!ClassDefaultObject || GetDefaultsCount() == GetPropertiesSize());
|
|
|
|
// only serialize the class default object if the archive allows serialization of ObjectArchetype
|
|
// otherwise, serialize the properties that the ClassDefaultObject references
|
|
// The logic behind this is the assumption that the reason for not serializing the ObjectArchetype
|
|
// is because we are performing some actions on objects of this class and we don't want to perform
|
|
// that action on the ClassDefaultObject. However, we do want to perform that action on objects that
|
|
// the ClassDefaultObject is referencing, so we'll serialize it's properties instead of serializing
|
|
// the object itself
|
|
if (!Ar.IsIgnoringArchetypeRef())
|
|
{
|
|
Ar << ClassDefaultObject;
|
|
}
|
|
else if ((ClassDefaultObject != nullptr && !Ar.HasAnyPortFlags(PPF_DuplicateForPIE | PPF_Duplicate)) || ClassDefaultObject != nullptr)
|
|
{
|
|
ClassDefaultObject->Serialize(Ar);
|
|
}
|
|
}
|
|
|
|
if (!Ar.IsLoading() && !Ar.IsSaving())
|
|
{
|
|
if (GetSparseClassDataStruct() != nullptr)
|
|
{
|
|
SerializeSparseClassData(FStructuredArchiveFromArchive(Ar).GetSlot());
|
|
}
|
|
}
|
|
|
|
// mark the archive we that we are no longer serializing defaults
|
|
Ar.StopSerializingDefaults();
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
if (ClassDefaultObject == NULL)
|
|
{
|
|
check(GConfig);
|
|
if (GEventDrivenLoaderEnabled || Ar.IsUsingEventDrivenLoader())
|
|
{
|
|
ClassDefaultObject = GetDefaultObject();
|
|
// we do this later anyway, once we find it and set it in the export table.
|
|
// ClassDefaultObject->SetFlags(RF_NeedLoad | RF_NeedPostLoad | RF_NeedPostLoadSubobjects);
|
|
}
|
|
else if (!Ar.HasAnyPortFlags(PPF_DuplicateForPIE | PPF_Duplicate))
|
|
{
|
|
UE_LOG(LogClass, Error, TEXT("CDO for class %s did not load!"), *GetPathName());
|
|
ensure(ClassDefaultObject != NULL);
|
|
ClassDefaultObject = GetDefaultObject();
|
|
Ar.ForceBlueprintFinalization();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UClass::ImplementsInterface(const class UClass* SomeInterface) const
|
|
{
|
|
if (SomeInterface != NULL && SomeInterface->HasAnyClassFlags(CLASS_Interface) && SomeInterface != UInterface::StaticClass())
|
|
{
|
|
for (const UClass* CurrentClass = this; CurrentClass; CurrentClass = CurrentClass->GetSuperClass())
|
|
{
|
|
// SomeInterface might be a base interface of our implemented interface
|
|
for (TArray<FImplementedInterface>::TConstIterator It(CurrentClass->Interfaces); It; ++It)
|
|
{
|
|
const UClass* InterfaceClass = It->Class;
|
|
if (InterfaceClass && InterfaceClass->IsChildOf(SomeInterface))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** serializes the passed in object as this class's default object using the given archive
|
|
* @param Object the object to serialize as default
|
|
* @param Ar the archive to serialize from
|
|
*/
|
|
void UClass::SerializeDefaultObject(UObject* Object, FStructuredArchive::FSlot Slot)
|
|
{
|
|
// tell the archive that it's allowed to load data for transient properties
|
|
FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive();
|
|
|
|
UnderlyingArchive.StartSerializingDefaults();
|
|
|
|
if (((UnderlyingArchive.IsLoading() || UnderlyingArchive.IsSaving()) && !UnderlyingArchive.WantBinaryPropertySerialization()))
|
|
{
|
|
// class default objects do not always have a vtable when saved
|
|
// so use script serialization as opposed to native serialization to
|
|
// guarantee that all property data is loaded into the correct location
|
|
SerializeTaggedProperties(Slot, (uint8*)Object, GetSuperClass(), (uint8*)Object->GetArchetype());
|
|
}
|
|
else if (UnderlyingArchive.GetPortFlags() != 0)
|
|
{
|
|
SerializeBinEx(Slot, Object, Object->GetArchetype(), GetSuperClass());
|
|
}
|
|
else
|
|
{
|
|
SerializeBin(Slot, Object);
|
|
}
|
|
UnderlyingArchive.StopSerializingDefaults();
|
|
}
|
|
|
|
void UClass::SerializeSparseClassData(FStructuredArchive::FSlot Slot)
|
|
{
|
|
if (!SparseClassDataStruct)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// tell the archive that it's allowed to load data for transient properties
|
|
FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive();
|
|
|
|
// make sure we always have sparse class a sparse class data struct to read from/write to
|
|
GetOrCreateSparseClassData();
|
|
|
|
if (((UnderlyingArchive.IsLoading() || UnderlyingArchive.IsSaving()) && !UnderlyingArchive.WantBinaryPropertySerialization()))
|
|
{
|
|
// class default objects do not always have a vtable when saved
|
|
// so use script serialization as opposed to native serialization to
|
|
// guarantee that all property data is loaded into the correct location
|
|
SparseClassDataStruct->SerializeItem(Slot, SparseClassData, GetArchetypeForSparseClassData());
|
|
}
|
|
else if (UnderlyingArchive.GetPortFlags() != 0)
|
|
{
|
|
SparseClassDataStruct->SerializeBinEx(Slot, (uint8*)SparseClassData, SparseClassDataStruct, GetSparseClassDataArchetypeStruct());
|
|
}
|
|
else
|
|
{
|
|
SparseClassDataStruct->SerializeBin(Slot, (uint8*)SparseClassData);
|
|
}
|
|
}
|
|
|
|
FArchive& operator<<(FArchive& Ar, FImplementedInterface& A)
|
|
{
|
|
Ar << A.Class;
|
|
Ar << A.PointerOffset;
|
|
Ar << A.bImplementedByK2;
|
|
|
|
return Ar;
|
|
}
|
|
|
|
void* UClass::GetArchetypeForSparseClassData() const
|
|
{
|
|
UClass* SuperClass = GetSuperClass();
|
|
return SuperClass ? SuperClass->GetOrCreateSparseClassData() : nullptr;
|
|
}
|
|
|
|
UScriptStruct* UClass::GetSparseClassDataArchetypeStruct() const
|
|
{
|
|
UClass* SuperClass = GetSuperClass();
|
|
return SuperClass ? SuperClass->GetSparseClassDataStruct() : nullptr;
|
|
}
|
|
|
|
UObject* UClass::GetArchetypeForCDO() const
|
|
{
|
|
UClass* SuperClass = GetSuperClass();
|
|
return SuperClass ? SuperClass->GetDefaultObject() : nullptr;
|
|
}
|
|
|
|
void UClass::PurgeClass(bool bRecompilingOnLoad)
|
|
{
|
|
ClassConstructor = nullptr;
|
|
ClassVTableHelperCtorCaller = nullptr;
|
|
ClassFlags = CLASS_None;
|
|
ClassCastFlags = CASTCLASS_None;
|
|
ClassUnique = 0;
|
|
ClassReps.Empty();
|
|
NetFields.Empty();
|
|
|
|
#if WITH_EDITOR
|
|
if (!bRecompilingOnLoad)
|
|
{
|
|
// this is not safe to do at COL time. The meta data is not loaded yet, so if we attempt to load it, we recursively load the package and that will fail
|
|
RemoveMetaData("HideCategories");
|
|
RemoveMetaData("ShowCategories");
|
|
RemoveMetaData("HideFunctions");
|
|
RemoveMetaData("AutoExpandCategories");
|
|
RemoveMetaData("AutoCollapseCategories");
|
|
RemoveMetaData("ClassGroupNames");
|
|
}
|
|
#endif
|
|
|
|
ClassDefaultObject = nullptr;
|
|
|
|
Interfaces.Empty();
|
|
NativeFunctionLookupTable.Empty();
|
|
SetSuperStruct(nullptr);
|
|
Children = nullptr;
|
|
Script.Empty();
|
|
MinAlignment = 0;
|
|
RefLink = nullptr;
|
|
PropertyLink = nullptr;
|
|
DestructorLink = nullptr;
|
|
ClassAddReferencedObjects = nullptr;
|
|
|
|
ScriptAndPropertyObjectReferences.Empty();
|
|
DeleteUnresolvedScriptProperties();
|
|
|
|
FuncMap.Empty();
|
|
ClearFunctionMapsCaches();
|
|
PropertyLink = nullptr;
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
{
|
|
for (UPropertyWrapper* Wrapper: PropertyWrappers)
|
|
{
|
|
Wrapper->SetProperty(nullptr);
|
|
}
|
|
PropertyWrappers.Empty();
|
|
}
|
|
|
|
// When compiling properties can't be immediately destroyed because we need
|
|
// to fix up references to these properties. The caller of PurgeClass is
|
|
// expected to call DestroyPropertiesPendingDestruction
|
|
FField* LastField = ChildProperties;
|
|
if (LastField)
|
|
{
|
|
while (LastField->Next)
|
|
{
|
|
LastField = LastField->Next;
|
|
}
|
|
check(LastField->Next == nullptr);
|
|
LastField->Next = PropertiesPendingDestruction;
|
|
PropertiesPendingDestruction = ChildProperties;
|
|
ChildProperties = nullptr;
|
|
}
|
|
// Update the serial number so that FFieldPaths that point to properties of this struct know they need to resolve themselves again
|
|
FieldPathSerialNumber = GetNextFieldPathSerialNumber();
|
|
#else
|
|
{
|
|
// Destroy all properties owned by this struct
|
|
DestroyPropertyLinkedList(ChildProperties);
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
DestroyUnversionedSchema(this);
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
void UClass::DestroyPropertiesPendingDestruction()
|
|
{
|
|
DestroyPropertyLinkedList(PropertiesPendingDestruction);
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
UClass* UClass::FindCommonBase(UClass* InClassA, UClass* InClassB)
|
|
{
|
|
check(InClassA);
|
|
UClass* CommonClass = InClassA;
|
|
while (InClassB && !InClassB->IsChildOf(CommonClass))
|
|
{
|
|
CommonClass = CommonClass->GetSuperClass();
|
|
|
|
if (!CommonClass)
|
|
break;
|
|
}
|
|
return CommonClass;
|
|
}
|
|
|
|
UClass* UClass::FindCommonBase(const TArray<UClass*>& InClasses)
|
|
{
|
|
check(InClasses.Num() > 0);
|
|
auto ClassIter = InClasses.CreateConstIterator();
|
|
UClass* CommonClass = *ClassIter;
|
|
ClassIter++;
|
|
|
|
for (; ClassIter; ++ClassIter)
|
|
{
|
|
CommonClass = UClass::FindCommonBase(CommonClass, *ClassIter);
|
|
}
|
|
return CommonClass;
|
|
}
|
|
|
|
bool UClass::IsFunctionImplementedInScript(FName InFunctionName) const
|
|
{
|
|
// Implemented in classes such as UBlueprintGeneratedClass
|
|
return false;
|
|
}
|
|
|
|
bool UClass::HasProperty(FProperty* InProperty) const
|
|
{
|
|
if (InProperty->GetOwner<UObject>())
|
|
{
|
|
UClass* PropertiesClass = InProperty->GetOwner<UClass>();
|
|
if (PropertiesClass)
|
|
{
|
|
return IsChildOf(PropertiesClass);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
UClass constructors.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Internal constructor.
|
|
*/
|
|
UClass::UClass(const FObjectInitializer& ObjectInitializer)
|
|
: UStruct(ObjectInitializer), ClassUnique(0), bCooked(false), ClassFlags(CLASS_None), ClassCastFlags(CASTCLASS_None), ClassWithin(UObject::StaticClass()), ClassGeneratedBy(nullptr)
|
|
#if WITH_EDITORONLY_DATA
|
|
,
|
|
PropertiesPendingDestruction(nullptr)
|
|
#endif
|
|
,
|
|
ClassDefaultObject(nullptr),
|
|
SparseClassData(nullptr),
|
|
SparseClassDataStruct(nullptr)
|
|
{
|
|
// If you add properties here, please update the other constructors and PurgeClass()
|
|
|
|
SetCppTypeInfoStatic(&DefaultCppClassTypeInfoStatic);
|
|
TRACE_LOADTIME_CLASS_INFO(this, GetFName());
|
|
}
|
|
|
|
/**
|
|
* Create a new UClass given its superclass.
|
|
*/
|
|
UClass::UClass(const FObjectInitializer& ObjectInitializer, UClass* InBaseClass)
|
|
: UStruct(ObjectInitializer, InBaseClass), ClassUnique(0), bCooked(false), ClassFlags(CLASS_None), ClassCastFlags(CASTCLASS_None), ClassWithin(UObject::StaticClass()), ClassGeneratedBy(nullptr)
|
|
#if WITH_EDITORONLY_DATA
|
|
,
|
|
PropertiesPendingDestruction(nullptr)
|
|
#endif
|
|
,
|
|
ClassDefaultObject(nullptr),
|
|
SparseClassData(nullptr),
|
|
SparseClassDataStruct(nullptr)
|
|
{
|
|
// If you add properties here, please update the other constructors and PurgeClass()
|
|
|
|
SetCppTypeInfoStatic(&DefaultCppClassTypeInfoStatic);
|
|
|
|
UClass* ParentClass = GetSuperClass();
|
|
if (ParentClass)
|
|
{
|
|
ClassWithin = ParentClass->ClassWithin;
|
|
Bind();
|
|
|
|
// if this is a native class, we may have defined a StaticConfigName() which overrides
|
|
// the one from the parent class, so get our config name from there
|
|
if (IsNative())
|
|
{
|
|
ClassConfigName = StaticConfigName();
|
|
}
|
|
else
|
|
{
|
|
// otherwise, inherit our parent class's config name
|
|
ClassConfigName = ParentClass->ClassConfigName;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when statically linked.
|
|
*/
|
|
UClass::UClass(
|
|
EStaticConstructor,
|
|
FName InName,
|
|
uint32 InSize,
|
|
uint32 InAlignment,
|
|
EClassFlags InClassFlags,
|
|
EClassCastFlags InClassCastFlags,
|
|
const TCHAR* InConfigName,
|
|
EObjectFlags InFlags,
|
|
ClassConstructorType InClassConstructor,
|
|
ClassVTableHelperCtorCallerType InClassVTableHelperCtorCaller,
|
|
ClassAddReferencedObjectsType InClassAddReferencedObjects)
|
|
: UStruct(EC_StaticConstructor, InSize, InAlignment, InFlags), ClassConstructor(InClassConstructor), ClassVTableHelperCtorCaller(InClassVTableHelperCtorCaller), ClassAddReferencedObjects(InClassAddReferencedObjects), ClassUnique(0), bCooked(false), ClassFlags(InClassFlags | CLASS_Native), ClassCastFlags(InClassCastFlags), ClassWithin(nullptr), ClassGeneratedBy(nullptr)
|
|
#if WITH_EDITORONLY_DATA
|
|
,
|
|
PropertiesPendingDestruction(nullptr)
|
|
#endif
|
|
,
|
|
ClassConfigName(),
|
|
NetFields(),
|
|
ClassDefaultObject(nullptr),
|
|
SparseClassData(nullptr),
|
|
SparseClassDataStruct(nullptr)
|
|
{
|
|
// If you add properties here, please update the other constructors and PurgeClass()
|
|
|
|
SetCppTypeInfoStatic(&DefaultCppClassTypeInfoStatic);
|
|
|
|
// We store the pointer to the ConfigName in an FName temporarily, this cast is intentional
|
|
// as we expect the mis-typed data to get picked up in UClass::DeferredRegister. PVS-Studio
|
|
// complains about this operation, but AFAIK it is safe (and we've been doing it a long time)
|
|
// so the warning has been disabled for now:
|
|
*(const TCHAR**)&ClassConfigName = InConfigName; //-V580
|
|
}
|
|
|
|
void* UClass::CreateSparseClassData()
|
|
{
|
|
check(SparseClassData == nullptr);
|
|
|
|
if (SparseClassDataStruct)
|
|
{
|
|
SparseClassData = FMemory::Malloc(SparseClassDataStruct->GetStructureSize(), SparseClassDataStruct->GetMinAlignment());
|
|
SparseClassDataStruct->GetCppStructOps()->Construct(SparseClassData);
|
|
}
|
|
if (SparseClassData)
|
|
{
|
|
// initialize per class data from the archetype if we have one
|
|
void* SparseArchetypeData = GetArchetypeForSparseClassData();
|
|
UStruct* SparseClassDataArchetypeStruct = GetSparseClassDataArchetypeStruct();
|
|
|
|
if (SparseArchetypeData)
|
|
{
|
|
for (FProperty* P = SparseClassDataArchetypeStruct->PropertyLink; P; P = P->PropertyLinkNext)
|
|
{
|
|
P->CopyCompleteValue_InContainer(SparseClassData, SparseArchetypeData);
|
|
}
|
|
}
|
|
}
|
|
|
|
return SparseClassData;
|
|
}
|
|
|
|
void UClass::CleanupSparseClassData()
|
|
{
|
|
if (SparseClassData)
|
|
{
|
|
SparseClassDataStruct->GetCppStructOps()->Destruct(SparseClassData);
|
|
FMemory::Free(SparseClassData);
|
|
SparseClassData = nullptr;
|
|
}
|
|
}
|
|
|
|
UScriptStruct* UClass::GetSparseClassDataStruct() const
|
|
{
|
|
// this info is specified on the object via code generation so we use it instead of looking at the UClass
|
|
return SparseClassDataStruct;
|
|
}
|
|
|
|
void UClass::SetSparseClassDataStruct(UScriptStruct* InSparseClassDataStruct)
|
|
{
|
|
if (SparseClassDataStruct != InSparseClassDataStruct)
|
|
{
|
|
SparseClassDataStruct = InSparseClassDataStruct;
|
|
|
|
// the old type and new type may not match when we do a hot reload so get rid of the old data
|
|
CleanupSparseClassData();
|
|
}
|
|
}
|
|
|
|
#if WITH_HOT_RELOAD
|
|
|
|
bool UClass::HotReloadPrivateStaticClass(
|
|
uint32 InSize,
|
|
EClassFlags InClassFlags,
|
|
EClassCastFlags InClassCastFlags,
|
|
const TCHAR* InConfigName,
|
|
ClassConstructorType InClassConstructor,
|
|
ClassVTableHelperCtorCallerType InClassVTableHelperCtorCaller,
|
|
ClassAddReferencedObjectsType InClassAddReferencedObjects,
|
|
class UClass* TClass_Super_StaticClass,
|
|
class UClass* TClass_WithinClass_StaticClass)
|
|
{
|
|
if (InSize != PropertiesSize)
|
|
{
|
|
UClass::GetDefaultPropertiesFeedbackContext().Logf(ELogVerbosity::Warning, TEXT("Property size mismatch. Will not update class %s (was %d, new %d)."), *GetName(), PropertiesSize, InSize);
|
|
return false;
|
|
}
|
|
// We could do this later, but might as well get it before we start corrupting the object
|
|
UObject* CDO = GetDefaultObject();
|
|
void* OldVTable = *(void**)CDO;
|
|
|
|
//@todo safe? ClassFlags = InClassFlags | CLASS_Native;
|
|
//@todo safe? ClassCastFlags = InClassCastFlags;
|
|
//@todo safe? ClassConfigName = InConfigName;
|
|
ClassConstructorType OldClassConstructor = ClassConstructor;
|
|
ClassConstructor = InClassConstructor;
|
|
ClassVTableHelperCtorCaller = InClassVTableHelperCtorCaller;
|
|
ClassAddReferencedObjects = InClassAddReferencedObjects;
|
|
/* No recursive ::StaticClass calls allowed. Setup extras. */
|
|
/* @todo safe?
|
|
if (TClass_Super_StaticClass != this)
|
|
{
|
|
SetSuperStruct(TClass_Super_StaticClass);
|
|
}
|
|
else
|
|
{
|
|
SetSuperStruct(NULL);
|
|
}
|
|
ClassWithin = TClass_WithinClass_StaticClass;
|
|
*/
|
|
|
|
UE_LOG(LogClass, Verbose, TEXT("Attempting to change VTable for class %s."), *GetName());
|
|
ClassWithin = UPackage::StaticClass(); // We are just avoiding error checks with this...we don't care about this temp object other than to get the vtable.
|
|
|
|
static struct FUseVTableConstructorsCache
|
|
{
|
|
FUseVTableConstructorsCache()
|
|
{
|
|
bUseVTableConstructors = false;
|
|
GConfig->GetBool(TEXT("Core.System"), TEXT("UseVTableConstructors"), bUseVTableConstructors, GEngineIni);
|
|
}
|
|
|
|
bool bUseVTableConstructors;
|
|
} UseVTableConstructorsCache;
|
|
|
|
UObject* TempObjectForVTable = nullptr;
|
|
{
|
|
TGuardValue<bool> Guard(GIsRetrievingVTablePtr, true);
|
|
FVTableHelper Helper;
|
|
TempObjectForVTable = ClassVTableHelperCtorCaller(Helper);
|
|
TempObjectForVTable->AtomicallyClearInternalFlags(EInternalObjectFlags::PendingConstruction);
|
|
}
|
|
|
|
if (!TempObjectForVTable->IsRooted())
|
|
{
|
|
TempObjectForVTable->MarkPendingKill();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("Hot Reload: Was not expecting temporary object '%s' for class '%s' to become rooted during construction. This object cannot be marked pending kill."), *TempObjectForVTable->GetFName().ToString(), *this->GetName());
|
|
}
|
|
|
|
ClassWithin = TClass_WithinClass_StaticClass;
|
|
|
|
void* NewVTable = *(void**)TempObjectForVTable;
|
|
if (NewVTable != OldVTable)
|
|
{
|
|
int32 Count = 0;
|
|
int32 CountClass = 0;
|
|
for (FRawObjectIterator It; It; ++It)
|
|
{
|
|
UObject* Target = static_cast<UObject*>(It->Object);
|
|
if (OldVTable == *(void**)Target)
|
|
{
|
|
*(void**)Target = NewVTable;
|
|
Count++;
|
|
}
|
|
else if (dynamic_cast<UClass*>(Target))
|
|
{
|
|
UClass* Class = CastChecked<UClass>(Target);
|
|
if (Class->ClassConstructor == OldClassConstructor)
|
|
{
|
|
Class->ClassConstructor = ClassConstructor;
|
|
Class->ClassVTableHelperCtorCaller = ClassVTableHelperCtorCaller;
|
|
Class->ClassAddReferencedObjects = ClassAddReferencedObjects;
|
|
CountClass++;
|
|
}
|
|
}
|
|
}
|
|
UE_LOG(LogClass, Verbose, TEXT("Updated the vtable for %d live objects and %d blueprint classes. %016llx -> %016llx"), Count, CountClass, PTRINT(OldVTable), PTRINT(NewVTable));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogClass, Error, TEXT("VTable for class %s did not change?"), *GetName());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UClass::ReplaceNativeFunction(FName InFName, FNativeFuncPtr InPointer, bool bAddToFunctionRemapTable)
|
|
{
|
|
IHotReloadInterface* HotReloadSupport = nullptr;
|
|
|
|
if (bAddToFunctionRemapTable)
|
|
{
|
|
HotReloadSupport = &FModuleManager::LoadModuleChecked<IHotReloadInterface>("HotReload");
|
|
}
|
|
|
|
// Find the function in the class's native function lookup table.
|
|
for (int32 FunctionIndex = 0; FunctionIndex < NativeFunctionLookupTable.Num(); ++FunctionIndex)
|
|
{
|
|
FNativeFunctionLookup& NativeFunctionLookup = NativeFunctionLookupTable[FunctionIndex];
|
|
if (NativeFunctionLookup.Name == InFName)
|
|
{
|
|
if (bAddToFunctionRemapTable)
|
|
{
|
|
HotReloadSupport->AddHotReloadFunctionRemap(InPointer, NativeFunctionLookup.Pointer);
|
|
}
|
|
NativeFunctionLookup.Pointer = InPointer;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
UClass* UClass::GetAuthoritativeClass()
|
|
{
|
|
#if WITH_HOT_RELOAD && WITH_ENGINE
|
|
if (GIsHotReload)
|
|
{
|
|
const TMap<UClass*, UClass*>& ReinstancedClasses = GetClassesToReinstanceForHotReload();
|
|
if (UClass* const* FoundMapping = ReinstancedClasses.Find(this))
|
|
{
|
|
return *FoundMapping ? *FoundMapping : this;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return this;
|
|
}
|
|
|
|
void UClass::AddNativeFunction(const ANSICHAR* InName, FNativeFuncPtr InPointer)
|
|
{
|
|
FName InFName(InName);
|
|
#if WITH_HOT_RELOAD
|
|
if (GIsHotReload)
|
|
{
|
|
// Find the function in the class's native function lookup table.
|
|
if (ReplaceNativeFunction(InFName, InPointer, true))
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// function was not found, so it's new
|
|
UE_LOG(LogClass, Log, TEXT("Function %s is new."), *InFName.ToString());
|
|
}
|
|
}
|
|
#endif
|
|
new (NativeFunctionLookupTable) FNativeFunctionLookup(InFName, InPointer);
|
|
}
|
|
|
|
void UClass::AddNativeFunction(const WIDECHAR* InName, FNativeFuncPtr InPointer)
|
|
{
|
|
FName InFName(InName);
|
|
#if WITH_HOT_RELOAD
|
|
if (GIsHotReload)
|
|
{
|
|
// Find the function in the class's native function lookup table.
|
|
if (ReplaceNativeFunction(InFName, InPointer, true))
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// function was not found, so it's new
|
|
UE_LOG(LogClass, Log, TEXT("Function %s is new."), *InFName.ToString());
|
|
}
|
|
}
|
|
#endif
|
|
new (NativeFunctionLookupTable) FNativeFunctionLookup(InFName, InPointer);
|
|
}
|
|
|
|
void UClass::CreateLinkAndAddChildFunctionsToMap(const FClassFunctionLinkInfo* Functions, uint32 NumFunctions)
|
|
{
|
|
for (; NumFunctions; --NumFunctions, ++Functions)
|
|
{
|
|
const char* FuncNameUTF8 = Functions->FuncNameUTF8;
|
|
UFunction* Func = Functions->CreateFuncPtr();
|
|
|
|
Func->Next = Children;
|
|
Children = Func;
|
|
|
|
AddFunctionToFunctionMap(Func, FName(UTF8_TO_TCHAR(FuncNameUTF8)));
|
|
}
|
|
}
|
|
|
|
void UClass::ClearFunctionMapsCaches()
|
|
{
|
|
FRWScopeLock ScopeLock(SuperFuncMapLock, FRWScopeLockType::SLT_Write);
|
|
SuperFuncMap.Empty();
|
|
}
|
|
|
|
UFunction* UClass::FindFunctionByName(FName InName, EIncludeSuperFlag::Type IncludeSuper) const
|
|
{
|
|
LLM_SCOPE(ELLMTag::UObject);
|
|
UFunction* Result = FuncMap.FindRef(InName);
|
|
if (Result == nullptr && IncludeSuper == EIncludeSuperFlag::IncludeSuper)
|
|
{
|
|
UClass* SuperClass = GetSuperClass();
|
|
if (SuperClass || Interfaces.Num() > 0)
|
|
{
|
|
bool bFoundInSuperFuncMap = false;
|
|
{
|
|
FRWScopeLock ScopeLock(SuperFuncMapLock, FRWScopeLockType::SLT_ReadOnly);
|
|
if (UFunction** SuperResult = SuperFuncMap.Find(InName))
|
|
{
|
|
Result = *SuperResult;
|
|
bFoundInSuperFuncMap = true;
|
|
}
|
|
}
|
|
|
|
if (!bFoundInSuperFuncMap)
|
|
{
|
|
for (const FImplementedInterface& Inter: Interfaces)
|
|
{
|
|
Result = Inter.Class ? Inter.Class->FindFunctionByName(InName) : nullptr;
|
|
if (Result)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (SuperClass && Result == nullptr)
|
|
{
|
|
Result = SuperClass->FindFunctionByName(InName);
|
|
}
|
|
|
|
FRWScopeLock ScopeLock(SuperFuncMapLock, FRWScopeLockType::SLT_Write);
|
|
SuperFuncMap.Add(InName, Result);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void UClass::AssembleReferenceTokenStreams()
|
|
{
|
|
SCOPED_BOOT_TIMING("AssembleReferenceTokenStreams (can be optimized)");
|
|
// Iterate over all class objects and force the default objects to be created. Additionally also
|
|
// assembles the token reference stream at this point. This is required for class objects that are
|
|
// not taken into account for garbage collection but have instances that are.
|
|
for (FRawObjectIterator It(false); It; ++It) // GetDefaultObject can create a new class, that need to be handled as well, so we cannot use TObjectIterator
|
|
{
|
|
if (UClass* Class = Cast<UClass>((UObject*)(It->Object)))
|
|
{
|
|
// Force the default object to be created (except when we're in the middle of exit purge -
|
|
// this may happen if we exited PreInit early because of error).
|
|
//
|
|
// Keep from handling script generated classes here, as those systems handle CDO
|
|
// instantiation themselves.
|
|
if (!GExitPurge && !Class->HasAnyFlags(RF_BeingRegenerated))
|
|
{
|
|
Class->GetDefaultObject(); // Force the default object to be constructed if it isn't already
|
|
}
|
|
// Assemble reference token stream for garbage collection/ RTGC.
|
|
if (!Class->HasAnyFlags(RF_ClassDefaultObject) && !Class->HasAnyClassFlags(CLASS_TokenStreamAssembled))
|
|
{
|
|
Class->AssembleReferenceTokenStream();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const FString UClass::GetConfigName() const
|
|
{
|
|
static FName NAME_GameplayTags("GameplayTags");
|
|
|
|
if (ClassConfigName == NAME_Engine)
|
|
{
|
|
return GEngineIni;
|
|
}
|
|
else if (ClassConfigName == NAME_Editor)
|
|
{
|
|
return GEditorIni;
|
|
}
|
|
else if (ClassConfigName == NAME_Input)
|
|
{
|
|
return GInputIni;
|
|
}
|
|
else if (ClassConfigName == NAME_Game)
|
|
{
|
|
return GGameIni;
|
|
}
|
|
else if (ClassConfigName == NAME_EditorSettings)
|
|
{
|
|
return GEditorSettingsIni;
|
|
}
|
|
else if (ClassConfigName == NAME_EditorLayout)
|
|
{
|
|
return GEditorLayoutIni;
|
|
}
|
|
else if (ClassConfigName == NAME_EditorKeyBindings)
|
|
{
|
|
return GEditorKeyBindingsIni;
|
|
}
|
|
else if (ClassConfigName == NAME_None)
|
|
{
|
|
UE_LOG(LogClass, Fatal, TEXT("UObject::GetConfigName() called on class with config name 'None'. Class flags = 0x%08X"), (uint32)ClassFlags);
|
|
return TEXT("");
|
|
}
|
|
else if (ClassConfigName == NAME_GameUserSettings)
|
|
{
|
|
return GGameUserSettingsIni;
|
|
}
|
|
else if (ClassConfigName == NAME_GameplayTags)
|
|
{
|
|
return GGameplayTagsIni;
|
|
}
|
|
else
|
|
{
|
|
// generate the class ini name, and make sure it's up to date
|
|
FString ConfigGameName;
|
|
FConfigCacheIni::LoadGlobalIniFile(ConfigGameName, *ClassConfigName.ToString());
|
|
return ConfigGameName;
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR || HACK_HEADER_GENERATOR
|
|
void UClass::GetHideFunctions(TArray<FString>& OutHideFunctions) const
|
|
{
|
|
static const FName NAME_HideFunctions(TEXT("HideFunctions"));
|
|
if (const FString* HideFunctions = FindMetaData(NAME_HideFunctions))
|
|
{
|
|
HideFunctions->ParseIntoArray(OutHideFunctions, TEXT(" "), true);
|
|
}
|
|
}
|
|
|
|
bool UClass::IsFunctionHidden(const TCHAR* InFunction) const
|
|
{
|
|
static const FName NAME_HideFunctions(TEXT("HideFunctions"));
|
|
if (const FString* HideFunctions = FindMetaData(NAME_HideFunctions))
|
|
{
|
|
return !!FCString::StrfindDelim(**HideFunctions, InFunction, TEXT(" "));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UClass::GetAutoExpandCategories(TArray<FString>& OutAutoExpandCategories) const
|
|
{
|
|
static const FName NAME_AutoExpandCategories(TEXT("AutoExpandCategories"));
|
|
if (const FString* AutoExpandCategories = FindMetaData(NAME_AutoExpandCategories))
|
|
{
|
|
AutoExpandCategories->ParseIntoArray(OutAutoExpandCategories, TEXT(" "), true);
|
|
}
|
|
}
|
|
|
|
bool UClass::IsAutoExpandCategory(const TCHAR* InCategory) const
|
|
{
|
|
static const FName NAME_AutoExpandCategories(TEXT("AutoExpandCategories"));
|
|
if (const FString* AutoExpandCategories = FindMetaData(NAME_AutoExpandCategories))
|
|
{
|
|
return !!FCString::StrfindDelim(**AutoExpandCategories, InCategory, TEXT(" "));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UClass::GetAutoCollapseCategories(TArray<FString>& OutAutoCollapseCategories) const
|
|
{
|
|
static const FName NAME_AutoCollapseCategories(TEXT("AutoCollapseCategories"));
|
|
if (const FString* AutoCollapseCategories = FindMetaData(NAME_AutoCollapseCategories))
|
|
{
|
|
AutoCollapseCategories->ParseIntoArray(OutAutoCollapseCategories, TEXT(" "), true);
|
|
}
|
|
}
|
|
|
|
bool UClass::IsAutoCollapseCategory(const TCHAR* InCategory) const
|
|
{
|
|
static const FName NAME_AutoCollapseCategories(TEXT("AutoCollapseCategories"));
|
|
if (const FString* AutoCollapseCategories = FindMetaData(NAME_AutoCollapseCategories))
|
|
{
|
|
return !!FCString::StrfindDelim(**AutoCollapseCategories, InCategory, TEXT(" "));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UClass::GetClassGroupNames(TArray<FString>& OutClassGroupNames) const
|
|
{
|
|
static const FName NAME_ClassGroupNames(TEXT("ClassGroupNames"));
|
|
if (const FString* ClassGroupNames = FindMetaData(NAME_ClassGroupNames))
|
|
{
|
|
ClassGroupNames->ParseIntoArray(OutClassGroupNames, TEXT(" "), true);
|
|
}
|
|
}
|
|
|
|
bool UClass::IsClassGroupName(const TCHAR* InGroupName) const
|
|
{
|
|
static const FName NAME_ClassGroupNames(TEXT("ClassGroupNames"));
|
|
if (const FString* ClassGroupNames = FindMetaData(NAME_ClassGroupNames))
|
|
{
|
|
return !!FCString::StrfindDelim(**ClassGroupNames, InGroupName, TEXT(" "));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif // WITH_EDITOR || HACK_HEADER_GENERATOR
|
|
|
|
IMPLEMENT_CORE_INTRINSIC_CLASS(UClass, UStruct,
|
|
{
|
|
Class->ClassAddReferencedObjects = &UClass::AddReferencedObjects;
|
|
|
|
Class->EmitObjectReference(STRUCT_OFFSET(UClass, ClassDefaultObject), TEXT("ClassDefaultObject"));
|
|
Class->EmitObjectReference(STRUCT_OFFSET(UClass, ClassWithin), TEXT("ClassWithin"));
|
|
Class->EmitObjectReference(STRUCT_OFFSET(UClass, ClassGeneratedBy), TEXT("ClassGeneratedBy"));
|
|
Class->EmitObjectArrayReference(STRUCT_OFFSET(UClass, NetFields), TEXT("NetFields"));
|
|
});
|
|
|
|
void GetPrivateStaticClassBody(
|
|
const TCHAR* PackageName,
|
|
const TCHAR* Name,
|
|
UClass*& ReturnClass,
|
|
void (*RegisterNativeFunc)(),
|
|
uint32 InSize,
|
|
uint32 InAlignment,
|
|
EClassFlags InClassFlags,
|
|
EClassCastFlags InClassCastFlags,
|
|
const TCHAR* InConfigName,
|
|
UClass::ClassConstructorType InClassConstructor,
|
|
UClass::ClassVTableHelperCtorCallerType InClassVTableHelperCtorCaller,
|
|
UClass::ClassAddReferencedObjectsType InClassAddReferencedObjects,
|
|
UClass::StaticClassFunctionType InSuperClassFn,
|
|
UClass::StaticClassFunctionType InWithinClassFn,
|
|
bool bIsDynamic /*= false*/,
|
|
UDynamicClass::DynamicClassInitializerType InDynamicClassInitializerFn /*= nullptr*/
|
|
)
|
|
{
|
|
#if WITH_HOT_RELOAD
|
|
if (GIsHotReload)
|
|
{
|
|
check(!bIsDynamic);
|
|
UPackage* Package = FindPackage(NULL, PackageName);
|
|
if (Package)
|
|
{
|
|
ReturnClass = FindObject<UClass>((UObject*)Package, Name);
|
|
if (ReturnClass)
|
|
{
|
|
if (ReturnClass->HotReloadPrivateStaticClass(
|
|
InSize,
|
|
InClassFlags,
|
|
InClassCastFlags,
|
|
InConfigName,
|
|
InClassConstructor,
|
|
InClassVTableHelperCtorCaller,
|
|
InClassAddReferencedObjects,
|
|
InSuperClassFn(),
|
|
InWithinClassFn()))
|
|
{
|
|
// Register the class's native functions.
|
|
RegisterNativeFunc();
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogClass, Log, TEXT("Could not find existing class %s in package %s for HotReload, assuming new class"), Name, PackageName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogClass, Log, TEXT("Could not find existing package %s for HotReload of class %s, assuming a new package."), PackageName, Name);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!bIsDynamic)
|
|
{
|
|
ReturnClass = (UClass*)GUObjectAllocator.AllocateUObject(sizeof(UClass), alignof(UClass), true);
|
|
ReturnClass = ::new (ReturnClass)
|
|
UClass(
|
|
EC_StaticConstructor,
|
|
Name,
|
|
InSize,
|
|
InAlignment,
|
|
InClassFlags,
|
|
InClassCastFlags,
|
|
InConfigName,
|
|
EObjectFlags(RF_Public | RF_Standalone | RF_Transient | RF_MarkAsNative | RF_MarkAsRootSet),
|
|
InClassConstructor,
|
|
InClassVTableHelperCtorCaller,
|
|
InClassAddReferencedObjects);
|
|
check(ReturnClass);
|
|
}
|
|
else
|
|
{
|
|
ReturnClass = (UClass*)GUObjectAllocator.AllocateUObject(sizeof(UDynamicClass), alignof(UDynamicClass), GIsInitialLoad);
|
|
ReturnClass = ::new (ReturnClass)
|
|
UDynamicClass(
|
|
EC_StaticConstructor,
|
|
Name,
|
|
InSize,
|
|
InAlignment,
|
|
InClassFlags | CLASS_CompiledFromBlueprint,
|
|
InClassCastFlags,
|
|
InConfigName,
|
|
EObjectFlags(RF_Public | RF_Standalone | RF_Transient | RF_Dynamic | (GIsInitialLoad ? RF_MarkAsRootSet : RF_NoFlags)),
|
|
InClassConstructor,
|
|
InClassVTableHelperCtorCaller,
|
|
InClassAddReferencedObjects,
|
|
InDynamicClassInitializerFn);
|
|
check(ReturnClass);
|
|
}
|
|
InitializePrivateStaticClass(
|
|
InSuperClassFn(),
|
|
ReturnClass,
|
|
InWithinClassFn(),
|
|
PackageName,
|
|
Name);
|
|
|
|
// Register the class's native functions.
|
|
RegisterNativeFunc();
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
UFunction.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
UFunction::UFunction(const FObjectInitializer& ObjectInitializer, UFunction* InSuperFunction, EFunctionFlags InFunctionFlags, SIZE_T ParamsSize)
|
|
: UStruct(ObjectInitializer, InSuperFunction, ParamsSize), FunctionFlags(InFunctionFlags), RPCId(0), RPCResponseId(0), FirstPropertyToInit(nullptr)
|
|
#if UE_BLUEPRINT_EVENTGRAPH_FASTCALLS
|
|
,
|
|
EventGraphFunction(nullptr),
|
|
EventGraphCallOffset(0)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
UFunction::UFunction(UFunction* InSuperFunction, EFunctionFlags InFunctionFlags, SIZE_T ParamsSize)
|
|
: UStruct(InSuperFunction, ParamsSize), FunctionFlags(InFunctionFlags), RPCId(0), RPCResponseId(0), FirstPropertyToInit(NULL)
|
|
{
|
|
}
|
|
|
|
void UFunction::InitializeDerivedMembers()
|
|
{
|
|
NumParms = 0;
|
|
ParmsSize = 0;
|
|
ReturnValueOffset = MAX_uint16;
|
|
|
|
for (FProperty* Property = CastField<FProperty>(ChildProperties); Property; Property = CastField<FProperty>(Property->Next))
|
|
{
|
|
if (Property->PropertyFlags & CPF_Parm)
|
|
{
|
|
NumParms++;
|
|
ParmsSize = Property->GetOffset_ForUFunction() + Property->GetSize();
|
|
if (Property->PropertyFlags & CPF_ReturnParm)
|
|
{
|
|
ReturnValueOffset = Property->GetOffset_ForUFunction();
|
|
}
|
|
}
|
|
else if ((FunctionFlags & FUNC_HasDefaults) != 0)
|
|
{
|
|
if (!Property->HasAnyPropertyFlags(CPF_ZeroConstructor))
|
|
{
|
|
FirstPropertyToInit = Property;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UFunction::Invoke(UObject* Obj, FFrame& Stack, RESULT_DECL)
|
|
{
|
|
checkSlow(Func);
|
|
|
|
UClass* OuterClass = (UClass*)GetOuter();
|
|
if (OuterClass->IsChildOf(UInterface::StaticClass()))
|
|
{
|
|
Obj = (UObject*)Obj->GetInterfaceAddress(OuterClass);
|
|
}
|
|
|
|
TGuardValue<UFunction*> NativeFuncGuard(Stack.CurrentNativeFunction, this);
|
|
return (*Func)(Obj, Stack, RESULT_PARAM);
|
|
}
|
|
|
|
void UFunction::Serialize(FArchive& Ar)
|
|
{
|
|
#if WITH_EDITOR
|
|
const static FName NAME_UFunction(TEXT("UFunction"));
|
|
FArchive::FScopeAddDebugData S(Ar, NAME_UFunction);
|
|
FArchive::FScopeAddDebugData Q(Ar, GetFName());
|
|
#endif
|
|
|
|
Super::Serialize(Ar);
|
|
|
|
Ar.ThisContainsCode();
|
|
|
|
Ar << FunctionFlags;
|
|
|
|
// Replication info.
|
|
if (FunctionFlags & FUNC_Net)
|
|
{
|
|
// Unused
|
|
int16 RepOffset = 0;
|
|
Ar << RepOffset;
|
|
}
|
|
|
|
#if !UE_BLUEPRINT_EVENTGRAPH_FASTCALLS
|
|
// We need to serialize these values even if the feature is disabled, in order to keep the serialization stream in sync
|
|
UFunction* EventGraphFunction = nullptr;
|
|
int32 EventGraphCallOffset = 0;
|
|
#endif
|
|
|
|
if (Ar.UE4Ver() >= VER_UE4_SERIALIZE_BLUEPRINT_EVENTGRAPH_FASTCALLS_IN_UFUNCTION)
|
|
{
|
|
Ar << EventGraphFunction;
|
|
Ar << EventGraphCallOffset;
|
|
}
|
|
|
|
// Precomputation.
|
|
if ((Ar.GetPortFlags() & PPF_Duplicate) != 0)
|
|
{
|
|
Ar << NumParms;
|
|
Ar << ParmsSize;
|
|
Ar << ReturnValueOffset;
|
|
Ar << FirstPropertyToInit;
|
|
}
|
|
else
|
|
{
|
|
if (Ar.IsLoading())
|
|
{
|
|
InitializeDerivedMembers();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UFunction::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
UClass* const OwningClass = GetOuterUClass();
|
|
if (OwningClass && HasAnyFunctionFlags(FUNC_Net))
|
|
{
|
|
OwningClass->ClassFlags &= ~CLASS_ReplicationDataIsSetUp;
|
|
}
|
|
}
|
|
|
|
FProperty* UFunction::GetReturnProperty() const
|
|
{
|
|
for (TFieldIterator<FProperty> It(this); It && (It->PropertyFlags & CPF_Parm); ++It)
|
|
{
|
|
if (It->PropertyFlags & CPF_ReturnParm)
|
|
{
|
|
return *It;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void UFunction::Bind()
|
|
{
|
|
UClass* OwnerClass = GetOwnerClass();
|
|
|
|
// if this isn't a native function, or this function belongs to a native interface class (which has no C++ version),
|
|
// use ProcessInternal (call into script VM only) as the function pointer for this function
|
|
if (!HasAnyFunctionFlags(FUNC_Native))
|
|
{
|
|
// Use processing function.
|
|
Func = &UObject::ProcessInternal;
|
|
}
|
|
else
|
|
{
|
|
// Find the function in the class's native function lookup table.
|
|
FName Name = GetFName();
|
|
FNativeFunctionLookup* Found = OwnerClass->NativeFunctionLookupTable.FindByPredicate([=](const FNativeFunctionLookup& NativeFunctionLookup)
|
|
{
|
|
return Name == NativeFunctionLookup.Name;
|
|
});
|
|
if (Found)
|
|
{
|
|
Func = Found->Pointer;
|
|
}
|
|
#if USE_COMPILED_IN_NATIVES
|
|
else if (!HasAnyFunctionFlags(FUNC_NetRequest))
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("Failed to bind native function %s.%s"), *OwnerClass->GetName(), *GetName());
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void UFunction::Link(FArchive& Ar, bool bRelinkExistingProperties)
|
|
{
|
|
Super::Link(Ar, bRelinkExistingProperties);
|
|
|
|
InitializeDerivedMembers();
|
|
}
|
|
|
|
bool UFunction::IsSignatureCompatibleWith(const UFunction* OtherFunction) const
|
|
{
|
|
const uint64 IgnoreFlags = UFunction::GetDefaultIgnoredSignatureCompatibilityFlags();
|
|
|
|
return IsSignatureCompatibleWith(OtherFunction, IgnoreFlags);
|
|
}
|
|
|
|
bool FStructUtils::ArePropertiesTheSame(const FProperty* A, const FProperty* B, bool bCheckPropertiesNames)
|
|
{
|
|
if (A == B)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!A || !B) // one of properties is null
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (bCheckPropertiesNames && (A->GetFName() != B->GetFName()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (A->GetSize() != B->GetSize())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (A->GetOffset_ForGC() != B->GetOffset_ForGC())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!A->SameType(B))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FStructUtils::TheSameLayout(const UStruct* StructA, const UStruct* StructB, bool bCheckPropertiesNames)
|
|
{
|
|
bool bResult = false;
|
|
if (StructA && StructB && (StructA->GetPropertiesSize() == StructB->GetPropertiesSize()) && (StructA->GetMinAlignment() == StructB->GetMinAlignment()))
|
|
{
|
|
const FProperty* PropertyA = StructA->PropertyLink;
|
|
const FProperty* PropertyB = StructB->PropertyLink;
|
|
|
|
bResult = true;
|
|
while (bResult && (PropertyA != PropertyB))
|
|
{
|
|
bResult = ArePropertiesTheSame(PropertyA, PropertyB, bCheckPropertiesNames);
|
|
PropertyA = PropertyA ? PropertyA->PropertyLinkNext : NULL;
|
|
PropertyB = PropertyB ? PropertyB->PropertyLinkNext : NULL;
|
|
}
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
UStruct* FStructUtils::FindStructureInPackageChecked(const TCHAR* StructName, const TCHAR* PackageName)
|
|
{
|
|
const FName StructPackageFName(PackageName);
|
|
if (StructPackageFName != NAME_None)
|
|
{
|
|
static TMap<FName, UPackage*> StaticStructPackageMap;
|
|
|
|
UPackage* StructPackage;
|
|
UPackage** StructPackagePtr = StaticStructPackageMap.Find(StructPackageFName);
|
|
if (StructPackagePtr != nullptr)
|
|
{
|
|
StructPackage = *StructPackagePtr;
|
|
}
|
|
else
|
|
{
|
|
StructPackage = StaticStructPackageMap.Add(StructPackageFName, FindObjectChecked<UPackage>(nullptr, PackageName));
|
|
}
|
|
|
|
return FindObjectChecked<UStruct>(StructPackage, StructName);
|
|
}
|
|
else
|
|
{
|
|
return FindObjectChecked<UStruct>(ANY_PACKAGE, StructName);
|
|
}
|
|
}
|
|
|
|
bool UFunction::IsSignatureCompatibleWith(const UFunction* OtherFunction, uint64 IgnoreFlags) const
|
|
{
|
|
// Early out if they're exactly the same function
|
|
if (this == OtherFunction)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Run thru the parameter property chains to compare each property
|
|
TFieldIterator<FProperty> IteratorA(this);
|
|
TFieldIterator<FProperty> IteratorB(OtherFunction);
|
|
|
|
while (IteratorA && (IteratorA->PropertyFlags & CPF_Parm))
|
|
{
|
|
if (IteratorB && (IteratorB->PropertyFlags & CPF_Parm))
|
|
{
|
|
// Compare the two properties to make sure their types are identical
|
|
// Note: currently this requires both to be strictly identical and wouldn't allow functions that differ only by how derived a class is,
|
|
// which might be desirable when binding delegates, assuming there is directionality in the SignatureIsCompatibleWith call
|
|
FProperty* PropA = *IteratorA;
|
|
FProperty* PropB = *IteratorB;
|
|
|
|
// Check the flags as well
|
|
const uint64 PropertyMash = PropA->PropertyFlags ^ PropB->PropertyFlags;
|
|
if (!FStructUtils::ArePropertiesTheSame(PropA, PropB, false) || ((PropertyMash & ~IgnoreFlags) != 0))
|
|
{
|
|
// Type mismatch between an argument of A and B
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// B ran out of arguments before A did
|
|
return false;
|
|
}
|
|
++IteratorA;
|
|
++IteratorB;
|
|
}
|
|
|
|
// They matched all the way thru A's properties, but it could still be a mismatch if B has remaining parameters
|
|
return !(IteratorB && (IteratorB->PropertyFlags & CPF_Parm));
|
|
}
|
|
|
|
static UScriptStruct* StaticGetBaseStructureInternal(FName Name)
|
|
{
|
|
static UPackage* CoreUObjectPkg = FindObjectChecked<UPackage>(nullptr, TEXT("/Script/CoreUObject"));
|
|
|
|
UScriptStruct* Result = (UScriptStruct*)StaticFindObjectFastInternal(UScriptStruct::StaticClass(), CoreUObjectPkg, Name, false, false, RF_NoFlags, EInternalObjectFlags::None);
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
if (!Result)
|
|
{
|
|
UE_LOG(LogClass, Fatal, TEXT("Failed to find native struct '%s.%s'"), *CoreUObjectPkg->GetName(), *Name.ToString());
|
|
}
|
|
#endif
|
|
return Result;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FRotator>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Rotator"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FQuat>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Quat"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FTransform>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Transform"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FLinearColor>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("LinearColor"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FColor>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Color"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FPlane>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Plane"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FVector>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Vector"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FVector2D>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Vector2D"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FVector4>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Vector4"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FRandomStream>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("RandomStream"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FGuid>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Guid"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FBox2D>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Box2D"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FFallbackStruct>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("FallbackStruct"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FFloatRangeBound>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("FloatRangeBound"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FFloatRange>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("FloatRange"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FInt32RangeBound>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Int32RangeBound"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FInt32Range>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Int32Range"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FFloatInterval>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("FloatInterval"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FInt32Interval>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("Int32Interval"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FSoftObjectPath>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("SoftObjectPath"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FSoftClassPath>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("SoftClassPath"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FPrimaryAssetType>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("PrimaryAssetType"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FPrimaryAssetId>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("PrimaryAssetId"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FPolyglotTextData>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("PolyglotTextData"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FDateTime>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("DateTime"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FFrameNumber>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("FrameNumber"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FFrameTime>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("FrameTime"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FAssetBundleData>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("AssetBundleData"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
UScriptStruct* TBaseStructure<FTestUninitializedScriptStructMembersTest>::Get()
|
|
{
|
|
static UScriptStruct* ScriptStruct = StaticGetBaseStructureInternal(TEXT("TestUninitializedScriptStructMembersTest"));
|
|
return ScriptStruct;
|
|
}
|
|
|
|
IMPLEMENT_CORE_INTRINSIC_CLASS(UFunction, UStruct,
|
|
{});
|
|
|
|
UDelegateFunction::UDelegateFunction(const FObjectInitializer& ObjectInitializer, UFunction* InSuperFunction, EFunctionFlags InFunctionFlags, SIZE_T ParamsSize)
|
|
: UFunction(ObjectInitializer, InSuperFunction, InFunctionFlags, ParamsSize)
|
|
{
|
|
}
|
|
|
|
UDelegateFunction::UDelegateFunction(UFunction* InSuperFunction, EFunctionFlags InFunctionFlags, SIZE_T ParamsSize)
|
|
: UFunction(InSuperFunction, InFunctionFlags, ParamsSize)
|
|
{
|
|
}
|
|
|
|
IMPLEMENT_CORE_INTRINSIC_CLASS(UDelegateFunction, UFunction,
|
|
{});
|
|
|
|
USparseDelegateFunction::USparseDelegateFunction(const FObjectInitializer& ObjectInitializer, UFunction* InSuperFunction, EFunctionFlags InFunctionFlags, SIZE_T ParamsSize)
|
|
: UDelegateFunction(ObjectInitializer, InSuperFunction, InFunctionFlags, ParamsSize)
|
|
{
|
|
}
|
|
|
|
USparseDelegateFunction::USparseDelegateFunction(UFunction* InSuperFunction, EFunctionFlags InFunctionFlags, SIZE_T ParamsSize)
|
|
: UDelegateFunction(InSuperFunction, InFunctionFlags, ParamsSize)
|
|
{
|
|
}
|
|
|
|
void USparseDelegateFunction::Serialize(FArchive& Ar)
|
|
{
|
|
Super::Serialize(Ar);
|
|
|
|
Ar << OwningClassName;
|
|
Ar << DelegateName;
|
|
}
|
|
|
|
IMPLEMENT_CORE_INTRINSIC_CLASS(USparseDelegateFunction, UDelegateFunction,
|
|
{});
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
UDynamicClass constructors.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Internal constructor.
|
|
*/
|
|
UDynamicClass::UDynamicClass(const FObjectInitializer& ObjectInitializer)
|
|
: UClass(ObjectInitializer), AnimClassImplementation(nullptr)
|
|
{
|
|
// If you add properties here, please update the other constructors and PurgeClass()
|
|
}
|
|
|
|
/**
|
|
* Create a new UDynamicClass given its superclass.
|
|
*/
|
|
UDynamicClass::UDynamicClass(const FObjectInitializer& ObjectInitializer, UClass* InBaseClass)
|
|
: UClass(ObjectInitializer, InBaseClass), AnimClassImplementation(nullptr)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Called when dynamically linked.
|
|
*/
|
|
UDynamicClass::UDynamicClass(
|
|
EStaticConstructor,
|
|
FName InName,
|
|
uint32 InSize,
|
|
uint32 InAlignment,
|
|
EClassFlags InClassFlags,
|
|
EClassCastFlags InClassCastFlags,
|
|
const TCHAR* InConfigName,
|
|
EObjectFlags InFlags,
|
|
ClassConstructorType InClassConstructor,
|
|
ClassVTableHelperCtorCallerType InClassVTableHelperCtorCaller,
|
|
ClassAddReferencedObjectsType InClassAddReferencedObjects,
|
|
DynamicClassInitializerType InDynamicClassInitializer)
|
|
: UClass(
|
|
EC_StaticConstructor, InName, InSize, InAlignment, InClassFlags, InClassCastFlags, InConfigName, InFlags, InClassConstructor, InClassVTableHelperCtorCaller, InClassAddReferencedObjects),
|
|
AnimClassImplementation(nullptr),
|
|
DynamicClassInitializer(InDynamicClassInitializer)
|
|
{
|
|
}
|
|
|
|
void UDynamicClass::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
|
|
{
|
|
UDynamicClass* This = CastChecked<UDynamicClass>(InThis);
|
|
|
|
Collector.AddReferencedObjects(This->MiscConvertedSubobjects, This);
|
|
Collector.AddReferencedObjects(This->ReferencedConvertedFields, This);
|
|
Collector.AddReferencedObjects(This->UsedAssets, This);
|
|
Collector.AddReferencedObjects(This->DynamicBindingObjects, This);
|
|
Collector.AddReferencedObjects(This->ComponentTemplates, This);
|
|
Collector.AddReferencedObjects(This->Timelines, This);
|
|
|
|
for (TPair<FName, UClass*>& Override: This->ComponentClassOverrides)
|
|
{
|
|
Collector.AddReferencedObject(Override.Value);
|
|
}
|
|
|
|
Collector.AddReferencedObject(This->AnimClassImplementation, This);
|
|
|
|
Super::AddReferencedObjects(This, Collector);
|
|
}
|
|
|
|
UObject* UDynamicClass::CreateDefaultObject()
|
|
{
|
|
#if DO_CHECK
|
|
if (!HasAnyFlags(RF_ClassDefaultObject) && (0 == (ClassFlags & CLASS_Constructed)))
|
|
{
|
|
UE_LOG(LogClass, Error, TEXT("CDO is created for a dynamic class, before the class was constructed. %s"), *GetPathName());
|
|
}
|
|
#endif
|
|
return Super::CreateDefaultObject();
|
|
}
|
|
|
|
void UDynamicClass::PurgeClass(bool bRecompilingOnLoad)
|
|
{
|
|
Super::PurgeClass(bRecompilingOnLoad);
|
|
|
|
MiscConvertedSubobjects.Empty();
|
|
ReferencedConvertedFields.Empty();
|
|
UsedAssets.Empty();
|
|
|
|
DynamicBindingObjects.Empty();
|
|
ComponentTemplates.Empty();
|
|
Timelines.Empty();
|
|
ComponentClassOverrides.Empty();
|
|
|
|
AnimClassImplementation = nullptr;
|
|
}
|
|
|
|
UObject* UDynamicClass::FindArchetype(const UClass* ArchetypeClass, const FName ArchetypeName) const
|
|
{
|
|
UObject* Archetype = static_cast<UObject*>(FindObjectWithOuter(this, ArchetypeClass, ArchetypeName));
|
|
if (!Archetype)
|
|
{
|
|
// See UBlueprintGeneratedClass::FindArchetype, UE-35259, UE-37480
|
|
const FName ArchetypeBaseName = FName(ArchetypeName, 0);
|
|
if (ArchetypeBaseName != ArchetypeName)
|
|
{
|
|
UObject* const* FountComponentTemplate = ComponentTemplates.FindByPredicate([&](UObject* InObj) -> bool
|
|
{
|
|
return InObj && (InObj->GetFName() == ArchetypeBaseName) && InObj->IsA(ArchetypeClass);
|
|
});
|
|
Archetype = FountComponentTemplate ? *FountComponentTemplate : nullptr;
|
|
}
|
|
}
|
|
const UClass* SuperClass = GetSuperClass();
|
|
return Archetype ? Archetype :
|
|
(SuperClass ? SuperClass->FindArchetype(ArchetypeClass, ArchetypeName) : nullptr);
|
|
}
|
|
|
|
void UDynamicClass::SetupObjectInitializer(FObjectInitializer& ObjectInitializer) const
|
|
{
|
|
for (const TPair<FName, UClass*>& Override: ComponentClassOverrides)
|
|
{
|
|
ObjectInitializer.SetDefaultSubobjectClass(Override.Key, Override.Value);
|
|
}
|
|
|
|
GetSuperClass()->SetupObjectInitializer(ObjectInitializer);
|
|
}
|
|
|
|
FStructProperty* UDynamicClass::FindStructPropertyChecked(const TCHAR* PropertyName) const
|
|
{
|
|
return FindFieldChecked<FStructProperty>(this, PropertyName);
|
|
}
|
|
|
|
const FString& UDynamicClass::GetTempPackagePrefix()
|
|
{
|
|
static const FString PackagePrefix(TEXT("/Temp/__TEMP_BP__"));
|
|
return PackagePrefix;
|
|
}
|
|
|
|
IMPLEMENT_CORE_INTRINSIC_CLASS(UDynamicClass, UClass,
|
|
{
|
|
Class->ClassAddReferencedObjects = &UDynamicClass::AddReferencedObjects;
|
|
});
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER == 1900
|
|
#ifdef PRAGMA_ENABLE_SHADOW_VARIABLE_WARNINGS
|
|
PRAGMA_ENABLE_SHADOW_VARIABLE_WARNINGS
|
|
#endif
|
|
#endif
|
|
|
|
#include "UObject/DefineUPropertyMacros.h"
|