EM_Task/CoreUObject/Private/UObject/UObjectBaseUtility.cpp

630 lines
20 KiB
C++
Raw Permalink Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
UObjectBaseUtility.cpp: Unreal UObject functions that only depend on UObjectBase
=============================================================================*/
#include "UObject/UObjectBaseUtility.h"
#include "UObject/Class.h"
#include "UObject/Package.h"
#include "UObject/UObjectHash.h"
#include "Templates/Casts.h"
#include "UObject/Interface.h"
#include "Misc/StringBuilder.h"
#include "Modules/ModuleManager.h"
#include "ProfilingDebugging/MallocProfiler.h"
/***********************/
/******** Names ********/
/***********************/
/**
* Returns the fully qualified pathname for this object, in the format:
* 'Outermost.[Outer:]Name'
*
* @param StopOuter if specified, indicates that the output string should be relative to this object. if StopOuter
* does not exist in this object's Outer chain, the result would be the same as passing NULL.
*
* @note safe to call on NULL object pointers!
*/
FString UObjectBaseUtility::GetPathName(const UObject* StopOuter /*=NULL*/) const
{
FString Result;
GetPathName(StopOuter, Result);
return Result;
}
/**
* Version of GetPathName() that eliminates unnecessary copies and appends an existing string.
*/
void UObjectBaseUtility::GetPathName(const UObject* StopOuter, FString& ResultString) const
{
TStringBuilder<256> ResultBuilder;
GetPathName(StopOuter, ResultBuilder);
ResultString += FStringView(ResultBuilder);
}
void UObjectBaseUtility::GetPathName(const UObject* StopOuter, FStringBuilderBase& ResultString) const
{
if (this != StopOuter && this != NULL)
{
UObject* ObjOuter = GetOuter();
if (ObjOuter && ObjOuter != StopOuter)
{
ObjOuter->GetPathName(StopOuter, ResultString);
// SUBOBJECT_DELIMITER_CHAR is used to indicate that this object's outer is not a UPackage
if (ObjOuter->GetClass() != UPackage::StaticClass() && ObjOuter->GetOuter()->GetClass() == UPackage::StaticClass())
{
ResultString << SUBOBJECT_DELIMITER_CHAR;
}
else
{
ResultString << TEXT('.');
}
}
GetFName().AppendString(ResultString);
}
else
{
ResultString << TEXT("None");
}
}
/**
* Returns the fully qualified pathname for this object as well as the name of the class, in the format:
* 'ClassName Outermost.[Outer:]Name'.
*
* @param StopOuter if specified, indicates that the output string should be relative to this object. if StopOuter
* does not exist in this object's Outer chain, the result would be the same as passing NULL.
*
* @note safe to call on NULL object pointers!
*/
FString UObjectBaseUtility::GetFullName(const UObject* StopOuter, EObjectFullNameFlags Flags) const
{
FString Result;
Result.Empty(128);
GetFullName(StopOuter, Result, Flags);
return Result;
}
/**
* Version of GetFullName() that eliminates unnecessary copies and appends an existing string.
*/
void UObjectBaseUtility::GetFullName(const UObject* StopOuter, FString& ResultString, EObjectFullNameFlags Flags) const
{
if (this != nullptr)
{
if (EnumHasAllFlags(Flags, EObjectFullNameFlags::IncludeClassPackage))
{
ResultString += GetClass()->GetPathName();
}
else
{
GetClass()->AppendName(ResultString);
}
ResultString += TEXT(' ');
GetPathName(StopOuter, ResultString);
}
else
{
ResultString += TEXT("None");
}
}
/**
* Walks up the chain of packages until it reaches the top level, which it ignores.
*
* @param bStartWithOuter whether to include this object's name in the returned string
* @return string containing the path name for this object, minus the outermost-package's name
*/
FString UObjectBaseUtility::GetFullGroupName(bool bStartWithOuter) const
{
const UObjectBaseUtility* Obj = bStartWithOuter ? GetOuter() : this;
return Obj ? Obj->GetPathName(GetOutermost()) : TEXT("");
}
/***********************/
/*** Outer & Package ***/
/***********************/
void UObjectBaseUtility::DetachExternalPackage()
{
ClearFlags(RF_HasExternalPackage);
}
void UObjectBaseUtility::ReattachExternalPackage()
{
// GetObjectExternalPackageThreadSafe doesn't check for the RF_HasExternalPackage before looking up the external package
if (!HasAnyFlags(RF_HasExternalPackage) && GetObjectExternalPackageThreadSafe(this))
{
SetFlags(RF_HasExternalPackage);
}
}
/**
* Walks up the list of outers until it finds the top-level one that isn't a package.
* Will return null if called on a package
* @return outermost non-null, non-package Outer.
*/
UObject* UObjectBaseUtility::GetOutermostObject() const
{
UObject* Top = (UObject*)(this);
if (Top->IsA<UPackage>())
{
return nullptr;
}
for (;;)
{
UObject* CurrentOuter = Top->GetOuter();
if (CurrentOuter->IsA<UPackage>())
{
return Top;
}
Top = CurrentOuter;
}
}
/**
* Walks up the list of outers until it finds a package directly associated with the object.
*
* @return the package the object is in.
*/
UPackage* UObjectBaseUtility::GetPackage() const
{
const UObject* Top = static_cast<const UObject*>(this);
for (;;)
{
// GetExternalPackage will return itself if called on a UPackage
if (UPackage* Package = Top->GetExternalPackage())
{
return Package;
}
Top = Top->GetOuter();
}
}
/**
* Legacy function, has the same behavior as GetPackage
* use GetPackage instead.
* @return the package the object is in.
* @see GetPackage
*/
UPackage* UObjectBaseUtility::GetOutermost() const
{
return GetPackage();
}
/**
* Finds the outermost package and marks it dirty
*/
bool UObjectBaseUtility::MarkPackageDirty() const
{
// since transient objects will never be saved into a package, there is no need to mark a package dirty
// if we're transient
if (!HasAnyFlags(RF_Transient))
{
UPackage* Package = GetOutermost();
if (Package != NULL)
{
// It is against policy to dirty a map or package during load in the Editor, to enforce this policy
// we explicitly disable the ability to dirty a package or map during load. Commandlets can still
// set the dirty state on load.
if (IsRunningCommandlet() ||
(GIsEditor && !GIsEditorLoadingPackage && !GIsCookerLoadingPackage && !GIsPlayInEditorWorld && !IsInAsyncLoadingThread()
#if WITH_HOT_RELOAD
&& !GIsHotReload
#endif // WITH_HOT_RELOAD
#if WITH_EDITORONLY_DATA
&& !Package->bIsCookedForEditor // Cooked packages can't be modified nor marked as dirty
#endif
))
{
const bool bIsDirty = Package->IsDirty();
// We prevent needless re-dirtying as this can be an expensive operation.
if (!bIsDirty)
{
Package->SetDirtyFlag(true);
}
// Always call PackageMarkedDirtyEvent, even when the package is already dirty
Package->PackageMarkedDirtyEvent.Broadcast(Package, bIsDirty);
return true;
}
else
{
// notify the caller that the request to mark the package as dirty was suppressed
return false;
}
}
}
return true;
}
/**
* Determines whether this object is a template object
*
* @return true if this object is a template object (owned by a UClass)
*/
bool UObjectBaseUtility::IsTemplate(EObjectFlags TemplateTypes) const
{
for (const UObjectBaseUtility* TestOuter = this; TestOuter; TestOuter = TestOuter->GetOuter())
{
if (TestOuter->HasAnyFlags(TemplateTypes))
return true;
}
return false;
}
/**
* Traverses the outer chain searching for the next object of a certain type. (T must be derived from UObject)
*
* @param Target class to search for
* @return a pointer to the first object in this object's Outer chain which is of the correct type.
*/
UObject* UObjectBaseUtility::GetTypedOuter(UClass* Target) const
{
UObject* Result = NULL;
for (UObject* NextOuter = GetOuter(); Result == NULL && NextOuter != NULL; NextOuter = NextOuter->GetOuter())
{
if (NextOuter->IsA(Target))
{
Result = NextOuter;
}
}
return Result;
}
/*-----------------------------------------------------------------------------
UObject accessors that depend on UClass.
-----------------------------------------------------------------------------*/
/**
* @return true if the specified object appears somewhere in this object's outer chain.
*/
bool UObjectBaseUtility::IsIn(const UObject* SomeOuter) const
{
if (SomeOuter->IsA<UPackage>())
{
return IsInPackage(static_cast<const UPackage*>(SomeOuter));
}
return IsInOuter(SomeOuter);
}
/** Overload to determine if an object is in the specified package which can now be different than its outer chain. */
bool UObjectBaseUtility::IsIn(const UPackage* SomePackage) const
{
// uncomment the ensure to more easily find where IsIn should be changed to IsInPackage
// ensure(0);
return IsInPackage(SomePackage);
}
bool UObjectBaseUtility::IsInOuter(const UObject* SomeOuter) const
{
for (UObject* It = GetOuter(); It; It = It->GetOuter())
{
if (It == SomeOuter)
{
return true;
}
}
return SomeOuter == nullptr;
}
/**
* @return true if the object is contained in the specified package.
*/
bool UObjectBaseUtility::IsInPackage(const UPackage* SomePackage) const
{
return SomePackage != this && GetPackage() == SomePackage;
}
/**
* Find out if this object is inside (has an outer) that is of the specified class
* @param SomeBaseClass The base class to compare against
* @return True if this object is in an object of the given type.
*/
bool UObjectBaseUtility::IsInA(const UClass* SomeBaseClass) const
{
for (const UObjectBaseUtility* It = this; It; It = It->GetOuter())
{
if (It->IsA(SomeBaseClass))
{
return true;
}
}
return SomeBaseClass == NULL;
}
/**
* Checks whether this object's top-most package has any of the specified flags
*
* @param CheckFlagMask a bitmask of EPackageFlags values to check for
*
* @return true if the PackageFlags member of this object's top-package has any bits from the mask set.
*/
bool UObjectBaseUtility::RootPackageHasAnyFlags(uint32 CheckFlagMask) const
{
return GetOutermost()->HasAnyPackageFlags(CheckFlagMask);
}
/***********************/
/******** Class ********/
/***********************/
/**
* Finds the most-derived class which is a parent of both TestClass and this object's class.
*
* @param TestClass the class to find the common base for
*/
const UClass* UObjectBaseUtility::FindNearestCommonBaseClass(const UClass* TestClass) const
{
const UClass* Result = NULL;
if (TestClass != NULL)
{
const UClass* CurrentClass = GetClass();
// early out if it's the same class or one is the parent of the other
// (the check for TestClass->IsChildOf(CurrentClass) returns true if TestClass == CurrentClass
if (TestClass->IsChildOf(CurrentClass))
{
Result = CurrentClass;
}
else if (CurrentClass->IsChildOf(TestClass))
{
Result = TestClass;
}
else
{
// find the nearest parent of TestClass which is also a parent of CurrentClass
for (UClass* Cls = TestClass->GetSuperClass(); Cls; Cls = Cls->GetSuperClass())
{
if (CurrentClass->IsChildOf(Cls))
{
Result = Cls;
break;
}
}
}
}
// at this point, Result should only be NULL if TestClass is NULL
checkfSlow(Result != NULL || TestClass == NULL, TEXT("No common base class found for object '%s' with TestClass '%s'"), *GetFullName(), *TestClass->GetFullName());
return Result;
}
/**
* Returns a pointer to this object safely converted to a pointer to the specified interface class.
*
* @param InterfaceClass the interface class to use for the returned type
*
* @return a pointer that can be assigned to a variable of the interface type specified, or NULL if this object's
* class doesn't implement the interface indicated. Will be the same value as 'this' if the interface class
* isn't native.
*/
void* UObjectBaseUtility::GetInterfaceAddress(UClass* InterfaceClass)
{
void* Result = NULL;
if (InterfaceClass != NULL && InterfaceClass->HasAnyClassFlags(CLASS_Interface) && InterfaceClass != UInterface::StaticClass())
{
// Script interface
if (!InterfaceClass->HasAnyClassFlags(CLASS_Native))
{
if (GetClass()->ImplementsInterface(InterfaceClass))
{
// if it isn't a native interface, the address won't be different
Result = this;
}
}
// Native interface
else
{
for (UClass* CurrentClass = GetClass(); Result == NULL && CurrentClass != NULL; CurrentClass = CurrentClass->GetSuperClass())
{
for (TArray<FImplementedInterface>::TIterator It(CurrentClass->Interfaces); It; ++It)
{
// See if this is the implementation we are looking for, and it was done natively, not in K2
FImplementedInterface& ImplInterface = *It;
if (!ImplInterface.bImplementedByK2 && ImplInterface.Class->IsChildOf(InterfaceClass))
{
Result = (uint8*)this + It->PointerOffset;
break;
}
}
}
}
}
return Result;
}
void* UObjectBaseUtility::GetNativeInterfaceAddress(UClass* InterfaceClass)
{
check(InterfaceClass != NULL);
check(InterfaceClass->HasAllClassFlags(CLASS_Interface | CLASS_Native));
check(InterfaceClass != UInterface::StaticClass());
for (UClass* CurrentClass = GetClass(); CurrentClass; CurrentClass = CurrentClass->GetSuperClass())
{
for (auto It = CurrentClass->Interfaces.CreateConstIterator(); It; ++It)
{
// See if this is the implementation we are looking for, and it was done natively, not in K2
auto& ImplInterface = *It;
if (!ImplInterface.bImplementedByK2 && ImplInterface.Class->IsChildOf(InterfaceClass))
{
if (It->PointerOffset)
{
return (uint8*)this + It->PointerOffset;
}
}
}
}
return NULL;
}
bool UObjectBaseUtility::IsDefaultSubobject() const
{
const bool bIsInstanced = GetOuter() && (GetOuter()->HasAnyFlags(RF_ClassDefaultObject) || ((UObject*)this)->GetArchetype() != GetClass()->GetDefaultObject(false));
return bIsInstanced;
}
UClass* GetParentNativeClass(UClass* Class)
{
while (Class && !Class->IsNative())
{
Class = Class->GetSuperClass();
}
return Class;
}
#if STATS && USE_MALLOC_PROFILER
void FScopeCycleCounterUObject::TrackObjectForMallocProfiling(const UObjectBaseUtility* InObject)
{
// Get the package name from the outermost item (if available - can't use GetOutermost here)
FName PackageName;
if (InObject->GetOuter())
{
UObjectBaseUtility* Top = InObject->GetOuter();
for (;;)
{
UObjectBaseUtility* CurrentOuter = Top->GetOuter();
if (!CurrentOuter)
{
PackageName = Top->GetFName();
break;
}
Top = CurrentOuter;
}
}
// Get the class name (if available)
FName ClassName;
if (InObject->GetClass())
{
ClassName = InObject->GetClass()->GetFName();
}
TrackObjectForMallocProfiling(PackageName, ClassName, InObject->GetFName());
}
void FScopeCycleCounterUObject::TrackObjectForMallocProfiling(const FName InPackageName, const FName InClassName, const FName InObjectName)
{
static const TCHAR PackageTagCategory[] = TEXT("Package:");
static const TCHAR ObjectTagCategory[] = TEXT("Object:");
static const TCHAR ClassTagCategory[] = TEXT("Class:");
// We use an array rather than an FString to try and minimize heap allocations
TArray<TCHAR, TInlineAllocator<256>> ScratchSpaceBuffer;
auto AppendNameToBuffer = [&](const FName InName)
{
const FNameEntry* NameEntry = InName.GetDisplayNameEntry();
if (NameEntry->IsWide())
{
WIDECHAR WideName[NAME_SIZE];
NameEntry->GetWideName(WideName);
const WIDECHAR* NameCharPtr = WideName;
while (*NameCharPtr != 0)
{
ScratchSpaceBuffer.Add((TCHAR)*NameCharPtr++);
}
}
else
{
ANSICHAR AnsiName[NAME_SIZE];
NameEntry->GetAnsiName(AnsiName);
const ANSICHAR* NameCharPtr = AnsiName;
while (*NameCharPtr != 0)
{
ScratchSpaceBuffer.Add((TCHAR)*NameCharPtr++);
}
}
};
if (!InPackageName.IsNone())
{
// "Package:/Path/To/Package"
ScratchSpaceBuffer.Reset();
ScratchSpaceBuffer.Append(PackageTagCategory, UE_ARRAY_COUNT(PackageTagCategory) - 1);
AppendNameToBuffer(InPackageName);
ScratchSpaceBuffer.Add(0);
PackageTag = FName(ScratchSpaceBuffer.GetData());
GMallocProfiler->AddTag(PackageTag);
// "Object:/Path/To/Package/ObjectName"
ScratchSpaceBuffer.Reset();
ScratchSpaceBuffer.Append(ObjectTagCategory, UE_ARRAY_COUNT(ObjectTagCategory) - 1);
AppendNameToBuffer(InPackageName);
ScratchSpaceBuffer.Add(TEXT('/'));
AppendNameToBuffer(InObjectName);
ScratchSpaceBuffer.Add(0);
ObjectTag = FName(ScratchSpaceBuffer.GetData());
GMallocProfiler->AddTag(ObjectTag);
}
if (!InClassName.IsNone())
{
// "Class:ClassName"
ScratchSpaceBuffer.Reset();
ScratchSpaceBuffer.Append(ClassTagCategory, UE_ARRAY_COUNT(ClassTagCategory) - 1);
AppendNameToBuffer(InClassName);
ScratchSpaceBuffer.Add(0);
ClassTag = FName(ScratchSpaceBuffer.GetData());
GMallocProfiler->AddTag(ClassTag);
}
}
void FScopeCycleCounterUObject::UntrackObjectForMallocProfiling()
{
if (!PackageTag.IsNone())
{
GMallocProfiler->RemoveTag(PackageTag);
}
if (!ClassTag.IsNone())
{
GMallocProfiler->RemoveTag(ClassTag);
}
if (!ObjectTag.IsNone())
{
GMallocProfiler->RemoveTag(ObjectTag);
}
}
#endif
#if !STATS && !ENABLE_STATNAMEDEVENTS && USE_LIGHTWEIGHT_STATS_FOR_HITCH_DETECTION && USE_HITCH_DETECTION && USE_LIGHTWEIGHT_UOBJECT_STATS_FOR_HITCH_DETECTION
#include "HAL/ThreadHeartBeat.h"
#include "HAL/ThreadManager.h"
void FScopeCycleCounterUObject::ReportHitch()
{
float Delta = float(FGameThreadHitchHeartBeat::Get().GetCurrentTime() - FGameThreadHitchHeartBeat::Get().GetFrameStartTime()) * 1000.0f;
const uint32 CurrentThreadId = FPlatformTLS::GetCurrentThreadId();
const FString& ThreadString = FThreadManager::GetThreadName(CurrentThreadId);
FString StackString;
if (CurrentThreadId == GGameThreadId)
{
if (StatObject->IsValidLowLevel() && StatObject->IsValidLowLevelFast())
{
StackString = GetFullNameSafe(StatObject);
}
else
{
StackString = FString(TEXT("[UObject was invalid]"));
}
}
else
{
StackString = FString(TEXT("[Not grabbing UObject name from other threads]"));
}
UE_LOG(LogCore, Error, TEXT("Leaving UObject scope on hitch (+%8.2fms) [%s] %s"), Delta, *ThreadString, *StackString);
}
#endif