EM_Task/CoreUObject/Private/Blueprint/BlueprintSupport.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

3114 lines
137 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Blueprint/BlueprintSupport.h"
#include "Misc/ScopeLock.h"
#include "Misc/CoreMisc.h"
#include "Misc/ConfigCacheIni.h"
#include "UObject/UObjectHash.h"
#include "UObject/Object.h"
#include "UObject/GarbageCollection.h"
#include "UObject/Class.h"
#include "UObject/Package.h"
#include "Templates/Casts.h"
#include "UObject/UnrealType.h"
#include "Serialization/DuplicatedDataWriter.h"
#include "Misc/PackageName.h"
#include "UObject/ObjectResource.h"
#include "UObject/GCObject.h"
#include "UObject/LinkerPlaceholderClass.h"
#include "UObject/LinkerPlaceholderExportObject.h"
#include "UObject/LinkerPlaceholderFunction.h"
#include "UObject/ReferenceChainSearch.h"
#include "UObject/StructScriptLoader.h"
#include "UObject/UObjectThreadContext.h"
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
#include "UObject/UObjectIterator.h"
#endif
DEFINE_LOG_CATEGORY_STATIC(LogBlueprintSupport, Log, All);
const FName FBlueprintTags::GeneratedClassPath(TEXT("GeneratedClass"));
const FName FBlueprintTags::ParentClassPath(TEXT("ParentClass"));
const FName FBlueprintTags::NativeParentClassPath(TEXT("NativeParentClass"));
const FName FBlueprintTags::ClassFlags(TEXT("ClassFlags"));
const FName FBlueprintTags::BlueprintType(TEXT("BlueprintType"));
const FName FBlueprintTags::BlueprintDescription(TEXT("BlueprintDescription"));
const FName FBlueprintTags::BlueprintDisplayName(TEXT("BlueprintDisplayName"));
const FName FBlueprintTags::IsDataOnly(TEXT("IsDataOnly"));
const FName FBlueprintTags::ImplementedInterfaces(TEXT("ImplementedInterfaces"));
const FName FBlueprintTags::FindInBlueprintsData(TEXT("FiBData"));
const FName FBlueprintTags::UnversionedFindInBlueprintsData(TEXT("FiB"));
const FName FBlueprintTags::NumReplicatedProperties(TEXT("NumReplicatedProperties"));
const FName FBlueprintTags::NumNativeComponents(TEXT("NativeComponents"));
const FName FBlueprintTags::NumBlueprintComponents(TEXT("BlueprintComponents"));
const FName FBlueprintTags::BlueprintPathWithinPackage(TEXT("BlueprintPath"));
/**
* Defined in BlueprintSupport.cpp
* Duplicates all fields of a class in depth-first order. It makes sure that everything contained
* in a class is duplicated before the class itself, as well as all function parameters before the
* function itself.
*
* @param StructToDuplicate Instance of the struct that is about to be duplicated
* @param Writer duplicate writer instance to write the duplicated data to
*/
void FBlueprintSupport::DuplicateAllFields(UStruct* StructToDuplicate, FDuplicateDataWriter& Writer)
{
// This is a very simple fake topological-sort to make sure everything contained in the class
// is processed before the class itself is, and each function parameter is processed before the function
if (StructToDuplicate)
{
// Make sure each field gets allocated into the array
for (TFieldIterator<UField> FieldIt(StructToDuplicate, EFieldIteratorFlags::ExcludeSuper); FieldIt; ++FieldIt)
{
UField* Field = *FieldIt;
// Make sure functions also do their parameters and children first
if (UFunction* Function = dynamic_cast<UFunction*>(Field))
{
for (TFieldIterator<UField> FunctionFieldIt(Function, EFieldIteratorFlags::ExcludeSuper); FunctionFieldIt; ++FunctionFieldIt)
{
UField* InnerField = *FunctionFieldIt;
Writer.GetDuplicatedObject(InnerField);
}
}
Writer.GetDuplicatedObject(Field);
}
}
}
bool FBlueprintSupport::UseDeferredDependencyLoading()
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
static const FBoolConfigValueHelper DeferDependencyLoads(TEXT("Kismet"), TEXT("bDeferDependencyLoads"), GEngineIni);
bool bUseDeferredDependencyLoading = DeferDependencyLoads;
if (FPlatformProperties::RequiresCookedData())
{
static const FBoolConfigValueHelper DisableCookedBuildDefering(TEXT("Kismet"), TEXT("bForceDisableCookedDependencyDeferring"), GEngineIni);
bUseDeferredDependencyLoading &= !((bool)DisableCookedBuildDefering);
}
return bUseDeferredDependencyLoading;
#else
return false;
#endif
}
bool FBlueprintSupport::IsDeferredExportCreationDisabled()
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
static const FBoolConfigValueHelper NoDeferredExports(TEXT("Kismet"), TEXT("bForceDisableDeferredExportCreation"), GEngineIni);
return !UseDeferredDependencyLoading() || NoDeferredExports;
#else
return false;
#endif
}
bool FBlueprintSupport::IsDeferredCDOInitializationDisabled()
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
static const FBoolConfigValueHelper NoDeferredCDOInit(TEXT("Kismet"), TEXT("bForceDisableDeferredCDOInitialization"), GEngineIni);
return !UseDeferredDependencyLoading() || NoDeferredCDOInit;
#else
return false;
#endif
}
static FFlushReinstancingQueueFPtr FlushReinstancingQueueFPtr = nullptr;
static FClassReparentingFPtr ClassReparentingFPtr = nullptr;
void FBlueprintSupport::FlushReinstancingQueue()
{
if (FlushReinstancingQueueFPtr)
{
(*FlushReinstancingQueueFPtr)();
}
}
void FBlueprintSupport::ReparentHierarchies(const TMap<UClass*, UClass*>& OldClassToNewClass)
{
if (ClassReparentingFPtr)
{
(*ClassReparentingFPtr)(OldClassToNewClass);
}
}
void FBlueprintSupport::SetFlushReinstancingQueueFPtr(FFlushReinstancingQueueFPtr Ptr)
{
FlushReinstancingQueueFPtr = Ptr;
}
void FBlueprintSupport::SetClassReparentingFPtr(FClassReparentingFPtr Ptr)
{
ClassReparentingFPtr = Ptr;
}
bool FBlueprintSupport::IsDeferredDependencyPlaceholder(UObject* LoadedObj)
{
return LoadedObj && (LoadedObj->IsA<ULinkerPlaceholderClass>() ||
LoadedObj->IsA<ULinkerPlaceholderFunction>() ||
LoadedObj->IsA<ULinkerPlaceholderExportObject>());
}
void FBlueprintSupport::RegisterDeferredDependenciesInStruct(const UStruct* Struct, void* StructData)
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
if (GEventDrivenLoaderEnabled)
{
return;
}
for (TPropertyValueIterator<const FObjectProperty> It(Struct, StructData); It; ++It)
{
const FObjectProperty* Property = It.Key();
void* PropertyValue = (void*)It.Value();
UObject* ObjectValue = *((UObject**)PropertyValue);
ULinkerPlaceholderExportObject* PlaceholderVal = Cast<ULinkerPlaceholderExportObject>(ObjectValue);
ULinkerPlaceholderClass* PlaceholderClass = Cast<ULinkerPlaceholderClass>(ObjectValue);
if (PlaceholderVal == nullptr && PlaceholderClass == nullptr)
{
continue;
}
// Create a stack of property trackers to deal with any outer Struct Properties
TArray<const FProperty*> PropertyChain;
It.GetPropertyChain(PropertyChain);
TIndirectArray<FScopedPlaceholderPropertyTracker> PlaceholderStack;
// Iterate property chain in reverse order as we need to start with parent
for (int32 PropertyIndex = PropertyChain.Num() - 1; PropertyIndex >= 0; PropertyIndex--)
{
if (const FStructProperty* StructProperty = CastField<FStructProperty>(PropertyChain[PropertyIndex]))
{
PlaceholderStack.Add(new FScopedPlaceholderPropertyTracker(StructProperty));
}
}
if (PlaceholderVal)
{
PlaceholderVal->AddReferencingPropertyValue(Property, PropertyValue);
}
else
{
PlaceholderClass->AddReferencingPropertyValue(Property, PropertyValue);
}
// Specifically destroy entries in reverse order they were added, to simulate unrolling a code stack
for (int32 StackIndex = PlaceholderStack.Num() - 1; StackIndex >= 0; StackIndex--)
{
PlaceholderStack.RemoveAt(StackIndex);
}
}
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
bool FBlueprintSupport::IsInBlueprintPackage(UObject* LoadedObj)
{
UPackage* Pkg = LoadedObj->GetOutermost();
if (Pkg && !Pkg->HasAnyPackageFlags(PKG_CompiledIn))
{
TArray<UObject*> PkgObjects;
GetObjectsWithOuter(Pkg, PkgObjects, /*bIncludeNestedObjects =*/false);
UObject* PkgCDO = nullptr;
UClass* PkgClass = nullptr;
for (UObject* PkgObj: PkgObjects)
{
if (PkgObj->HasAnyFlags(RF_ClassDefaultObject))
{
PkgCDO = PkgObj;
}
else if (UClass* AsClass = Cast<UClass>(PkgObj))
{
PkgClass = AsClass;
}
}
const bool bHasBlueprintClass = PkgClass && PkgClass->HasAnyClassFlags(CLASS_CompiledFromBlueprint);
return bHasBlueprintClass
//&& (PkgCDO && PkgCDO->GetClass() == PkgClass)
#if WITH_EDITORONLY_DATA
//&& (PkgClass->ClassGeneratedBy != nullptr) && (PkgClass->ClassGeneratedBy->GetOuter() == Pkg)
#endif
;
}
return false;
}
static TArray<FBlueprintWarningDeclaration> BlueprintWarnings;
static TSet<FName> BlueprintWarningsToTreatAsError;
static TSet<FName> BlueprintWarningsToSuppress;
void FBlueprintSupport::RegisterBlueprintWarning(const FBlueprintWarningDeclaration& Warning)
{
BlueprintWarnings.Add(Warning);
}
const TArray<FBlueprintWarningDeclaration>& FBlueprintSupport::GetBlueprintWarnings()
{
return BlueprintWarnings;
}
void FBlueprintSupport::UpdateWarningBehavior(const TArray<FName>& WarningIdentifiersToTreatAsError, const TArray<FName>& WarningIdentifiersToSuppress)
{
BlueprintWarningsToTreatAsError = TSet<FName>(WarningIdentifiersToTreatAsError);
BlueprintWarningsToSuppress = TSet<FName>(WarningIdentifiersToSuppress);
}
bool FBlueprintSupport::ShouldTreatWarningAsError(FName WarningIdentifier)
{
return BlueprintWarningsToTreatAsError.Find(WarningIdentifier) != nullptr;
}
bool FBlueprintSupport::ShouldSuppressWarning(FName WarningIdentifier)
{
return BlueprintWarningsToSuppress.Find(WarningIdentifier) != nullptr;
}
bool FBlueprintSupport::IsClassPlaceholder(UClass* Class)
{
while (Class)
{
if (Cast<ULinkerPlaceholderClass>(Class))
{
return true;
}
Class = Class->GetSuperClass();
}
return false;
}
#if WITH_EDITOR
void FBlueprintSupport::ValidateNoRefsToOutOfDateClasses()
{
// ensure no TRASH/REINST types remain:
TArray<UObject*> OutOfDateClasses;
GetObjectsOfClass(UClass::StaticClass(), OutOfDateClasses);
OutOfDateClasses.RemoveAllSwap(
[](UObject* Obj)
{
UClass* AsClass = CastChecked<UClass>(Obj);
return (!AsClass->HasAnyClassFlags(CLASS_NewerVersionExists)) || !AsClass->HasAnyClassFlags(CLASS_CompiledFromBlueprint);
});
for (UObject* Obj: OutOfDateClasses)
{
FReferenceChainSearch RefChainSearch(Obj, EReferenceChainSearchMode::Shortest);
if (RefChainSearch.GetReferenceChains().Num() != 0)
{
RefChainSearch.PrintResults();
ensureAlwaysMsgf(false, TEXT("Found and output bad class references"));
}
}
}
void FBlueprintSupport::ValidateNoExternalRefsToSkeletons()
{
// bit of a hack to find the skel class, because UBlueprint is not visible here,
// but it's very useful to be able to validate BP assumptions in low level code:
auto IsSkeleton = [](UClass* InClass)
{
return InClass->ClassGeneratedBy && InClass->GetName().StartsWith(TEXT("SKEL_"));
};
auto IsOuteredToSkeleton = [IsSkeleton](UObject* Object)
{
UObject* Iter = Object->GetOuter();
while (Iter)
{
if (UClass* AsClass = Cast<UClass>(Iter))
{
if (IsSkeleton(AsClass))
{
return true;
}
}
Iter = Iter->GetOuter();
}
return false;
};
TArray<UObject*> SkeletonClasses;
GetObjectsOfClass(UClass::StaticClass(), SkeletonClasses);
SkeletonClasses.RemoveAllSwap(
[IsSkeleton](UObject* Obj)
{
UClass* AsClass = CastChecked<UClass>(Obj);
return !IsSkeleton(AsClass);
});
for (UObject* SkeletonClass: SkeletonClasses)
{
FReferenceChainSearch RefChainSearch(SkeletonClass, EReferenceChainSearchMode::Shortest | EReferenceChainSearchMode::ExternalOnly);
bool bBadRefs = false;
for (const FReferenceChainSearch::FReferenceChain* Chain: RefChainSearch.GetReferenceChains())
{
if (Chain->GetRootNode()->Object->GetOutermost() != SkeletonClass->GetOutermost())
{
bBadRefs = true;
for (int32 NodeIndex = 1; bBadRefs && NodeIndex < Chain->Num(); ++NodeIndex)
{
// if there's a skeleton class (or an object outered to a skeleton class) somewhere in the chain, then it's fine:
UObject* ObjectReferencingSkeletonClass = Chain->GetNode(NodeIndex)->Object;
if (UClass* AsClass = Cast<UClass>(ObjectReferencingSkeletonClass))
{
if (IsSkeleton(AsClass))
{
bBadRefs = false;
}
}
else if (IsOuteredToSkeleton(ObjectReferencingSkeletonClass))
{
bBadRefs = false;
}
}
}
}
if (bBadRefs)
{
RefChainSearch.PrintResults();
ensureAlwaysMsgf(false, TEXT("Found and output bad references to skeleton classes"));
}
}
}
#endif // WITH_EDITOR
/*******************************************************************************
* FScopedClassDependencyGather
******************************************************************************/
#if WITH_EDITOR
UClass* FScopedClassDependencyGather::BatchMasterClass = NULL;
TArray<UClass*> FScopedClassDependencyGather::BatchClassDependencies;
FScopedClassDependencyGather::FScopedClassDependencyGather(UClass* ClassToGather, FUObjectSerializeContext* InLoadContext)
: bMasterClass(false), LoadContext(InLoadContext)
{
// Do NOT track duplication dependencies, as these are intermediate products that we don't care about
if (!GIsDuplicatingClassForReinstancing)
{
if (BatchMasterClass == NULL)
{
// If there is no current dependency master, register this class as the master, and reset the array
BatchMasterClass = ClassToGather;
BatchClassDependencies.Empty();
bMasterClass = true;
}
else
{
// This class was instantiated while another class was gathering dependencies, so record it as a dependency
BatchClassDependencies.AddUnique(ClassToGather);
}
}
}
FScopedClassDependencyGather::~FScopedClassDependencyGather()
{
// If this gatherer was the initial gatherer for the current scope, process
// dependencies (unless compiling on load is explicitly disabled)
if (bMasterClass)
{
auto DependencyIter = BatchClassDependencies.CreateIterator();
// implemented as a lambda, to prevent duplicated code between
// BatchMasterClass and BatchClassDependencies entries
auto RecompileClassLambda = [&DependencyIter](UClass* Class, FUObjectSerializeContext* InLoadContext)
{
Class->ConditionalRecompileClass(InLoadContext);
// because of the above call to ConditionalRecompileClass(), the
// specified Class gets "cleaned and sanitized" (meaning its old
// properties get moved to a TRASH class, and new ones are
// constructed in their place)... the unfortunate side-effect of
// this is that child classes that have already been linked are now
// referencing TRASH inherited properties; to resolve this issue,
// here we go back through dependencies that were already recompiled
// and re-link any that are sub-classes
//
// @TODO: this isn't the most optimal solution to this problem; we
// should probably instead prevent CleanAndSanitizeClass()
// from running for BytecodeOnly compiles (we would then need
// to block UField re-creation)... UE-14957 was created to
// track this issue
auto ReverseIt = DependencyIter;
for (--ReverseIt; ReverseIt.GetIndex() >= 0; --ReverseIt)
{
UClass* ProcessedDependency = *ReverseIt;
if (ProcessedDependency->IsChildOf(Class))
{
ProcessedDependency->StaticLink(/*bRelinkExistingProperties =*/true);
}
}
};
BatchMasterClass->ConditionalRecompileClass(LoadContext);
BatchMasterClass = NULL;
}
}
TArray<UClass*> const& FScopedClassDependencyGather::GetCachedDependencies()
{
return BatchClassDependencies;
}
#endif // WITH_EDITOR
/*******************************************************************************
* FLinkerLoad
******************************************************************************/
// rather than littering the code with USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// checks, let's just define DEFERRED_DEPENDENCY_CHECK for the file
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
#define DEFERRED_DEPENDENCY_CHECK(CheckExpr) ensure(CheckExpr)
#else // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
#define DEFERRED_DEPENDENCY_CHECK(CheckExpr)
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
struct FPreloadMembersHelper
{
static void PreloadMembers(UObject* InObject)
{
// Collect a list of all things this element owns
TArray<UObject*> BPMemberReferences;
FReferenceFinder ComponentCollector(BPMemberReferences, InObject, false, true, true, true);
ComponentCollector.FindReferences(InObject);
// Iterate over the list, and preload everything so it is valid for refreshing
for (TArray<UObject*>::TIterator it(BPMemberReferences); it; ++it)
{
UObject* CurrentObject = *it;
if (!CurrentObject->HasAnyFlags(RF_LoadCompleted))
{
check(!GEventDrivenLoaderEnabled || !EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME);
CurrentObject->SetFlags(RF_NeedLoad);
if (FLinkerLoad* Linker = CurrentObject->GetLinker())
{
Linker->Preload(CurrentObject);
PreloadMembers(CurrentObject);
}
}
}
}
static void PreloadObject(UObject* InObject)
{
if (InObject && !InObject->HasAnyFlags(RF_LoadCompleted))
{
check(!GEventDrivenLoaderEnabled || !EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME);
InObject->SetFlags(RF_NeedLoad);
if (FLinkerLoad* Linker = InObject->GetLinker())
{
Linker->Preload(InObject);
}
}
}
};
/**
* A helper utility for tracking exports whose classes we're currently running
* through ForceRegenerateClass(). This is primarily relied upon to help prevent
* infinite recursion since ForceRegenerateClass() doesn't do anything to
* progress the state of the linker.
*/
struct FResolvingExportTracker: TThreadSingleton<FResolvingExportTracker>
{
public:
/** */
void FlagLinkerExportAsResolving(FLinkerLoad* Linker, int32 ExportIndex)
{
ResolvingExports.FindOrAdd(Linker).Add(ExportIndex);
}
/** */
bool IsLinkerExportBeingResolved(FLinkerLoad* Linker, int32 ExportIndex) const
{
if (const TSet<int32>* ExportIndices = ResolvingExports.Find(Linker))
{
return ExportIndices->Contains(ExportIndex);
}
return false;
}
/** */
void FlagExportClassAsFullyResolved(FLinkerLoad* Linker, int32 ExportIndex)
{
if (TSet<int32>* ExportIndices = ResolvingExports.Find(Linker))
{
ExportIndices->Remove(ExportIndex);
if (ExportIndices->Num() == 0)
{
ResolvingExports.Remove(Linker);
}
}
}
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
void FlagFullExportResolvePassComplete(FLinkerLoad* Linker)
{
FullyResolvedLinkers.Add(Linker);
}
bool HasPerformedFullExportResolvePass(FLinkerLoad* Linker)
{
return FullyResolvedLinkers.Contains(Linker);
}
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
void Reset(FLinkerLoad* Linker)
{
ResolvingExports.Remove(Linker);
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
FullyResolvedLinkers.Remove(Linker);
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// ClassToPlaceholderMap may have entries because instances of placeholder classes (which
// will be resolved in ResolveDeferredExports()), will never have had ResolvePlaceholders
// for their class called. These entries are harmless and we can discard them here:
ClassToPlaceholderMap.Reset();
}
void AddLinkerPlaceholderObject(UClass* ClassWaitingFor, ULinkerPlaceholderExportObject* Placeholder)
{
ClassToPlaceholderMap.FindOrAdd(ClassWaitingFor).Add(Placeholder);
}
void ResolvePlaceholders(UClass* ForClass)
{
TArray<ULinkerPlaceholderExportObject*>* PlaceholdersPtr = ClassToPlaceholderMap.Find(ForClass);
if (PlaceholdersPtr)
{
// Resolving placeholders below may incur additional loads that can, in turn, add
// new elements to ClassToPlaceholderMap. This could trigger a reallocation of the
// elements and invalidate the value ptr that was obtained above, which could lead
// to an invalid memory access. Thus, we copy the array value here before iterating.
TArray<ULinkerPlaceholderExportObject*> Placeholders(*PlaceholdersPtr);
for (ULinkerPlaceholderExportObject* Placeholder: Placeholders)
{
if (!Placeholder->IsMarkedResolved())
{
FLinkerLoad* Linker = Placeholder->GetLinker();
if (ensure(Linker))
{
Linker->ResolvePlaceholder(Placeholder);
}
}
}
// Remove from map as we could get GCd later
ClassToPlaceholderMap.Remove(ForClass);
}
}
private:
/** */
TMap<FLinkerLoad*, TSet<int32>> ResolvingExports;
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
TSet<FLinkerLoad*> FullyResolvedLinkers;
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
TMap<UClass*, TArray<ULinkerPlaceholderExportObject*>> ClassToPlaceholderMap;
};
/**
* Regenerates/Refreshes a blueprint class
*
* @param LoadClass Instance of the class currently being loaded and which is the parent for the blueprint
* @param ExportObject Current object being exported
* @return Returns true if regeneration was successful, otherwise false
*/
bool FLinkerLoad::RegenerateBlueprintClass(UClass* LoadClass, UObject* ExportObject)
{
auto GetClassSourceObjectLambda = [](UClass* ForClass) -> UObject*
{
return ForClass->ClassGeneratedBy ? ForClass->ClassGeneratedBy : ForClass;
};
UObject* ClassSourceObject = GetClassSourceObjectLambda(LoadClass);
// determine if somewhere further down the callstack, we're already in this
// function for this class
bool const bAlreadyRegenerating = ClassSourceObject->HasAnyFlags(RF_BeingRegenerated);
// Flag the class source object, so we know we're already in the process of compiling this class
ClassSourceObject->SetFlags(RF_BeingRegenerated);
// Cache off the current CDO, and specify the CDO for the load class
// manually... do this before we Preload() any children members so that if
// one of those preloads subsequently ends up back here for this class,
// then the ExportObject is carried along and used in the eventual RegenerateClass() call
UObject* CurrentCDO = ExportObject;
check(!bAlreadyRegenerating || (LoadClass->ClassDefaultObject == ExportObject));
LoadClass->ClassDefaultObject = CurrentCDO;
// Finish loading the class here, so we have all the appropriate data to copy over to the new CDO
TArray<UObject*> AllChildMembers;
GetObjectsWithOuter(LoadClass, /*out*/ AllChildMembers);
for (int32 Index = 0; Index < AllChildMembers.Num(); ++Index)
{
UObject* Member = AllChildMembers[Index];
Preload(Member);
}
// if this was subsequently regenerated from one of the above preloads, then
// we don't have to finish this off, it was already done
bool const bWasSubsequentlyRegenerated = !ClassSourceObject->HasAnyFlags(RF_BeingRegenerated);
// @TODO: find some other condition to block this if we've already
// regenerated the class (not just if we've regenerated the class
// from an above Preload(Member))... UBlueprint::RegenerateClass()
// has an internal conditional to block getting into it again, but we
// can't check UBlueprint members from this module
if (!bWasSubsequentlyRegenerated)
{
Preload(LoadClass);
LoadClass->StaticLink(true);
Preload(CurrentCDO);
// CDO preloaded - we can now resolve placeholders:
FResolvingExportTracker::Get().ResolvePlaceholders(LoadClass);
// Make sure that we regenerate any parent classes first before attempting to build a child
TArray<UClass*> ClassChainOrdered;
{
// Just ordering the class hierarchy from root to leafs:
UClass* ClassChain = LoadClass->GetSuperClass();
while (ClassChain && ClassChain->HasAnyClassFlags(CLASS_CompiledFromBlueprint))
{
// O(n) insert, but n is tiny because this is a class hierarchy...
ClassChainOrdered.Insert(ClassChain, 0);
ClassChain = ClassChain->GetSuperClass();
}
}
for (UClass* SuperClass: ClassChainOrdered)
{
UObject* SuperClassSourceObject = GetClassSourceObjectLambda(SuperClass);
if (SuperClassSourceObject && SuperClassSourceObject->HasAnyFlags(RF_BeingRegenerated))
{
// This code appears to be completely unused:
// Always load the parent blueprint here in case there is a circular dependency. This will
// ensure that the blueprint is fully serialized before attempting to regenerate the class.
FPreloadMembersHelper::PreloadObject(SuperClassSourceObject);
FPreloadMembersHelper::PreloadMembers(SuperClassSourceObject);
// recurse into this function for this parent class;
// 'ClassDefaultObject' should be the class's original ExportObject
RegenerateBlueprintClass(SuperClass, SuperClass->ClassDefaultObject);
}
}
{
ClassSourceObject = GetClassSourceObjectLambda(LoadClass);
// Preload the blueprint to make sure it has all the data the class needs for regeneration
FPreloadMembersHelper::PreloadObject(ClassSourceObject);
UClass* RegeneratedClass = ClassSourceObject->RegenerateClass(LoadClass, CurrentCDO);
if (RegeneratedClass)
{
ClassSourceObject->ClearFlags(RF_BeingRegenerated);
// Fix up the linker so that the RegeneratedClass is used
LoadClass->ClearFlags(RF_NeedLoad | RF_NeedPostLoad | RF_NeedPostLoadSubobjects);
}
#if WITH_EDITOR
// Ensure that the class source object is marked standalone so it doesn't get GC'd in the editor.
// In particular, this is needed for a BPGC asset in a cooked package.
if (LoadClass->bCooked)
{
ClassSourceObject->SetFlags(RF_Standalone);
}
#endif // if WITH_EDITOR
}
}
bool const bSuccessfulRegeneration = !ClassSourceObject->HasAnyFlags(RF_BeingRegenerated);
// if this wasn't already flagged as regenerating when we first entered this
// function, the clear it ourselves.
if (!bAlreadyRegenerating)
{
ClassSourceObject->ClearFlags(RF_BeingRegenerated);
}
return bSuccessfulRegeneration;
}
/**
* Frivolous helper functions, to provide unique identifying names for our different placeholder types.
*/
template <class PlaceholderType>
static FString GetPlaceholderPrefix()
{
return TEXT("PLACEHOLDER_");
}
template <>
FString GetPlaceholderPrefix<ULinkerPlaceholderFunction>()
{
return TEXT("PLACEHOLDER-FUNCTION_");
}
template <>
FString GetPlaceholderPrefix<ULinkerPlaceholderClass>()
{
return TEXT("PLACEHOLDER-CLASS_");
}
/** Internal utility function for spawning various type of placeholder objects. */
template <class PlaceholderType>
static PlaceholderType* MakeImportPlaceholder(UObject* Outer, const TCHAR* TargetObjName, int32 ImportIndex = INDEX_NONE)
{
PlaceholderType* PlaceholderObj = nullptr;
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
FName PlaceholderName(*FString::Printf(TEXT("%s_%s"), *GetPlaceholderPrefix<PlaceholderType>(), TargetObjName));
PlaceholderName = MakeUniqueObjectName(Outer, PlaceholderType::StaticClass(), PlaceholderName);
PlaceholderObj = NewObject<PlaceholderType>(Outer, PlaceholderType::StaticClass(), PlaceholderName, RF_Public | RF_Transient);
if (ImportIndex != INDEX_NONE)
{
PlaceholderObj->PackageIndex = FPackageIndex::FromImport(ImportIndex);
}
// else, this is probably coming from something like an ImportText() call,
// and isn't referenced by the ImportMap... instead, this should be stored
// in the FLinkerLoad's ImportPlaceholders map
// make sure the class is fully formed (has its
// ClassAddReferencedObjects/ClassConstructor members set)
PlaceholderObj->Bind();
PlaceholderObj->StaticLink(/*bRelinkExistingProperties =*/true);
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
if (ULinkerPlaceholderClass* OuterAsPlaceholder = dynamic_cast<ULinkerPlaceholderClass*>(Outer))
{
OuterAsPlaceholder->AddChildObject(PlaceholderObj);
}
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
return PlaceholderObj;
}
/** Recursive utility function, set up to find a specific import that has already been created (emulates a block from FLinkerLoad::CreateImport)*/
static UObject* FindExistingImportObject(const int32 Index, const TArray<FObjectImport>& ImportMap)
{
const FObjectImport& Import = ImportMap[Index];
UObject* FindOuter = nullptr;
if (Import.OuterIndex.IsImport())
{
int32 OuterIndex = Import.OuterIndex.ToImport();
const FObjectImport& OuterImport = ImportMap[OuterIndex];
if (OuterImport.XObject != nullptr)
{
FindOuter = OuterImport.XObject;
}
else
{
FindOuter = FindExistingImportObject(OuterIndex, ImportMap);
}
}
UObject* FoundObject = nullptr;
if (FindOuter != nullptr || Import.OuterIndex.IsNull())
{
if (UObject* ClassPackage = FindObject<UPackage>(/*Outer =*/nullptr, *Import.ClassPackage.ToString()))
{
if (UClass* ImportClass = FindObject<UClass>(ClassPackage, *Import.ClassName.ToString()))
{
// This function is set up to emulate a block towards the top of
// FLinkerLoad::CreateImport(). However, since this is used in
// deferred dependency loading we need to be careful not to invoke
// subsequent loads. The block in CreateImport() calls Preload()
// and GetDefaultObject() which are not suitable here, so to
// emulate/keep the contract that that block provides, we'll only
// lookup the object if its class is loaded, and has a CDO (this
// is just to mitigate risk from this change)
if (!ImportClass->HasAnyFlags(RF_NeedLoad) && ImportClass->ClassDefaultObject)
{
FoundObject = StaticFindObjectFast(ImportClass, FindOuter, Import.ObjectName);
}
}
}
}
return FoundObject;
}
/**
* This utility struct helps track blueprint structs/linkers that are currently
* in the middle of a call to ResolveDeferredDependencies(). This can be used
* to know if a dependency's resolve needs to be finished (to avoid unwanted
* placeholder references ending up in script-code).
*/
struct FUnresolvedStructTracker
{
public:
/** Marks the specified struct (and its linker) as "resolving" for the lifetime of this instance */
FUnresolvedStructTracker(UStruct* LoadStruct)
: TrackedStruct(LoadStruct)
{
DEFERRED_DEPENDENCY_CHECK((LoadStruct != nullptr) && (LoadStruct->GetLinker() != nullptr));
FScopeLock UnresolvedStructsLock(&UnresolvedStructsCritical);
UnresolvedStructs.Add(LoadStruct);
}
/** Removes the wrapped struct from the "resolving" set (it has been fully "resolved") */
~FUnresolvedStructTracker()
{
// even if another FUnresolvedStructTracker added this struct earlier,
// we want the most nested one removing it from the set (because this
// means the struct is fully resolved, even if we're still in the middle
// of a ResolveDeferredDependencies() call further up the stack)
FScopeLock UnresolvedStructsLock(&UnresolvedStructsCritical);
UnresolvedStructs.Remove(TrackedStruct);
}
/**
* Checks to see if the specified import object is a blueprint class/struct
* that is currently in the midst of resolving (and hasn't completed that
* resolve elsewhere in some nested call).
*
* @param ImportObject The object you wish to check.
* @return True if the specified object is a class/struct that hasn't been fully resolved yet (otherwise false).
*/
static bool IsImportStructUnresolved(UObject* ImportObject)
{
FScopeLock UnresolvedStructsLock(&UnresolvedStructsCritical);
return UnresolvedStructs.Contains(ImportObject);
}
/**
* Checks to see if the specified linker is associated with any of the
* unresolved structs that this is currently tracking.
*
* NOTE: This could return false, even if the linker is in a
* ResolveDeferredDependencies() call futher up the callstack... in
* that scenario, the associated struct was fully resolved by a
* subsequent call to the same function (for the same linker/struct).
*
* @param Linker The linker you want to check.
* @return True if the specified linker is in the midst of an unfinished ResolveDeferredDependencies() call (otherwise false).
*/
static bool IsAssociatedStructUnresolved(const FLinkerLoad* Linker)
{
FScopeLock UnresolvedStructsLock(&UnresolvedStructsCritical);
for (UObject* UnresolvedObj: UnresolvedStructs)
{
// each unresolved struct should have a linker set on it, because
// they would have had to go through Preload()
if (UnresolvedObj->GetLinker() == Linker)
{
return true;
}
}
return false;
}
/** */
static void Reset(const FLinkerLoad* Linker)
{
FScopeLock UnresolvedStructsLock(&UnresolvedStructsCritical);
TArray<UObject*> ToRemove;
for (UObject* UnresolvedObj: UnresolvedStructs)
{
if (UnresolvedObj->GetLinker() == Linker)
{
ToRemove.Add(UnresolvedObj);
}
}
for (UObject* ResetingObj: ToRemove)
{
UnresolvedStructs.Remove(ResetingObj);
}
}
private:
/** The struct that is currently being "resolved" */
UStruct* TrackedStruct;
/**
* A set of blueprint structs & classes that are currently being "resolved"
* by ResolveDeferredDependencies() (using UObject* instead of UStruct, so
* we don't have to cast import objects before checking for their presence).
*/
static TSet<UObject*> UnresolvedStructs;
static FCriticalSection UnresolvedStructsCritical;
};
/** A global set that tracks structs currently being ran through (and unfinished by) FLinkerLoad::ResolveDeferredDependencies() */
TSet<UObject*> FUnresolvedStructTracker::UnresolvedStructs;
FCriticalSection FUnresolvedStructTracker::UnresolvedStructsCritical;
bool FLinkerLoad::DeferPotentialCircularImport(const int32 Index)
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
if (!FBlueprintSupport::UseDeferredDependencyLoading())
{
return false;
}
//--------------------------------------
// Phase 1: Stub in Dependencies
//--------------------------------------
FObjectImport& Import = ImportMap[Index];
if (Import.XObject != nullptr)
{
FLinkerPlaceholderBase* ImportPlaceholder = nullptr;
if (ULinkerPlaceholderClass* AsPlaceholderClass = Cast<ULinkerPlaceholderClass>(Import.XObject))
{
ImportPlaceholder = AsPlaceholderClass;
}
else if (ULinkerPlaceholderFunction* AsPlaceholderFunc = Cast<ULinkerPlaceholderFunction>(Import.XObject))
{
ImportPlaceholder = AsPlaceholderFunc;
}
const bool bIsResolvingPlaceholders = ImportPlaceholder && (LoadFlags & LOAD_DeferDependencyLoads) == LOAD_None;
// if this import already had a placeholder spawned for it, but the package
// has passed the need for placeholders (it's in the midst of ResolveDeferredDependencies)
if (bIsResolvingPlaceholders)
{
// this is to validate our assumption that this package is in ResolveDeferredDependencies() earlier up the stack
DEFERRED_DEPENDENCY_CHECK(FUnresolvedStructTracker::IsAssociatedStructUnresolved(this));
UClass* LoadClass = nullptr;
// Get the LoadClass that is currently in the midst of being resolved (needed to pass to ResolveDependencyPlaceholder)
{
// if DeferredCDOIndex is not set, then this is presumably a struct package (it should always be
// set at this point for class BP packages - see Preload() where DeferredCDOIndex is assigned)
if (DeferredCDOIndex != INDEX_NONE)
{
FPackageIndex ClassIndex = ExportMap[DeferredCDOIndex].ClassIndex;
DEFERRED_DEPENDENCY_CHECK(ClassIndex.IsExport());
if (ClassIndex.IsExport())
{
FObjectExport ClassExport = ExportMap[ClassIndex.ToExport()];
LoadClass = Cast<UClass>(ClassExport.Object);
}
DEFERRED_DEPENDENCY_CHECK(LoadClass != nullptr);
}
}
// go ahead and resolve the placeholder here (since someone's requesting it and we're already in the
// midst of resolving placeholders earlier in the stack) - the idea is that the resolve, already in progress, will
// eventually get to this placeholder, it just hasn't looped there yet
//
// this will prevent other, needless placeholders from being created (export templates that are relying on this class, etc.)
ResolveDependencyPlaceholder(ImportPlaceholder, LoadClass);
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
const bool bIsStillPlaceholder = Import.XObject && (Import.XObject->IsA<ULinkerPlaceholderClass>() || Import.XObject->IsA<ULinkerPlaceholderFunction>());
DEFERRED_DEPENDENCY_CHECK(!bIsStillPlaceholder);
return bIsStillPlaceholder;
#else
// presume that ResolveDependencyPlaceholder() worked and the import is no longer a placeholder
return false;
#endif
}
return (ImportPlaceholder != nullptr);
}
if ((LoadFlags & LOAD_DeferDependencyLoads) && !IsImportNative(Index))
{
// emulate the block in CreateImport(), that attempts to find an existing
// object in memory first... this is to account for async loading, which
// can clear Import.XObject (via FLinkerManager::DissociateImportsAndForcedExports)
// at inopportune times (after it's already been set) - in this case
// we shouldn't need a placeholder, because the object already exists; we
// just need to keep from serializing it any further (which is why we've
// emulated it here, to cut out on a Preload() call)
if (!GIsEditor && !IsRunningCommandlet())
{
Import.XObject = FindExistingImportObject(Index, ImportMap);
if (Import.XObject)
{
return true;
}
}
if (UObject* ClassPackage = FindObject<UPackage>(/*Outer =*/nullptr, *Import.ClassPackage.ToString()))
{
if (const UClass* ImportClass = FindObject<UClass>(ClassPackage, *Import.ClassName.ToString()))
{
if (ImportClass->IsChildOf<UClass>())
{
Import.XObject = MakeImportPlaceholder<ULinkerPlaceholderClass>(LinkerRoot, *Import.ObjectName.ToString(), Index);
}
else if (ImportClass->IsChildOf<UFunction>() && Import.OuterIndex.IsImport())
{
const int32 OuterImportIndex = Import.OuterIndex.ToImport();
// @TODO: if the sole reason why we have ULinkerPlaceholderFunction
// is that it's outer is a placeholder, then we
// could instead log it (with the placeholder) as
// a referencer, and then move the function later
if (DeferPotentialCircularImport(OuterImportIndex))
{
UObject* FuncOuter = ImportMap[OuterImportIndex].XObject;
// This is an ugly check to make sure we don't make a placeholder function for a missing native instance.
// We likely also need to avoid making placeholders for anything that's not outered to a ULinkerPlaceholderClass,
// but the DEFERRED_DEPENDENCY_CHECK may be out of date...
if (Cast<UClass>(FuncOuter))
{
Import.XObject = MakeImportPlaceholder<ULinkerPlaceholderFunction>(FuncOuter, *Import.ObjectName.ToString(), Index);
DEFERRED_DEPENDENCY_CHECK(dynamic_cast<ULinkerPlaceholderClass*>(FuncOuter) != nullptr);
}
}
}
}
}
// not the best way to check this (but we don't have ObjectFlags on an
// import), but we don't want non-native (blueprint) CDO refs slipping
// through... we've only seen these needed when serializing a class's
// bytecode, and we resolved that by deferring script serialization
DEFERRED_DEPENDENCY_CHECK(!Import.ObjectName.ToString().StartsWith("Default__"));
}
return (Import.XObject != nullptr);
#else // !USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
return false;
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
#if WITH_EDITOR
/** Helper function find the actual class object given import class and package namme */
static UClass* FindImportClass(FName ClassPackageName, FName ClassName)
{
UClass* Class = nullptr;
UPackage* ClassPackage = Cast<UPackage>(StaticFindObjectFast(UPackage::StaticClass(), nullptr, ClassPackageName));
if (ClassPackage)
{
Class = Cast<UClass>(StaticFindObjectFast(UClass::StaticClass(), ClassPackage, ClassName));
}
return Class;
}
bool FLinkerLoad::IsSuppressableBlueprintImportError(int32 ImportIndex) const
{
// We want to suppress any import errors that target a BlueprintGeneratedClass
// since these issues can occur when an externally referenced Blueprint is saved
// without compiling. This should not be a problem because all Blueprints are
// compiled-on-load.
static const FName NAME_BlueprintGeneratedClass("BlueprintGeneratedClass");
static const FName NAME_EnginePackage("/Script/Engine");
UClass* BlueprintGeneratedClass = FindImportClass(NAME_EnginePackage, NAME_BlueprintGeneratedClass);
check(BlueprintGeneratedClass);
// We will look at each outer of the Import to see if any of them are a BPGC
while (ImportMap.IsValidIndex(ImportIndex))
{
const FObjectImport& TestImport = ImportMap[ImportIndex];
UClass* ImportClass = FindImportClass(TestImport.ClassPackage, TestImport.ClassName);
if (ImportClass && ImportClass->IsChildOf(BlueprintGeneratedClass))
{
// The import is a BPGC, suppress errors
return true;
}
// Check if this is a BP CDO, if so our class will be in the import table
for (const FObjectImport& PotentialBPClass: ImportMap)
{
if (PotentialBPClass.ObjectName == TestImport.ClassName)
{
UClass* PotentialBPClassClass = FindImportClass(PotentialBPClass.ClassPackage, PotentialBPClass.ClassName);
if (PotentialBPClassClass && PotentialBPClassClass->IsChildOf(BlueprintGeneratedClass))
{
return true;
}
}
}
if (!TestImport.OuterIndex.IsNull() && TestImport.OuterIndex.IsImport())
{
ImportIndex = TestImport.OuterIndex.ToImport();
}
else
{
// It's not an import, we are done
break;
}
}
return false;
}
#endif // WITH_EDITOR
/**
* A helper struct that adds and removes its linker/export combo from the
* thread's FResolvingExportTracker (based off the scope it was declared within).
*/
struct FScopedResolvingExportTracker
{
public:
FScopedResolvingExportTracker(FLinkerLoad* Linker, int32 ExportIndex)
: TrackedLinker(Linker), TrackedExport(ExportIndex)
{
FResolvingExportTracker::Get().FlagLinkerExportAsResolving(Linker, ExportIndex);
}
~FScopedResolvingExportTracker()
{
FResolvingExportTracker::Get().FlagExportClassAsFullyResolved(TrackedLinker, TrackedExport);
}
private:
FLinkerLoad* TrackedLinker;
int32 TrackedExport;
};
bool FLinkerLoad::DeferExportCreation(const int32 Index, UObject* Outer)
{
FObjectExport& Export = ExportMap[Index];
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
if (!FBlueprintSupport::UseDeferredDependencyLoading() || FBlueprintSupport::IsDeferredExportCreationDisabled())
{
return false;
}
if ((Export.Object != nullptr))
{
return false;
}
UClass* LoadClass = GetExportLoadClass(Index);
if (LoadClass == nullptr)
{
return false;
}
if (ULinkerPlaceholderExportObject* OuterPlaceholder = Cast<ULinkerPlaceholderExportObject>(Outer))
{
// we deferred the outer, so its constructor has not had a chance
// to create and initialize native subobjects. We must defer this subobject:
FString ClassName = LoadClass->GetName();
FName PlaceholderName(*FString::Printf(TEXT("PLACEHOLDER-INST_of_%s"), *ClassName));
UClass* PlaceholderType = ULinkerPlaceholderExportObject::StaticClass();
PlaceholderName = MakeUniqueObjectName(Outer, PlaceholderType, PlaceholderName);
ULinkerPlaceholderExportObject* Placeholder = NewObject<ULinkerPlaceholderExportObject>(Outer, PlaceholderType, PlaceholderName, RF_Public | RF_Transient);
Placeholder->SetLinker(this, Index, false);
Placeholder->PackageIndex = FPackageIndex::FromExport(Index);
Export.Object = Placeholder;
// the subobject placeholder must be resolved after its outer has been resolved:
OuterPlaceholder->SetupPlaceholderSubobject(Placeholder);
return true;
}
if (LoadClass->HasAnyClassFlags(CLASS_Native))
{
return false;
}
ULinkerPlaceholderClass* AsPlaceholderClass = Cast<ULinkerPlaceholderClass>(LoadClass);
bool const bIsPlaceholderClass = (AsPlaceholderClass != nullptr);
FLinkerLoad* ClassLinker = LoadClass->GetLinker();
if (!bIsPlaceholderClass && ((ClassLinker == nullptr) || !ClassLinker->IsBlueprintFinalizationPending()) && (!LoadClass->ClassDefaultObject || LoadClass->ClassDefaultObject->HasAnyFlags(RF_LoadCompleted) || !LoadClass->ClassDefaultObject->HasAnyFlags(RF_WasLoaded)))
{
return false;
}
bool const bIsLoadingExportClass = (LoadFlags & LOAD_DeferDependencyLoads) ||
IsBlueprintFinalizationPending();
// if we're not in the process of "loading/finalizing" this package's
// Blueprint class, then we're either running this before the linker has got
// to that class, or we're finished and in the midst of regenerating that
// class... either way, we don't have to defer the export (as long as we
// make sure the export's class is fully regenerated... presumably it is in
// the midst of doing so somewhere up the callstack)
if (!bIsLoadingExportClass || (LoadFlags & LOAD_ResolvingDeferredExports) != 0)
{
DEFERRED_DEPENDENCY_CHECK(!IsExportBeingResolved(Index));
FScopedResolvingExportTracker ReentranceGuard(this, Index);
// we want to be very careful, since we haven't filled in the export yet,
// we could get stuck in a recursive loop here (force-finalizing the
// class here ends us back
ForceRegenerateClass(LoadClass);
return false;
}
UPackage* PlaceholderOuter = LinkerRoot;
UClass* PlaceholderType = ULinkerPlaceholderExportObject::StaticClass();
FString ClassName = LoadClass->GetName();
// ClassName.RemoveFromEnd("_C");
FName PlaceholderName(*FString::Printf(TEXT("PLACEHOLDER-INST_of_%s"), *ClassName));
PlaceholderName = MakeUniqueObjectName(PlaceholderOuter, PlaceholderType, PlaceholderName);
ULinkerPlaceholderExportObject* Placeholder = NewObject<ULinkerPlaceholderExportObject>(PlaceholderOuter, PlaceholderType, PlaceholderName, RF_Public | RF_Transient);
Placeholder->PackageIndex = FPackageIndex::FromExport(Index);
Placeholder->SetLinker(this, Index, false);
FResolvingExportTracker::Get().AddLinkerPlaceholderObject(LoadClass, Placeholder);
Export.Object = Placeholder;
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
return true;
}
int32 FLinkerLoad::FindCDOExportIndex(UClass* LoadClass)
{
DEFERRED_DEPENDENCY_CHECK(LoadClass->GetLinker() == this);
int32 const ClassExportIndex = LoadClass->GetLinkerIndex();
// @TODO: the cdo SHOULD be listed after the class in the ExportMap, so we
// could start with ClassExportIndex to save on some cycles
for (int32 ExportIndex = 0; ExportIndex < ExportMap.Num(); ++ExportIndex)
{
FObjectExport& Export = ExportMap[ExportIndex];
if ((Export.ObjectFlags & RF_ClassDefaultObject) && Export.ClassIndex.IsExport() && (Export.ClassIndex.ToExport() == ClassExportIndex))
{
return ExportIndex;
}
}
return INDEX_NONE;
}
UPackage* LoadPackageInternal(UPackage* InOuter, const TCHAR* InLongPackageName, uint32 LoadFlags, FLinkerLoad* ImportLinker, FArchive* InReaderOverride, const FLinkerInstancingContext* InstancingContext);
void FLinkerLoad::ResolveDeferredDependencies(UStruct* LoadStruct)
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
//--------------------------------------
// Phase 2: Resolve Dependency Stubs
//--------------------------------------
TGuardValue<uint32> LoadFlagsGuard(LoadFlags, (LoadFlags & ~LOAD_DeferDependencyLoads));
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
static int32 RecursiveDepth = 0;
int32 const MeasuredDepth = RecursiveDepth;
TGuardValue<int32> RecursiveDepthGuard(RecursiveDepth, RecursiveDepth + 1);
DEFERRED_DEPENDENCY_CHECK((LoadStruct != nullptr) && (LoadStruct->GetLinker() == this));
DEFERRED_DEPENDENCY_CHECK(LoadStruct->HasAnyFlags(RF_LoadCompleted));
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// scoped block to manage the lifetime of ScopedResolveTracker, so that
// this resolve is only tracked for the duration of resolving all its
// placeholder classes, all member struct's placholders, and its parent's
{
FUnresolvedStructTracker ScopedResolveTracker(LoadStruct);
UClass* const LoadClass = Cast<UClass>(LoadStruct);
int32 StartingImportIndex = 0;
// this function (for this linker) could be reentrant (see where we
// recursively call ResolveDeferredDependencies() for super-classes below);
// if that's the case, then we want to finish resolving the pending class
// before we continue on
if (ResolvingPlaceholderStack.Num() > 0)
{
// Since this method is recursive, we don't need to needlessly loop over all the imports we've already
// resolved. However, we can only guarantee that the oldest entry in the 'resolving' stack is from a loop below.
// Now that other places call ResolveDependencyPlaceholder(), the ResolvingPlaceholderStack may jump around and
// skip some entries. The only certainty is that this function is the initial entry point for ResolveDependencyPlaceholder().
const FPackageIndex& FirstResolvingIndex = ResolvingPlaceholderStack[0]->PackageIndex;
if (FirstResolvingIndex.IsNull())
{
// if the placeholder's package index is null, that means we've already looped over the entire
// ImportMap, and moved on to the loop below it (where we resolve placeholders from ImportText()
// and such - they don't have entries in the ImportMap), so skip the ImportMap loop
StartingImportIndex = ImportMap.Num();
}
else
{
DEFERRED_DEPENDENCY_CHECK(FirstResolvingIndex.IsImport());
// Since the ImportMap loop below resolves ULinkerPlaceholderFunction's owner first (out of order), we cannot
// even guarantee that we've resolved everything prior to FirstResolvingIndex, so don't set StartingImportIndex in this case
// if (FirstResolvingIndex.IsImport())
// {
// StartingImportIndex = FirstResolvingIndex.ToImport()+1;
// }
}
while (ResolvingPlaceholderStack.Num() > 0)
{
FLinkerPlaceholderBase* ResolvingPlaceholder = ResolvingPlaceholderStack.Pop();
// If this is a placeholder outside the ImportMap (from ImportText(), etc.), then it needs a PackagePath to
// resolve. Don't worry that one isn't passed in as a param here, ResolveDependencyPlaceholder() will
// look it up itself in ImportPlaceholders (the param is just an optimization)
ResolveDependencyPlaceholder(ResolvingPlaceholder, LoadClass);
}
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
for (int32 ImportIndex = 0; ImportIndex < StartingImportIndex; ++ImportIndex)
{
if (UObject* ImportObj = ImportMap[ImportIndex].XObject)
{
DEFERRED_DEPENDENCY_CHECK(Cast<ULinkerPlaceholderClass>(ImportObj) == nullptr);
DEFERRED_DEPENDENCY_CHECK(Cast<ULinkerPlaceholderFunction>(ImportObj) == nullptr);
}
}
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
}
// because this loop could recurse (and end up finishing all of this for
// us), we check HasUnresolvedDependencies() so we can early out
// from this loop in that situation (the loop has been finished elsewhere)
for (int32 ImportIndex = StartingImportIndex; ImportIndex < ImportMap.Num() && HasUnresolvedDependencies(); ++ImportIndex)
{
FObjectImport& Import = ImportMap[ImportIndex];
FLinkerLoad* SourceLinker = Import.SourceLinker;
// we cannot rely on Import.SourceLinker being set, if you look
// at FLinkerLoad::CreateImport(), you'll see in game builds
// that we try to circumvent the normal Import loading with a
// FindImportFast() call... if this is successful (the import
// has already been somewhat loaded), then we don't fill out the
// SourceLinker field
if (SourceLinker == nullptr)
{
if (Import.XObject != nullptr)
{
SourceLinker = Import.XObject->GetLinker();
// if (SourceLinker == nullptr)
//{
// UPackage* ImportPkg = Import.XObject->GetOutermost();
// // we use this package as placeholder for our
// // placeholders, so make sure to skip those (all other
// // imports should belong to another package)
// if (ImportPkg && ImportPkg != LinkerRoot)
// {
// SourceLinker = FindExistingLinkerForPackage(ImportPkg);
// }
// }
}
}
const UPackage* SourcePackage = (SourceLinker != nullptr) ? SourceLinker->LinkerRoot : nullptr;
// this package may not have introduced any (possible) cyclic
// dependencies, but it still could have been deferred (kept from
// fully loading... we need to make sure metadata gets loaded, etc.)
if ((SourcePackage != nullptr) && !SourcePackage->HasAnyFlags(RF_WasLoaded))
{
uint32 InternalLoadFlags = LoadFlags & (LOAD_NoVerify | LOAD_NoWarn | LOAD_Quiet);
// make sure LoadAllObjects() is called for this package
LoadPackageInternal(/*Outer =*/nullptr, *SourceLinker->Filename, InternalLoadFlags, this, nullptr /*InReaderOverride*/, nullptr /*InstancingContext*/); //-V595
}
DEFERRED_DEPENDENCY_CHECK(ResolvingPlaceholderStack.Num() == 0);
if (ULinkerPlaceholderClass* PlaceholderClass = Cast<ULinkerPlaceholderClass>(Import.XObject))
{
DEFERRED_DEPENDENCY_CHECK(PlaceholderClass->PackageIndex.ToImport() == ImportIndex);
// NOTE: we don't check that this resolve successfully replaced any
// references (by the return value), because this resolve
// could have been re-entered and completed by a nested call
// to the same function (for the same placeholder)
ResolveDependencyPlaceholder(PlaceholderClass, LoadClass);
}
else if (ULinkerPlaceholderFunction* PlaceholderFunction = Cast<ULinkerPlaceholderFunction>(Import.XObject))
{
if (ULinkerPlaceholderClass* PlaceholderOwner = Cast<ULinkerPlaceholderClass>(PlaceholderFunction->GetOwnerClass()))
{
ResolveDependencyPlaceholder(PlaceholderOwner, LoadClass);
}
DEFERRED_DEPENDENCY_CHECK(PlaceholderFunction->PackageIndex.ToImport() == ImportIndex);
ResolveDependencyPlaceholder(PlaceholderFunction, LoadClass);
}
else if (UScriptStruct* StructObj = Cast<UScriptStruct>(Import.XObject))
{
// in case this is a user defined struct, we have to resolve any
// deferred dependencies in the struct
if (SourceLinker != nullptr)
{
SourceLinker->ResolveDeferredDependencies(StructObj);
}
}
DEFERRED_DEPENDENCY_CHECK(ResolvingPlaceholderStack.Num() == 0);
}
// resolve any placeholders that were imported through methods like
// ImportText() (meaning the ImportMap wouldn't reference them)
while (ImportPlaceholders.Num() > 0)
{
auto PlaceholderIt = ImportPlaceholders.CreateIterator();
// store off the key before we resolve, in case this has been recursively removed
const FName PlaceholderKey = PlaceholderIt.Key();
ResolveDependencyPlaceholder(PlaceholderIt.Value(), LoadClass, PlaceholderKey);
ImportPlaceholders.Remove(PlaceholderKey);
}
if (UStruct* SuperStruct = LoadStruct->GetSuperStruct())
{
FLinkerLoad* SuperLinker = SuperStruct->GetLinker();
// NOTE: there is no harm in calling this when the super is not
// "actively resolving deferred dependencies"... this condition
// just saves on wasted ops, looping over the super's ImportMap
if ((SuperLinker != nullptr) && SuperLinker->HasUnresolvedDependencies())
{
// a resolve could have already been started up the stack, and in turn
// started loading a different package that resulted in another (this)
// resolve beginning... in that scenario, the original resolve could be
// for this class's super and we want to make sure that finishes before
// we regenerate this class (else the generated script code could end up
// with unwanted placeholder references; ones that would have been
// resolved by the super's linker)
SuperLinker->ResolveDeferredDependencies(SuperStruct);
}
}
// close the scope on ScopedResolveTracker (so LoadClass doesn't appear to
// be resolving through the rest of this function)
}
// @TODO: don't know if we need this, but could be good to have (as class
// regeneration is about to force load a lot of this), BUT! this
// doesn't work for map packages (because this would load the level's
// ALevelScriptActor instance BEFORE the class has been regenerated)
// LoadAllObjects();
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
auto CheckPlaceholderReferences = [this](FLinkerPlaceholderBase* Placeholder)
{
UObject* PlaceholderObj = Placeholder->GetPlaceholderAsUObject();
if (PlaceholderObj->GetOuter() == LinkerRoot)
{
// there shouldn't be any deferred dependencies (belonging to this
// linker) that need to be resolved by this point
DEFERRED_DEPENDENCY_CHECK(!Placeholder->HasKnownReferences());
if (!Placeholder->PackageIndex.IsNull() && ensure(Placeholder->PackageIndex.IsImport()))
{
const UObject* ImportObj = ImportMap[Placeholder->PackageIndex.ToImport()].XObject;
DEFERRED_DEPENDENCY_CHECK(ImportObj != PlaceholderObj);
DEFERRED_DEPENDENCY_CHECK(Cast<ULinkerPlaceholderClass>(ImportObj) == nullptr);
DEFERRED_DEPENDENCY_CHECK(Cast<ULinkerPlaceholderFunction>(ImportObj) == nullptr);
}
}
};
for (TObjectIterator<ULinkerPlaceholderClass> PlaceholderIt; PlaceholderIt; ++PlaceholderIt)
{
CheckPlaceholderReferences(*PlaceholderIt);
}
for (TObjectIterator<ULinkerPlaceholderFunction> PlaceholderIt; PlaceholderIt; ++PlaceholderIt)
{
CheckPlaceholderReferences(*PlaceholderIt);
}
DEFERRED_DEPENDENCY_CHECK(ImportPlaceholders.Num() == 0);
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
bool FLinkerLoad::HasUnresolvedDependencies() const
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
// checking (ResolvingPlaceholderStack.Num() <= 0) is not sufficient,
// because the linker could be in the midst of a nested resolve (for a
// struct, or super... see FLinkerLoad::ResolveDeferredDependencies)
bool bIsClassExportUnresolved = FUnresolvedStructTracker::IsAssociatedStructUnresolved(this);
// (ResolvingPlaceholderStack.Num() <= 0) should imply
// bIsClassExportUnresolved is true (but not the other way around)
DEFERRED_DEPENDENCY_CHECK((ResolvingPlaceholderStack.Num() <= 0) || bIsClassExportUnresolved);
return bIsClassExportUnresolved;
#else // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
return false;
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
int32 FLinkerLoad::ResolveDependencyPlaceholder(FLinkerPlaceholderBase* PlaceholderIn, UClass* ReferencingClass, const FName ObjectPathIn)
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
TGuardValue<uint32> LoadFlagsGuard(LoadFlags, (LoadFlags & ~LOAD_DeferDependencyLoads));
ResolvingPlaceholderStack.Push(PlaceholderIn);
UObject* PlaceholderObj = PlaceholderIn->GetPlaceholderAsUObject();
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
DEFERRED_DEPENDENCY_CHECK(PlaceholderObj != nullptr);
DEFERRED_DEPENDENCY_CHECK(PlaceholderObj->GetOutermost() == LinkerRoot);
const int32 ResolvingStackDepth = ResolvingPlaceholderStack.Num();
#endif
UObject* RealImportObj = nullptr;
FName ObjectPathName = NAME_None;
if (PlaceholderIn->PackageIndex.IsNull())
{
ObjectPathName = ObjectPathIn;
if (!ObjectPathIn.IsValid() || ObjectPathIn.IsNone())
{
const FName* ObjectPathPtr = ImportPlaceholders.FindKey(PlaceholderIn);
DEFERRED_DEPENDENCY_CHECK(ObjectPathPtr != nullptr);
if (ObjectPathPtr)
{
ObjectPathName = *ObjectPathPtr;
}
}
DEFERRED_DEPENDENCY_CHECK(ObjectPathName.IsValid() && !ObjectPathName.IsNone());
// emulating the StaticLoadObject() call in FObjectPropertyBase::FindImportedObject(),
// since this was most likely a placeholder
RealImportObj = StaticLoadObject(UObject::StaticClass(), /*Outer =*/nullptr, *ObjectPathName.ToString(), /*Filename =*/nullptr, (LOAD_NoWarn | LOAD_FindIfFail));
}
else
{
DEFERRED_DEPENDENCY_CHECK(PlaceholderIn->PackageIndex.IsImport());
int32 const ImportIndex = PlaceholderIn->PackageIndex.ToImport();
FObjectImport& Import = ImportMap[ImportIndex];
if ((Import.XObject != nullptr) && (Import.XObject != PlaceholderObj))
{
DEFERRED_DEPENDENCY_CHECK(ResolvingPlaceholderStack.Num() > 0 && ResolvingPlaceholderStack.Top() == PlaceholderIn);
DEFERRED_DEPENDENCY_CHECK(ResolvingPlaceholderStack.Num() == ResolvingStackDepth);
RealImportObj = Import.XObject;
}
else
{
// clear the placeholder from the import, so that a call to CreateImport()
// properly fills it in
Import.XObject = nullptr;
// NOTE: this is a possible point of recursion... CreateImport() could
// continue to load a package already started up the stack and you
// could end up in another ResolveDependencyPlaceholder() for some
// other placeholder before this one has completely finished resolving
RealImportObj = CreateImport(ImportIndex);
}
}
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
UFunction* AsFunction = Cast<UFunction>(RealImportObj);
UClass* FunctionOwner = (AsFunction != nullptr) ? AsFunction->GetOwnerClass() : nullptr;
// it's ok if super functions come in not fully loaded (missing
// RF_LoadCompleted... meaning it's in the middle of serializing in somewhere
// up the stack); the function will be forcefully ran through Preload(),
// when we regenerate the super class (see FRegenerationHelper::ForcedLoadMembers)
bool const bIsSuperFunction = (AsFunction != nullptr) && (ReferencingClass != nullptr) && ReferencingClass->IsChildOf(FunctionOwner);
// it's also possible that the loaded version of this function has been
// thrown out and replaced with a regenerated version (presumably from a
// blueprint compiling on load)... if that's the case, then this function
// will not have a corresponding linker assigned to it
bool const bIsRegeneratedFunc = (AsFunction != nullptr) && (AsFunction->GetLinker() == nullptr);
bool const bExpectsLoadCompleteFlag = (RealImportObj != nullptr) && !bIsSuperFunction && !bIsRegeneratedFunc;
// if we can't rely on the Import object's RF_LoadCompleted flag, then its
// owner class should at least have it
DEFERRED_DEPENDENCY_CHECK((RealImportObj == nullptr) || bExpectsLoadCompleteFlag ||
(FunctionOwner && FunctionOwner->HasAnyFlags(RF_LoadCompleted | RF_Dynamic)));
DEFERRED_DEPENDENCY_CHECK(RealImportObj != PlaceholderObj);
DEFERRED_DEPENDENCY_CHECK(!bExpectsLoadCompleteFlag || RealImportObj->HasAnyFlags(RF_LoadCompleted | RF_Dynamic));
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
int32 ReplacementCount = 0;
if (ReferencingClass != nullptr)
{
// @TODO: roll this into ULinkerPlaceholderClass's ResolveAllPlaceholderReferences()
for (FImplementedInterface& Interface: ReferencingClass->Interfaces)
{
if (Interface.Class == PlaceholderObj)
{
++ReplacementCount;
Interface.Class = CastChecked<UClass>(RealImportObj, ECastCheckedType::NullAllowed);
}
}
}
// make sure that we know what utilized this placeholder class... right now
// we only expect UObjectProperties/UClassProperties/UInterfaceProperties/
// FImplementedInterfaces to, but something else could have requested the
// class without logging itself with the placeholder... if the placeholder
// doesn't have any known references (and it hasn't already been resolved in
// some recursive call), then there is something out there still using this
// placeholder class
DEFERRED_DEPENDENCY_CHECK((ReplacementCount > 0) || PlaceholderIn->HasKnownReferences() || PlaceholderIn->HasBeenFullyResolved());
ReplacementCount += PlaceholderIn->ResolveAllPlaceholderReferences(RealImportObj);
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// @TODO: not an actual method, but would be nice to circumvent the need for bIsAsyncLoadRef below
// FAsyncObjectsReferencer::Get().RemoveObject(PlaceholderObj);
// there should not be any references left to this placeholder class
// (if there is, then we didn't log that referencer with the placeholder)
FReferencerInformationList UnresolvedReferences;
bool const bIsReferenced = false; // IsReferenced(PlaceholderObj, RF_NoFlags, /*bCheckSubObjects =*/false, &UnresolvedReferences);
// when we're running with async loading there may be an acceptable
// reference left in FAsyncObjectsReferencer (which reports its refs
// through FGCObject::GGCObjectReferencer)... since garbage collection can
// be ran during async loading, FAsyncObjectsReferencer is in charge of
// holding onto objects that are spawned during the process (to ensure
// they're not thrown away prematurely)
bool const bIsAsyncLoadRef = (UnresolvedReferences.ExternalReferences.Num() == 1) &&
PlaceholderObj->HasAnyInternalFlags(EInternalObjectFlags::AsyncLoading) && (UnresolvedReferences.ExternalReferences[0].Referencer == FGCObject::GGCObjectReferencer);
DEFERRED_DEPENDENCY_CHECK(!bIsReferenced || bIsAsyncLoadRef);
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// this could recurse back into ResolveDeferredDependencies(), which resolves all placeholders from this list,
// so by the time we're returned here, the list may be empty
if (ResolvingPlaceholderStack.Num() > 0)
{
DEFERRED_DEPENDENCY_CHECK(ResolvingPlaceholderStack.Top() == PlaceholderIn);
DEFERRED_DEPENDENCY_CHECK(ResolvingPlaceholderStack.Num() == ResolvingStackDepth);
ResolvingPlaceholderStack.Pop();
}
ImportPlaceholders.Remove(ObjectPathName);
return ReplacementCount;
#else // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
return 0;
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
void FLinkerLoad::PRIVATE_ForceLoadAllDependencies(UPackage* Package)
{
if (FLinkerLoad* PkgLinker = FindExistingLinkerForPackage(Package))
{
PkgLinker->ResolveAllImports();
}
}
void FLinkerLoad::ResolveAllImports()
{
for (int32 ImportIndex = 0; ImportIndex < ImportMap.Num() && IsBlueprintFinalizationPending(); ++ImportIndex)
{
// first, make sure every import object is available... just because
// it isn't present in the map already, doesn't mean it isn't in the
// middle of a resolve (the CreateImport() brings in an export
// object from another package, which could be resolving itself)...
//
// don't fret, all these imports were bound to get created sooner or
// later (like when the blueprint was regenerated)
//
// NOTE: this is a possible root point for recursion... accessing a
// separate package could continue its loading process which
// in turn, could end us back in this function before we ever
// returned from this
FObjectImport& Import = ImportMap[ImportIndex];
UObject* ImportObject = CreateImport(ImportIndex);
// see if this import is currently being resolved (presumably somewhere
// up the callstack)... if it is, we need to ensure that this dependency
// is fully resolved before we get to regenerating the blueprint (else,
// we could end up with placeholder classes in our script-code)
if (FUnresolvedStructTracker::IsImportStructUnresolved(ImportObject))
{
// because it is tracked by FUnresolvedStructTracker, it must be a struct
DEFERRED_DEPENDENCY_CHECK(Cast<UStruct>(ImportObject) != nullptr);
FLinkerLoad* SourceLinker = FindExistingLinkerForImport(ImportIndex);
if (SourceLinker)
{
SourceLinker->ResolveDeferredDependencies((UStruct*)ImportObject);
}
}
}
}
void FLinkerLoad::FinalizeBlueprint(UClass* LoadClass)
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
if (!FBlueprintSupport::UseDeferredDependencyLoading())
{
return;
}
DEFERRED_DEPENDENCY_CHECK(LoadClass->HasAnyFlags(RF_LoadCompleted));
//--------------------------------------
// Phase 3: Finalize (serialize CDO & regenerate class)
//--------------------------------------
TGuardValue<uint32> LoadFlagsGuard(LoadFlags, LoadFlags & ~LOAD_DeferDependencyLoads);
// we can get in a state where a sub-class is getting finalized here, before
// its super-class has been "finalized" (like when the super's
// ResolveDeferredDependencies() ends up Preloading a sub-class), so we
// want to make sure that its properly finalized before this sub-class is
// (so we can have a properly formed sub-class)
if (UClass* SuperClass = LoadClass->GetSuperClass())
{
FLinkerLoad* SuperLinker = SuperClass->GetLinker();
if ((SuperLinker != nullptr) && SuperLinker->IsBlueprintFinalizationPending())
{
DEFERRED_DEPENDENCY_CHECK(SuperLinker->DeferredCDOIndex != INDEX_NONE || SuperLinker->bForceBlueprintFinalization);
UObject* SuperCDO = SuperLinker->DeferredCDOIndex != INDEX_NONE ? SuperLinker->ExportMap[SuperLinker->DeferredCDOIndex].Object : SuperClass->ClassDefaultObject;
// we MUST have the super fully serialized before we can finalize
// this (class and CDO); if the SuperCDO is already in the midst of
// serializing somewhere up the stack (and a cyclic dependency has
// landed us here, finalizing one of it's children), then it is
// paramount that we force it through serialization (so we reset the
// RF_NeedLoad guard, and leave it to ResolveDeferredExports, for it
// to re-run the serialization)
if ((SuperCDO != nullptr) && !SuperCDO->HasAnyFlags(RF_NeedLoad | RF_LoadCompleted))
{
check(!GEventDrivenLoaderEnabled || !EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME);
SuperCDO->SetFlags(RF_NeedLoad);
}
SuperLinker->FinalizeBlueprint(SuperClass);
}
}
// at this point, we're sure that LoadClass doesn't contain any class
// placeholders (because ResolveDeferredDependencies() was ran on it);
// however, once we get to regenerating/compiling the blueprint, the graph
// (nodes, pins, etc.) could bring in new dependencies that weren't part of
// the class... this would normally be all fine and well, but in complicated
// dependency chains those graph-dependencies could already be in the middle
// of resolving themselves somewhere up the stack... if we just continue
// along and let the blueprint compile, then it could end up with
// placeholder refs in its script code (which it bad); we need to make sure
// that all dependencies don't have any placeholder classes left in them
//
// we don't want this to be part of ResolveDeferredDependencies()
// because we don't want this to count as a linker's "class resolution
// phase"; at this point, it is ok if other blueprints compile with refs to
// this LoadClass since it doesn't have any placeholders left in it (we also
// don't want this linker externally claiming that it has resolving left to
// do, otherwise other linkers could want to finish this off when they don't
// have to)... we do however need it here in FinalizeBlueprint(), because
// we need it ran for any super-classes before we regen
ResolveAllImports();
// Now that imports have been resolved we optionally flush the compilation
// queue. This is only done for level blueprints, which will have instances
// of actors in them that cannot reliably be reinstanced on load (see useage
// of Scene pointers in things like UActorComponent::ExecuteRegisterEvents)
// - on load the Scene may not yet be created, meaning this code cannot
// correctly be run. We could address that, but avoiding reinstancings is
// also a performance win:
#if WITH_EDITOR
LoadClass->FlushCompilationQueueForLevel();
#endif
// interfaces can invalidate classes which implement them (when the
// interface is regenerated), they essentially define the makeup of the
// implementing class; so here, like we do with the parent class above, we
// ensure that all implemented interfaces are finalized first - this helps
// avoid cyclic issues where an interface ends up invalidating a dependent
// class by being regenerated after the class (see UE-28846)
for (const FImplementedInterface& InterfaceDesc: LoadClass->Interfaces)
{
FLinkerLoad* InterfaceLinker = (InterfaceDesc.Class) ? InterfaceDesc.Class->GetLinker() : nullptr;
if ((InterfaceLinker != nullptr) && InterfaceLinker->IsBlueprintFinalizationPending())
{
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// the interface import should have been properly resolved above, in
// ResolveAllImports()
if (!ensure(!InterfaceLinker->HasUnresolvedDependencies()))
#else
if (InterfaceLinker->HasUnresolvedDependencies())
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
{
InterfaceLinker->ResolveDeferredDependencies(InterfaceDesc.Class);
}
InterfaceLinker->FinalizeBlueprint(InterfaceDesc.Class);
}
}
// replace any export placeholders that were created, and serialize in the
// class's CDO
ResolveDeferredExports(LoadClass);
// the above calls (ResolveAllImports(), ResolveDeferredExports(), etc.)
// could have caused some recursion... if it ended up finalizing a sub-class
// (of LoadClass), then that would have finalized this as well; so, before
// we continue, make sure that this didn't already get fully executed in
// some nested call
if (IsBlueprintFinalizationPending())
{
int32 DeferredCDOIndexCopy = DeferredCDOIndex;
UObject* CDO = DeferredCDOIndex != INDEX_NONE ? ExportMap[DeferredCDOIndexCopy].Object : LoadClass->ClassDefaultObject;
// clear this so IsBlueprintFinalizationPending() doesn't report true:
FLinkerLoad::bForceBlueprintFinalization = false;
// clear this because we're processing this CDO now:
DeferredCDOIndex = INDEX_NONE;
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// at this point there should not be any instances of the Blueprint
// (else, we'd have to re-instance and that is too expensive an
// operation to have at load time)
TArray<UObject*> ClassInstances;
GetObjectsOfClass(LoadClass, ClassInstances, /*bIncludeDerivedClasses =*/true);
// Filter out instances that are part of this package, they were handled in ResolveDeferredExports:
ClassInstances.RemoveAllSwap([LoadClass](UObject* Obj)
{
return Obj->GetOutermost() == LoadClass->GetOutermost();
});
for (UObject* ClassInst: ClassInstances)
{
// in the case that we do end up with instances, use this to find
// where they are referenced (to help sleuth out when/where they
// were created)
FReferencerInformationList InstanceReferences;
bool const bIsReferenced = false; // IsReferenced(ClassInst, RF_NoFlags, /*bCheckSubObjects =*/false, &InstanceReferences);
DEFERRED_DEPENDENCY_CHECK(!bIsReferenced);
}
DEFERRED_DEPENDENCY_CHECK(ClassInstances.Num() == 0);
UClass* BlueprintClass = DeferredCDOIndexCopy != INDEX_NONE ? Cast<UClass>(IndexToObject(ExportMap[DeferredCDOIndexCopy].ClassIndex)) : LoadClass;
DEFERRED_DEPENDENCY_CHECK(BlueprintClass == LoadClass);
DEFERRED_DEPENDENCY_CHECK(BlueprintClass->HasAnyClassFlags(CLASS_CompiledFromBlueprint));
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// for cooked builds (we skip script serialization for editor builds),
// certain scripts can contain references to external dependencies; and
// since the script is serialized in with the class (functions) we want
// those dependencies deferred until now (we expect this to be the right
// spot, because in editor builds, with RegenerateBlueprintClass(), this
// is where script code is regenerated)
FStructScriptLoader::ResolveDeferredScriptLoads(this);
DEFERRED_DEPENDENCY_CHECK(ImportPlaceholders.Num() == 0);
DEFERRED_DEPENDENCY_CHECK(LoadClass->GetOutermost() != GetTransientPackage());
// if we enable deferred dependency loading for cooked assets, and if we're also
// not in the editor context... we want to keep from regenerating in that scenario
#if !WITH_EDITOR
if (!LoadClass->bCooked)
#endif
{
UObject* OldCDO = LoadClass->ClassDefaultObject;
if (RegenerateBlueprintClass(LoadClass, CDO))
{
// emulate class CDO serialization (RegenerateBlueprintClass() could
// have a side-effect where it overwrites the class's CDO; so we
// want to make sure that we don't overwrite that new CDO with a
// stale one)
if (OldCDO == LoadClass->ClassDefaultObject)
{
LoadClass->ClassDefaultObject = CDO;
}
}
}
}
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
void FLinkerLoad::ResolveDeferredExports(UClass* LoadClass)
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
if (!IsBlueprintFinalizationPending())
{
return;
}
DEFERRED_DEPENDENCY_CHECK(DeferredCDOIndex != INDEX_NONE || bForceBlueprintFinalization);
UObject* BlueprintCDO = DeferredCDOIndex != INDEX_NONE ? ExportMap[DeferredCDOIndex].Object : LoadClass->ClassDefaultObject;
DEFERRED_DEPENDENCY_CHECK(BlueprintCDO != nullptr);
TArray<int32> DeferredTemplateObjects;
if (!FBlueprintSupport::IsDeferredExportCreationDisabled())
{
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
auto IsPlaceholderReferenced = [](ULinkerPlaceholderExportObject* ExportPlaceholder) -> bool
{
UObject* PlaceholderObj = ExportPlaceholder;
FReferencerInformationList UnresolvedReferences;
bool bIsReferenced = IsReferenced(PlaceholderObj, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, /*bCheckSubObjects =*/false, &UnresolvedReferences);
if (bIsReferenced && IsAsyncLoading())
{
// if we're async loading, then we assume a single external
// reference belongs to FAsyncObjectsReferencer, which is allowable
bIsReferenced = (UnresolvedReferences.ExternalReferences.Num() != 1) || (UnresolvedReferences.InternalReferences.Num() > 0);
}
return bIsReferenced;
};
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// we may have circumvented an export creation or two to avoid instantiating
// an BP object before its class has been finalized (to avoid costly re-
// instancing operations when the class ultimately finalizes)... so here, we
// find those skipped exports and properly create them (before we finalize
// our own class)
// Mark this linker as ResolvingDeferredExports so that we don't continue deferring exports
// we clear this flag after the loop. We have no TGuardValue for flags and so I'm setting
// and clearing the bit manually:
LoadFlags |= LOAD_ResolvingDeferredExports;
for (int32 ExportIndex = 0; ExportIndex < ExportMap.Num() && IsBlueprintFinalizationPending(); ++ExportIndex)
{
FObjectExport& Export = ExportMap[ExportIndex];
if (ULinkerPlaceholderExportObject* PlaceholderExport = Cast<ULinkerPlaceholderExportObject>(Export.Object))
{
if (Export.ClassIndex.IsExport())
{
DeferredTemplateObjects.Push(ExportIndex);
continue;
}
if (PlaceholderExport->IsDeferredSubobject())
{
continue;
}
UClass* ExportClass = GetExportLoadClass(ExportIndex);
// export class could be null... we create these placeholder
// exports for objects that are instances of an external
// (Blueprint) type, not knowing if that type (class) will
// successfully load... it may resolve to null in scenarios
// where its super class has been deleted, or its super belonged
// to a plugin that has been removed/disabled
if (ExportClass != nullptr)
{
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
DEFERRED_DEPENDENCY_CHECK(!ExportClass->HasAnyClassFlags(CLASS_Intrinsic) && ExportClass->HasAnyClassFlags(CLASS_CompiledFromBlueprint));
FLinkerLoad* ClassLinker = ExportClass->GetLinker();
DEFERRED_DEPENDENCY_CHECK((ClassLinker != nullptr) && (ClassLinker != this));
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
FScopedResolvingExportTracker ForceRegenGuard(this, ExportIndex);
// make sure this export's class is fully regenerated before
// we instantiate it (so we don't have to re-inst on load)
ForceRegenerateClass(ExportClass);
if (PlaceholderExport != Export.Object)
{
DEFERRED_DEPENDENCY_CHECK(!IsPlaceholderReferenced(PlaceholderExport));
continue;
}
}
// replace the placeholder with the proper object instance
PlaceholderExport->SetLinker(nullptr, INDEX_NONE);
Export.ResetObject();
UObject* ExportObj = CreateExport(ExportIndex);
// NOTE: we don't count how many references were resolved (and
// assert on it), because this could have only been created as
// part of the LoadAllObjects() pass (not for any specific
// container object).
PlaceholderExport->ResolveAllPlaceholderReferences(ExportObj);
ResolvedDeferredSubobjects(PlaceholderExport);
PlaceholderExport->MarkPendingKill();
// if we hadn't used a ULinkerPlaceholderExportObject in place of
// the expected export, then someone may have wanted it preloaded
if (ExportObj != nullptr)
{
Preload(ExportObj);
}
DEFERRED_DEPENDENCY_CHECK(!IsPlaceholderReferenced(PlaceholderExport));
}
}
LoadFlags &= ~LOAD_ResolvingDeferredExports;
}
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// this helps catch any placeholder export objects that may be created
// between now and when DeferredCDOIndex is cleared (they won't be resolved,
// so that is a problem!)
FResolvingExportTracker::Get().FlagFullExportResolvePassComplete(this);
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
// the ExportMap loop above could have recursed back into "finalization" for
// this asset, subsequently resolving all exports before this function could
// finish... that means there's no work left for this to do (and trying to
// redo the work would cause a crash), so we guard here against that
if (IsBlueprintFinalizationPending())
{
// have to prematurely set the CDO's linker so we can force a Preload()/
// Serialization of the CDO before we regenerate the Blueprint class
{
if (DeferredCDOIndex != INDEX_NONE)
{
const EObjectFlags OldFlags = BlueprintCDO->GetFlags();
BlueprintCDO->ClearFlags(RF_NeedLoad | RF_NeedPostLoad | RF_NeedPostLoadSubobjects);
BlueprintCDO->SetLinker(this, DeferredCDOIndex, /*bShouldDetatchExisting =*/false);
BlueprintCDO->SetFlags(OldFlags);
}
}
DEFERRED_DEPENDENCY_CHECK(BlueprintCDO->GetClass() == LoadClass);
// should load the CDO (ensuring that it has been serialized in by the
// time we get to class regeneration)
//
// NOTE: this is point where circular dependencies could reveal
// themselves, as the CDO could depend on a class not listed in
// the package's imports
//
// NOTE: how we don't guard against re-entrant behavior... if the CDO
// has already been "finalized", then its RF_NeedLoad flag would
// be cleared (and this will do nothing the 2nd time around)
Preload(BlueprintCDO);
// Ensure that all default subobject exports belonging to the CDO have been created. DSOs may no longer be
// referenced by a tagged property and thus may not get created and registered until after class regeneration.
// This can cause invalid subobjects to register themselves with a regenerated CDO if the native parent class
// has been changed to inherit from an entirely different type since the last time the class asset was saved.
// By constructing them here, we make sure that LoadAllObjects() won't construct them after class regeneration.
for (int32 ExportIndex = 0; ExportIndex < ExportMap.Num(); ++ExportIndex)
{
FObjectExport& Export = ExportMap[ExportIndex];
if ((Export.ObjectFlags & RF_DefaultSubObject) != 0 && Export.OuterIndex.IsExport() && Export.OuterIndex.ToExport() == DeferredCDOIndex)
{
if (Export.Object == nullptr && Export.OuterIndex.IsExport())
{
CreateExport(ExportIndex);
}
// In order to complete loading of the CDO we need to also preload its subobjects. Other CDOs
// will use these subobjects as archetypes for their own subobjects when they run InitSubobjectProperties
if (Export.Object)
{
Preload(Export.Object);
}
}
}
{
// Create any objects that (non CDO) objects that were deferred in this package:
TGuardValue<int32> ClearDeferredCDOToPreventDeferExportCreation(DeferredCDOIndex, INDEX_NONE);
for (int32 ExportIndex: DeferredTemplateObjects)
{
FObjectExport& Export = ExportMap[ExportIndex];
ULinkerPlaceholderExportObject* PlaceholderExport = Cast<ULinkerPlaceholderExportObject>(Export.Object);
if (ensure(PlaceholderExport))
{
// replace the placeholder with the proper object instance
PlaceholderExport->SetLinker(nullptr, INDEX_NONE);
Export.ResetObject();
UObject* ExportObj = CreateExport(ExportIndex);
PlaceholderExport->ResolveAllPlaceholderReferences(ExportObj);
ResolvedDeferredSubobjects(PlaceholderExport);
PlaceholderExport->MarkPendingKill();
if (ExportObj != nullptr)
{
Preload(ExportObj);
}
}
}
}
// sub-classes of this Blueprint could have had their CDO's
// initialization deferred (this occurs when the sub-class CDO is
// created before this super CDO has been fully serialized; we do this
// because the sub-class's CDO would not have been initialized with
// accurate values)
//
// in that case, the sub-class CDOs are waiting around until their
// super CDO is fully loaded (which is now)... we want to do this here,
// before this (super) Blueprint gets regenerated, because after it's
// regenerated the class layout (and property offsets) may no longer
// match the layout that sub-class CDOs were constructed with (making
// property copying dangerous)
FDeferredObjInitializationHelper::ResolveDeferredInitsFromArchetype(BlueprintCDO);
DEFERRED_DEPENDENCY_CHECK(BlueprintCDO->HasAnyFlags(RF_LoadCompleted));
}
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
void FLinkerLoad::ResolvePlaceholder(ULinkerPlaceholderExportObject* Placeholder)
{
int32 ExportIndex = Placeholder->PackageIndex.ToExport();
Placeholder->SetLinker(nullptr, INDEX_NONE);
FObjectExport& Export = ExportMap[ExportIndex];
Export.Object = nullptr;
UObject* ReplacementObject = CreateExport(ExportIndex);
Placeholder->ResolveAllPlaceholderReferences(ReplacementObject);
Placeholder->MarkPendingKill();
// recurse:
ResolvedDeferredSubobjects(Placeholder);
// attempt to preload, we don't really care if this doesn't complete but we don't want to fail
// to serialize an object:
if (ReplacementObject != nullptr)
{
Preload(ReplacementObject);
}
}
void FLinkerLoad::ResolvedDeferredSubobjects(ULinkerPlaceholderExportObject* OwningPlaceholder)
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
ensure(OwningPlaceholder->IsMarkedResolved());
for (ULinkerPlaceholderExportObject* PlaceholderSubobject: OwningPlaceholder->GetSubobjectPlaceholders())
{
int32 ExportIndex = PlaceholderSubobject->PackageIndex.ToExport();
PlaceholderSubobject->SetLinker(nullptr, INDEX_NONE);
FObjectExport& Export = ExportMap[ExportIndex];
Export.ResetObject();
UObject* ReplacementObject = CreateExport(ExportIndex);
PlaceholderSubobject->ResolveAllPlaceholderReferences(ReplacementObject);
PlaceholderSubobject->MarkPendingKill();
// recurse:
ResolvedDeferredSubobjects(PlaceholderSubobject);
// serialize:
if (ReplacementObject != nullptr)
{
Preload(ReplacementObject);
}
}
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
void FLinkerLoad::ForceBlueprintFinalization()
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
check(!bForceBlueprintFinalization);
bForceBlueprintFinalization = true;
#endif
}
bool FLinkerLoad::IsBlueprintFinalizationPending() const
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
return (DeferredCDOIndex != INDEX_NONE) || bForceBlueprintFinalization;
#else // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
return false;
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
bool FLinkerLoad::ForceRegenerateClass(UClass* ImportClass)
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
if (FLinkerLoad* ClassLinker = ImportClass->GetLinker())
{
//
// BE VERY CAREFUL with this! if these following statements are called
// in the wrong place, we could end up infinitely recursing
Preload(ImportClass);
DEFERRED_DEPENDENCY_CHECK(ImportClass->HasAnyFlags(RF_LoadCompleted));
if (ClassLinker->HasUnresolvedDependencies())
{
ClassLinker->ResolveDeferredDependencies(ImportClass);
}
if (ClassLinker->IsBlueprintFinalizationPending())
{
ClassLinker->FinalizeBlueprint(ImportClass);
}
return true;
}
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
return false;
}
bool FLinkerLoad::IsExportBeingResolved(int32 ExportIndex)
{
FObjectExport& Export = ExportMap[ExportIndex];
bool bIsExportClassBeingForceRegened = FResolvingExportTracker::Get().IsLinkerExportBeingResolved(this, ExportIndex);
FPackageIndex OuterIndex = Export.OuterIndex;
// since child exports require their outers be set upon creation, then those
// too count as being "resolved"... so here we check this export's outers too
while (!bIsExportClassBeingForceRegened && OuterIndex.IsExport())
{
int32 OuterExportIndex = OuterIndex.ToExport();
if (OuterExportIndex != INDEX_NONE)
{
FObjectExport& OuterExport = ExportMap[OuterExportIndex];
bIsExportClassBeingForceRegened |= FResolvingExportTracker::Get().IsLinkerExportBeingResolved(this, OuterExportIndex);
OuterIndex = OuterExport.OuterIndex;
}
else
{
break;
}
}
return bIsExportClassBeingForceRegened;
}
void FLinkerLoad::ResetDeferredLoadingState()
{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
DeferredCDOIndex = INDEX_NONE;
bForceBlueprintFinalization = false;
ResolvingPlaceholderStack.Empty();
ImportPlaceholders.Empty();
LoadFlags &= ~(LOAD_DeferDependencyLoads);
FResolvingExportTracker::Get().Reset(this);
FUnresolvedStructTracker::Reset(this);
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
bool FLinkerLoad::HasPerformedFullExportResolvePass()
{
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
return FResolvingExportTracker::Get().HasPerformedFullExportResolvePass(this);
#else
return false;
#endif
}
UObject* FLinkerLoad::RequestPlaceholderValue(UClass* ObjectType, const TCHAR* ObjectPath)
{
#if !USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
return nullptr;
#else
FLinkerPlaceholderBase* Placeholder = nullptr;
if (FBlueprintSupport::UseDeferredDependencyLoading() && (LoadFlags & LOAD_DeferDependencyLoads))
{
const FName ObjId(ObjectPath);
if (FLinkerPlaceholderBase** PlaceholderPtr = ImportPlaceholders.Find(ObjId))
{
Placeholder = *PlaceholderPtr;
}
// right now we only support external parties requesting CLASS placeholders;
// if there is a scenario where they're, through a different ObjectType,
// loading another Blueprint package when they shouldn't, then we need to
// handle that here as well
else if (ObjectType->IsChildOf<UClass>())
{
const FString ObjectPathStr(ObjectPath);
// we don't need placeholders for native object references (the
// calling code should properly handle null return values)
if (!FPackageName::IsScriptPackage(ObjectPathStr))
{
const FString ObjectName = FPackageName::ObjectPathToObjectName(ObjectPathStr);
Placeholder = MakeImportPlaceholder<ULinkerPlaceholderClass>(LinkerRoot, *ObjectName);
ImportPlaceholders.Add(ObjId, Placeholder);
}
}
}
return Placeholder ? Placeholder->GetPlaceholderAsUObject() : nullptr;
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
}
#if WITH_EDITORONLY_DATA
extern int32 GLinkerAllowDynamicClasses;
#endif
UObject* FLinkerLoad::FindImport(UClass* ImportClass, UObject* ImportOuter, const TCHAR* Name)
{
UObject* Result = StaticFindObject(ImportClass, ImportOuter, Name);
#if WITH_EDITORONLY_DATA
static FName NAME_BlueprintGeneratedClass(TEXT("BlueprintGeneratedClass"));
if (GLinkerAllowDynamicClasses && !Result && ImportClass->GetFName() == NAME_BlueprintGeneratedClass)
{
Result = StaticFindObject(UDynamicClass::StaticClass(), ImportOuter, Name);
}
#endif
return Result;
}
UObject* FLinkerLoad::FindImportFast(UClass* ImportClass, UObject* ImportOuter, FName Name, bool bAnyPackage)
{
UObject* Result = StaticFindObjectFast(ImportClass, ImportOuter, Name, false /*ExactClass*/, bAnyPackage);
#if WITH_EDITORONLY_DATA
static FName NAME_BlueprintGeneratedClass(TEXT("BlueprintGeneratedClass"));
if (GLinkerAllowDynamicClasses && !Result && ImportClass->GetFName() == NAME_BlueprintGeneratedClass)
{
Result = StaticFindObjectFast(UDynamicClass::StaticClass(), ImportOuter, Name, false /*ExactClass*/, bAnyPackage);
}
#endif
return Result;
}
void FLinkerLoad::CreateDynamicTypeLoader()
{
// In this case we can skip serializing PackageFileSummary and fill all the required info here
bHasSerializedPackageFileSummary = true;
// Try to get dependencies for dynamic classes
TArray<FBlueprintDependencyData> DependencyData;
FConvertedBlueprintsDependencies::Get().GetAssets(LinkerRoot->GetFName(), DependencyData);
if (!IsEventDrivenLoaderEnabled())
{
DependencyData.RemoveAll([=](const FBlueprintDependencyData& InData) -> bool
{
return InData.ObjectRef.PackageName == LinkerRoot->GetFName();
});
}
const FName DynamicClassName = UDynamicClass::StaticClass()->GetFName();
const FName DynamicClassPackageName = UDynamicClass::StaticClass()->GetOuterUPackage()->GetFName();
ensure(!ImportMap.Num());
// Create Imports
for (int32 DependencyIndex = 0; DependencyIndex < DependencyData.Num(); ++DependencyIndex)
{
FBlueprintDependencyData& Import = DependencyData[DependencyIndex];
FObjectImport* ObjectImport = new (ImportMap) FObjectImport(nullptr);
ObjectImport->ClassName = Import.ObjectRef.ClassName;
ObjectImport->ClassPackage = Import.ObjectRef.ClassPackageName;
ObjectImport->ObjectName = Import.ObjectRef.ObjectName;
if (Import.ObjectRef.OuterName == NAME_None)
{
ObjectImport->OuterIndex = FPackageIndex::FromImport(ImportMap.Num());
}
else
{
// A subobject - look for our outer in the previously setup imports. Iterate backwards here as it will usually be found in a few iterations
for (int32 OuterSearchIndex = ImportMap.Num() - 2; OuterSearchIndex >= 0; --OuterSearchIndex)
{
FObjectImport& SearchImport = ImportMap[OuterSearchIndex];
if (SearchImport.ObjectName == Import.ObjectRef.OuterName)
{
ObjectImport->OuterIndex = FPackageIndex::FromImport(OuterSearchIndex);
break;
}
}
// We must find out outer in the above search or the import table will be invalid
check(!ObjectImport->OuterIndex.IsNull());
}
FObjectImport* OuterImport = new (ImportMap) FObjectImport(nullptr);
OuterImport->ClassName = NAME_Package;
OuterImport->ClassPackage = GLongCoreUObjectPackageName;
OuterImport->ObjectName = Import.ObjectRef.PackageName;
if ((Import.ObjectRef.ClassName == DynamicClassName) && (!GEventDrivenLoaderEnabled || !EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME) && (Import.ObjectRef.ClassPackageName == DynamicClassPackageName))
{
const FString DynamicClassPath = Import.ObjectRef.PackageName.ToString() + TEXT(".") + Import.ObjectRef.ObjectName.ToString();
const FName DynamicClassPathName(*DynamicClassPath);
FDynamicClassStaticData* ClassConstructFn = GetDynamicClassMap().Find(DynamicClassPathName);
if (ensure(ClassConstructFn))
{
// The class object is created here. The class is not fully constructed yet (no CLASS_Constructed flag), ZConstructor will do that later.
// The class object is needed to resolve circular dependencies. Regular native classes use deferred initialization/registration to avoid them.
ClassConstructFn->StaticClassFn();
// We don't fill the ObjectImport->XObject and OuterImport->XObject, because the class still must be created as export.
}
}
}
// Create Export
const int32 DynamicTypeExportIndex = ExportMap.Num();
FObjectExport* const DynamicTypeExport = new (ExportMap) FObjectExport();
{
const FName* TypeNamePtr = GetConvertedDynamicPackageNameToTypeName().Find(LinkerRoot->GetFName());
DynamicTypeExport->ObjectName = TypeNamePtr ? *TypeNamePtr : NAME_None;
DynamicTypeExport->ThisIndex = FPackageIndex::FromExport(DynamicTypeExportIndex);
// This allows us to skip creating two additional imports for UDynamicClass and its package
DynamicTypeExport->DynamicType = FObjectExport::EDynamicType::DynamicType;
DynamicTypeExport->ObjectFlags |= RF_Public;
}
if (GEventDrivenLoaderEnabled)
{
const FString DynamicTypePath = GetExportPathName(DynamicTypeExportIndex);
const FName DynamicTypeClassName = GetDynamicTypeClassName(*DynamicTypePath);
if (DynamicTypeClassName == NAME_None)
{
UE_LOG(LogTemp, Error, TEXT("Exports %d, DynamicTypePath %s, Export Name %s, Package Root %s"), ExportMap.Num(), *DynamicTypePath, *DynamicTypeExport->ObjectName.ToString(), *LinkerRoot->GetPathName());
}
ensure(DynamicTypeClassName != NAME_None);
const bool bIsDynamicClass = DynamicTypeClassName == DynamicClassName;
const bool bIsDynamicStruct = DynamicTypeClassName == UScriptStruct::StaticClass()->GetFName();
if (bIsDynamicClass || bIsDynamicStruct)
{
FObjectExport* const CDOExport = bIsDynamicClass ? (new (ExportMap) FObjectExport()) : nullptr;
if (CDOExport)
{
const FString CDOName = FString(DEFAULT_OBJECT_PREFIX) + DynamicTypeExport->ObjectName.ToString();
CDOExport->ObjectName = *CDOName;
CDOExport->ThisIndex = FPackageIndex::FromExport(ExportMap.Num() - 1);
CDOExport->DynamicType = FObjectExport::EDynamicType::ClassDefaultObject;
CDOExport->ObjectFlags |= RF_Public | RF_ClassDefaultObject; //?
CDOExport->ClassIndex = DynamicTypeExport->ThisIndex;
}
// Note, the layout of the fake export table is assumed elsewhere
// check(ImportLinker->ExportMap.Num() == 2); // we assume there are two elements in the fake export table and the second one is the CDO
// LocalExportIndex = FPackageIndex::FromExport(1);
FObjectExport* const FakeExports[] = {DynamicTypeExport, CDOExport}; // must be sync'ed with FBlueprintDependencyData::DependencyTypes
int32 RunningIndex = 0;
for (int32 LocExportIndex = 0; LocExportIndex < (sizeof(FakeExports) / sizeof(FakeExports[0])); LocExportIndex++)
{
FObjectExport* const Export = FakeExports[LocExportIndex];
if (!Export)
{
continue;
}
Export->FirstExportDependency = RunningIndex;
enum class EDependencyType : uint8
{
SerializationBeforeSerialization,
CreateBeforeSerialization,
SerializationBeforeCreate,
CreateBeforeCreate,
};
auto HandleDependencyTypeForExport = [&](EDependencyType InDependencyType)
{
for (int32 DependencyDataIndex = 0; DependencyDataIndex < DependencyData.Num(); DependencyDataIndex++)
{
const FBlueprintDependencyData& Import = DependencyData[DependencyDataIndex];
const FBlueprintDependencyType DependencyType = Import.DependencyTypes[LocExportIndex];
auto IsMatchingDependencyType = [](FBlueprintDependencyType InDependencyTypeStruct, EDependencyType InDependencyTypeLoc) -> bool
{
switch (InDependencyTypeLoc)
{
case EDependencyType::SerializationBeforeSerialization:
return InDependencyTypeStruct.bSerializationBeforeSerializationDependency;
case EDependencyType::CreateBeforeSerialization:
return InDependencyTypeStruct.bCreateBeforeSerializationDependency;
case EDependencyType::SerializationBeforeCreate:
return InDependencyTypeStruct.bSerializationBeforeCreateDependency;
case EDependencyType::CreateBeforeCreate:
return InDependencyTypeStruct.bCreateBeforeCreateDependency;
}
check(false);
return false;
};
if (IsMatchingDependencyType(DependencyType, InDependencyType))
{
auto IncreaseDependencyTypeInExport = [](FObjectExport* InExport, EDependencyType InDependencyTypeLoc)
{
check(InExport);
switch (InDependencyTypeLoc)
{
case EDependencyType::SerializationBeforeSerialization:
InExport->SerializationBeforeSerializationDependencies++;
break;
case EDependencyType::CreateBeforeSerialization:
InExport->CreateBeforeSerializationDependencies++;
break;
case EDependencyType::SerializationBeforeCreate:
InExport->SerializationBeforeCreateDependencies++;
break;
case EDependencyType::CreateBeforeCreate:
InExport->CreateBeforeCreateDependencies++;
break;
}
};
IncreaseDependencyTypeInExport(Export, InDependencyType);
auto IndexInDependencyDataToImportIndex = [](int32 ArrayIndex) -> int32
{
return ArrayIndex * 2;
};
const int32 ImportIndex = IndexInDependencyDataToImportIndex(DependencyDataIndex);
PreloadDependencies.Add(FPackageIndex::FromImport(ImportIndex));
RunningIndex++;
}
}
};
// the order of Packages in PreloadDependencie must match FAsyncPackage::SetupExports_Event
HandleDependencyTypeForExport(EDependencyType::SerializationBeforeSerialization);
HandleDependencyTypeForExport(EDependencyType::CreateBeforeSerialization);
if (bIsDynamicClass && (Export == CDOExport))
{
// Add a serializebeforecreate arc from the class on the CDO. That will force us to finish the class before we create the CDO....
// and that will make sure that we load the class before we serialize things that reference the CDO.
Export->SerializationBeforeCreateDependencies++;
PreloadDependencies.Add(DynamicTypeExport->ThisIndex);
RunningIndex++;
}
HandleDependencyTypeForExport(EDependencyType::SerializationBeforeCreate);
HandleDependencyTypeForExport(EDependencyType::CreateBeforeCreate);
}
}
}
LinkerRoot->SetPackageFlags(LinkerRoot->GetPackageFlags() | PKG_CompiledIn);
}
/*******************************************************************************
* UObject
******************************************************************************/
/**
* Returns whether this object is contained in or part of a blueprint object
*/
bool UObject::IsInBlueprint() const
{
// Exclude blueprint classes as they may be regenerated at any time
// Need to exclude classes, CDOs, and their subobjects
const UObject* TestObject = this;
while (TestObject)
{
const UClass* ClassObject = dynamic_cast<const UClass*>(TestObject);
if (ClassObject && ClassObject->HasAnyClassFlags(CLASS_CompiledFromBlueprint) && ClassObject->ClassGeneratedBy)
{
return true;
}
else if (TestObject->HasAnyFlags(RF_ClassDefaultObject) && TestObject->GetClass() && TestObject->GetClass()->HasAnyClassFlags(CLASS_CompiledFromBlueprint) && TestObject->GetClass()->ClassGeneratedBy)
{
return true;
}
TestObject = TestObject->GetOuter();
}
return false;
}
/**
* Destroy properties that won't be destroyed by the native destructor
*/
void UObject::DestroyNonNativeProperties()
{
// Destroy properties that won't be destroyed by the native destructor
#if USE_UBER_GRAPH_PERSISTENT_FRAME
GetClass()->DestroyPersistentUberGraphFrame(this);
#endif
{
for (FProperty* P = GetClass()->DestructorLink; P; P = P->DestructorLinkNext)
{
P->DestroyValue_InContainer(this);
}
}
}
/*******************************************************************************
* FObjectInitializer
******************************************************************************/
/**
* Initializes a non-native property, according to the initialization rules. If the property is non-native
* and does not have a zero constructor, it is initialized with the default value.
* @param Property Property to be initialized
* @param Data Default data
* @return Returns true if that property was a non-native one, otherwise false
*/
bool FObjectInitializer::InitNonNativeProperty(FProperty* Property, UObject* Data)
{
if (!Property->GetOwnerClass()->HasAnyClassFlags(CLASS_Native | CLASS_Intrinsic)) // if this property belongs to a native class, it was already initialized by the class constructor
{
if (!Property->HasAnyPropertyFlags(CPF_ZeroConstructor)) // this stuff is already zero
{
Property->InitializeValue_InContainer(Data);
}
return true;
}
else
{
// we have reached a native base class, none of the rest of the properties will need initialization
return false;
}
}
/*******************************************************************************
* FDeferredInitializationTrackerBase
******************************************************************************/
FObjectInitializer* FDeferredInitializationTrackerBase::Add(const UObject* InitDependecy, const FObjectInitializer& DeferringInitializer)
{
FObjectInitializer* DeferredInitializerCopy = nullptr;
DEFERRED_DEPENDENCY_CHECK(InitDependecy);
if (InitDependecy)
{
UObject* InstanceObj = DeferringInitializer.GetObj();
ArchetypeInstanceMap.AddUnique(InitDependecy, InstanceObj);
DEFERRED_DEPENDENCY_CHECK(DeferredInitializers.Find(InstanceObj) == nullptr); // did we try to init the object twice?
// NOTE: we copy the FObjectInitializer, because it is most likely in the process of being destroyed
DeferredInitializerCopy = &DeferredInitializers.Add(InstanceObj, DeferringInitializer);
}
return DeferredInitializerCopy;
}
void FDeferredInitializationTrackerBase::ResolveArchetypeInstances(UObject* InitDependecy)
{
TArray<UObject*> ArchetypeInstances;
ArchetypeInstanceMap.MultiFind(InitDependecy, ArchetypeInstances);
for (UObject* Instance: ArchetypeInstances)
{
DEFERRED_DEPENDENCY_CHECK(ResolvingObjects.Contains(Instance) == false);
ResolvingObjects.Push(Instance);
if (ResolveDeferredInitialization(InitDependecy, Instance))
{
// For sub-objects, this has to come after ResolveDeferredInitialization(), since InitSubObjectProperties() is
// invoked there (which is where we fill this sub-object with values from the super)
PreloadDeferredDependents(Instance);
}
DEFERRED_DEPENDENCY_CHECK(ResolvingObjects.Top() == Instance);
ResolvingObjects.Pop();
}
ArchetypeInstanceMap.Remove(InitDependecy);
}
bool FDeferredInitializationTrackerBase::IsInitializationDeferred(const UObject* Object) const
{
return DeferredInitializers.Contains(Object);
}
bool FDeferredInitializationTrackerBase::DeferPreload(UObject* Object)
{
const bool bDeferPreload = IsInitializationDeferred(Object);
if (bDeferPreload && !IsResolving(Object))
{
DeferredPreloads.AddUnique(Object, Object);
}
return bDeferPreload;
}
bool FDeferredInitializationTrackerBase::IsResolving(UObject* ArchetypeInstance) const
{
return ResolvingObjects.Contains(ArchetypeInstance);
}
bool FDeferredInitializationTrackerBase::ResolveDeferredInitialization(UObject* /*ResolvingObject*/, UObject* ArchetypeInstance)
{
if (FObjectInitializer* DeferredInitializer = DeferredInitializers.Find(ArchetypeInstance))
{
// initializes and instances CDO properties (copies inherited values
// from the super's CDO)
FScriptIntegrationObjectHelper::PostConstructInitObject(*DeferredInitializer);
DeferredInitializers.Remove(ArchetypeInstance);
}
return true;
}
void FDeferredInitializationTrackerBase::PreloadDeferredDependents(UObject* ArchetypeInstance)
{
TArray<UObject*> ObjsToPreload;
DeferredPreloads.MultiFind(ArchetypeInstance, ObjsToPreload);
for (UObject* Object: ObjsToPreload)
{
FLinkerLoad* Linker = Object->GetLinker();
DEFERRED_DEPENDENCY_CHECK(Linker != nullptr);
if (Linker)
{
Linker->Preload(Object);
}
}
DeferredPreloads.Remove(ArchetypeInstance);
}
/*******************************************************************************
* FDeferredCdoInitializationTracker
******************************************************************************/
bool FDeferredCdoInitializationTracker::DeferPreload(UObject* Object)
{
bool bDeferPostload = false;
if (Object->HasAnyFlags(RF_ClassDefaultObject))
{
// When the initialization has been deferred we have to make sure to
// defer serialization as well - don't worry, for CDOs, Preload() will be invoked
// again from FinalizeBlueprint()->ResolveDeferredExports()
bDeferPostload = !IsResolving(Object) && IsInitializationDeferred(Object);
}
else
{
auto ShouldDeferSubObjectPreload = [this, Object](UObject* OwnerObject) -> bool
{
if (IsInitializationDeferred(OwnerObject))
{
const bool bDeferSubObjPostload = !IsResolving(OwnerObject);
if (bDeferSubObjPostload)
{
DeferredPreloads.AddUnique(OwnerObject, Object);
}
return bDeferSubObjPostload;
}
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
else if (OwnerObject)
{
UObject* OwnerClass = OwnerObject->GetClass();
for (auto& DeferredCdo: DeferredInitializers)
{
// we used to index these by class, so to ensure the same behavior validate
// our assumption that we can use the CDO object itself as the key (and that
// using the class wouldn't find a match instead)
DEFERRED_DEPENDENCY_CHECK(DeferredCdo.Key->GetClass() != OwnerClass);
}
}
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
return false;
};
if (Object->HasAnyFlags(RF_DefaultSubObject))
{
UObject* SubObjOuter = Object->GetOuter();
// NOTE: The outer of a DSO may not be a CDO like we want. It could
// be something like a component template. Right now we ignore
// those cases (IsDeferred() will reject this - only CDOs are
// deferred in this struct), but if this case proves to be a problem,
// then we may need to look up the outer chain, or see if the outer
// sub-obj is deferred itself.
bDeferPostload = ShouldDeferSubObjectPreload(SubObjOuter);
}
else if (Object->HasAnyFlags(RF_InheritableComponentTemplate))
{
UClass* OwningClass = Cast<UClass>(Object->GetOuter());
DEFERRED_DEPENDENCY_CHECK(OwningClass && OwningClass->ClassDefaultObject);
if (OwningClass)
{
bDeferPostload = ShouldDeferSubObjectPreload(OwningClass->ClassDefaultObject);
}
}
}
return bDeferPostload;
}
/*******************************************************************************
* FDeferredSubObjInitializationTracker
******************************************************************************/
bool FDeferredSubObjInitializationTracker::ResolveDeferredInitialization(UObject* ResolvingObject, UObject* ArchetypeInstance)
{
bool bInitializerRan = false;
// If we deferred the sub-object because the super CDO wasn't ready, we still
// need to check that its archetype is in a ready state (ready to be copied from)
if (ResolvingObject->HasAnyFlags(RF_ClassDefaultObject))
{
if (FObjectInitializer* DeferredInitializer = DeferredInitializers.Find(ArchetypeInstance))
{
UObject* Archetype = DeferredInitializer->GetArchetype();
// When this sub-object was created it's archetype object (the
// super's sub-obj) may not have been created yet. In that scenario, the
// component class's CDO would have been used in its place; now that
// the super is good, we should update the archetype
if (ArchetypeInstance->HasAnyFlags(RF_ClassDefaultObject))
{
Archetype = UObject::GetArchetypeFromRequiredInfo(ArchetypeInstance->GetClass(), ArchetypeInstance->GetOuter(), ArchetypeInstance->GetFName(), ArchetypeInstance->GetFlags());
}
const bool bArchetypeLoadPending = Archetype &&
(Archetype->HasAnyFlags(RF_NeedLoad) || (Archetype->HasAnyFlags(RF_WasLoaded) && !Archetype->HasAnyFlags(RF_LoadCompleted)));
if (bArchetypeLoadPending)
{
// Archetype isn't ready, move the deferred initializer to wait for its archetype
ArchetypeInstanceMap.Add(Archetype, ArchetypeInstance);
// don't need to add this to DeferredInitializers, as it is already there
}
else
{
bInitializerRan = FDeferredInitializationTrackerBase::ResolveDeferredInitialization(ResolvingObject, ArchetypeInstance);
}
}
}
else
{
bInitializerRan = FDeferredInitializationTrackerBase::ResolveDeferredInitialization(ResolvingObject, ArchetypeInstance);
}
return bInitializerRan;
}
/*******************************************************************************
* FDeferredObjInitializationHelper
******************************************************************************/
FObjectInitializer* FDeferredObjInitializationHelper::DeferObjectInitializerIfNeeded(const FObjectInitializer& DeferringInitializer)
{
FObjectInitializer* DeferredInitializerCopy = nullptr;
UObject* TargetObj = DeferringInitializer.GetObj();
if (TargetObj)
{
auto IsSuperCdoReadyToBeCopied = [](FDeferredCdoInitializationTracker& InCdoInitDeferalSys, const UClass* LoadClass, const UObject* SuperCDO) -> bool
{
// RF_WasLoaded indicates that this Super was loaded from disk (and hasn't been regenerated on load)
// regenerated CDOs will not have the RF_LoadCompleted
const bool bSuperCdoLoadPending = InCdoInitDeferalSys.IsInitializationDeferred(SuperCDO) ||
SuperCDO->HasAnyFlags(RF_NeedLoad) || (SuperCDO->HasAnyFlags(RF_WasLoaded) && !SuperCDO->HasAnyFlags(RF_LoadCompleted));
if (bSuperCdoLoadPending)
{
const FLinkerLoad* ObjLinker = LoadClass->GetLinker();
const bool bIsBpClassSerializing = ObjLinker && (ObjLinker->LoadFlags & LOAD_DeferDependencyLoads);
const bool bIsResolvingDeferredObjs = LoadClass->HasAnyFlags(RF_LoadCompleted) &&
ObjLinker && ObjLinker->IsBlueprintFinalizationPending();
DEFERRED_DEPENDENCY_CHECK(bIsBpClassSerializing || bIsResolvingDeferredObjs);
return !bIsBpClassSerializing && !bIsResolvingDeferredObjs;
}
return true;
};
const bool bIsCDO = TargetObj->HasAnyFlags(RF_ClassDefaultObject);
if (bIsCDO)
{
const UClass* CdoClass = DeferringInitializer.GetClass();
UClass* SuperClass = CdoClass->GetSuperClass();
if (!CdoClass->IsNative() && !SuperClass->IsNative())
{
DEFERRED_DEPENDENCY_CHECK(CdoClass->HasAnyClassFlags(CLASS_CompiledFromBlueprint));
DEFERRED_DEPENDENCY_CHECK(SuperClass->HasAnyClassFlags(CLASS_CompiledFromBlueprint));
const UObject* SuperCDO = DeferringInitializer.GetArchetype();
DEFERRED_DEPENDENCY_CHECK(SuperCDO && SuperCDO->HasAnyFlags(RF_ClassDefaultObject));
// use the ObjectArchetype for the super CDO because the SuperClass may have a REINST CDO cached currently
SuperClass = SuperCDO->GetClass();
FDeferredCdoInitializationTracker& CdoInitDeferalSys = FDeferredCdoInitializationTracker::Get();
if (!IsSuperCdoReadyToBeCopied(CdoInitDeferalSys, CdoClass, SuperCDO))
{
DeferredInitializerCopy = CdoInitDeferalSys.Add(SuperCDO, DeferringInitializer);
}
}
}
// since "InheritableComponentTemplate"s are not default sub-objects,
// they won't be fixed up by the owner's FObjectInitializer (CDO
// FObjectInitializers will init default sub-object properties, copying
// from the super's DSOs) - this means that we need to separately defer
// init'ing these sub-objects when their archetype hasn't been loaded yet
else if (TargetObj->HasAnyFlags(RF_InheritableComponentTemplate))
{
const UClass* OwnerClass = Cast<UClass>(TargetObj->GetOuter());
DEFERRED_DEPENDENCY_CHECK(OwnerClass && OwnerClass->HasAnyClassFlags(CLASS_CompiledFromBlueprint));
const UClass* SuperClass = OwnerClass->GetSuperClass();
if (SuperClass && !SuperClass->IsNative())
{
// It is possible that the archetype isn't even correct, if the
// super's sub-object hasn't even been created yet (in this case the
// component's CDO is used, which is probably wrong)
//
// So if the super CDO isn't ready, we need to defer this sub-object
const UObject* SuperCDO = SuperClass->ClassDefaultObject;
FDeferredCdoInitializationTracker& CdoInitDeferalSys = FDeferredCdoInitializationTracker::Get();
if (!IsSuperCdoReadyToBeCopied(CdoInitDeferalSys, OwnerClass, SuperCDO))
{
FDeferredSubObjInitializationTracker& SubObjInitDeferalSys = FDeferredSubObjInitializationTracker::Get();
DeferredInitializerCopy = SubObjInitDeferalSys.Add(SuperCDO, DeferringInitializer);
}
}
// if it passed the super CDO check above, assume the archetype is kosher
if (!DeferredInitializerCopy)
{
UObject* Archetype = DeferringInitializer.GetArchetype();
const bool bArchetypeLoadPending = Archetype &&
(Archetype->HasAnyFlags(RF_NeedLoad) || (Archetype->HasAnyFlags(RF_WasLoaded) && !Archetype->HasAnyFlags(RF_LoadCompleted)));
if (bArchetypeLoadPending)
{
FDeferredSubObjInitializationTracker& SubObjInitDeferalSys = FDeferredSubObjInitializationTracker::Get();
DeferredInitializerCopy = SubObjInitDeferalSys.Add(Archetype, DeferringInitializer);
}
}
}
}
return DeferredInitializerCopy;
}
bool FDeferredObjInitializationHelper::DeferObjectPreload(UObject* Object)
{
return FDeferredCdoInitializationTracker::Get().DeferPreload(Object) || FDeferredSubObjInitializationTracker::Get().DeferPreload(Object);
}
void FDeferredObjInitializationHelper::ResolveDeferredInitsFromArchetype(UObject* Archetype)
{
FDeferredCdoInitializationTracker& DeferredCdoTracker = FDeferredCdoInitializationTracker::Get();
FDeferredSubObjInitializationTracker& DeferredSubObjTracker = FDeferredSubObjInitializationTracker::Get();
#if USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
if (Archetype->HasAnyFlags(RF_ClassDefaultObject))
{
// we used to index the deferred initialization by class, so to ensure the same behavior validate
// our assumption that we can use the CDO object itself as the key (and that using the class wouldn't find a match instead)
auto IsDeferredByClass = [Archetype](const TMultiMap<const UObject*, UObject*>& ArchetypeMap) -> bool
{
for (auto& DeferredObj: ArchetypeMap)
{
if (DeferredObj.Key->GetClass() == Archetype->GetClass())
{
return true;
}
}
return false;
};
if (!DeferredCdoTracker.ArchetypeInstanceMap.Contains(Archetype))
{
DEFERRED_DEPENDENCY_CHECK(IsDeferredByClass(DeferredCdoTracker.ArchetypeInstanceMap) == false);
}
if (!DeferredSubObjTracker.ArchetypeInstanceMap.Contains(Archetype))
{
DEFERRED_DEPENDENCY_CHECK(IsDeferredByClass(DeferredSubObjTracker.ArchetypeInstanceMap) == false);
}
}
#endif // USE_DEFERRED_DEPENDENCY_CHECK_VERIFICATION_TESTS
DeferredCdoTracker.ResolveArchetypeInstances(Archetype);
DeferredSubObjTracker.ResolveArchetypeInstances(Archetype);
}
// don't want other files ending up with this internal define
#undef DEFERRED_DEPENDENCY_CHECK
FBlueprintDependencyObjectRef::FBlueprintDependencyObjectRef(const TCHAR* InPackageFolder, const TCHAR* InShortPackageName, const TCHAR* InObjectName, const TCHAR* InClassPackageName, const TCHAR* InClassName, const TCHAR* InOuterName)
: PackageName(*(FString(InPackageFolder) + TEXT("/") + InShortPackageName)), ObjectName(InObjectName), ClassPackageName(InClassPackageName), ClassName(InClassName), OuterName(InOuterName)
{}
FConvertedBlueprintsDependencies& FConvertedBlueprintsDependencies::Get()
{
static FConvertedBlueprintsDependencies ConvertedBlueprintsDependencies;
return ConvertedBlueprintsDependencies;
}
void FConvertedBlueprintsDependencies::RegisterConvertedClass(FName PackageName, GetDependenciesNamesFunc GetAssets)
{
check(!PackageNameToGetter.Contains(PackageName));
ensure(GetAssets);
PackageNameToGetter.Add(PackageName, GetAssets);
}
static bool IsBlueprintDependencyDataNull(const FBlueprintDependencyData& Dependency)
{
return Dependency.ObjectRef.ObjectName == NAME_None;
}
void FConvertedBlueprintsDependencies::GetAssets(FName PackageName, TArray<FBlueprintDependencyData>& OutDependencies) const
{
auto FuncPtr = PackageNameToGetter.Find(PackageName);
auto Func = (FuncPtr) ? (*FuncPtr) : nullptr;
ensure(Func || !FuncPtr);
if (Func)
{
Func(OutDependencies);
OutDependencies.RemoveAll(IsBlueprintDependencyDataNull);
}
}
void FConvertedBlueprintsDependencies::FillUsedAssetsInDynamicClass(UDynamicClass* DynamicClass, GetDependenciesNamesFunc GetUsedAssets)
{
check(DynamicClass && GetUsedAssets);
ensure(DynamicClass->UsedAssets.Num() == 0);
TArray<FBlueprintDependencyData> UsedAssetdData;
GetUsedAssets(UsedAssetdData);
if (GEventDrivenLoaderEnabled && EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME)
{
FLinkerLoad* Linker = DynamicClass->GetOutermost()->LinkerLoad;
if (Linker)
{
int32 ImportIndex = 0;
for (FBlueprintDependencyData& ItData: UsedAssetdData)
{
if (!IsBlueprintDependencyDataNull(ItData))
{
FObjectImport& Import = Linker->Imp(FPackageIndex::FromImport(ImportIndex));
check(Import.ObjectName == ItData.ObjectRef.ObjectName);
UObject* TheAsset = Import.XObject;
UE_CLOG(!TheAsset, LogBlueprintSupport, Error, TEXT("Could not find UDynamicClass dependent asset (EDL) %s in %s"), *ItData.ObjectRef.ObjectName.ToString(), *ItData.ObjectRef.PackageName.ToString());
DynamicClass->UsedAssets.Add(TheAsset);
ImportIndex += 2;
}
else
{
DynamicClass->UsedAssets.Add(nullptr);
}
}
return;
}
check(0);
}
for (FBlueprintDependencyData& ItData: UsedAssetdData)
{
if (ItData.ObjectRef.ObjectName != NAME_None)
{
const FString PathToObj = FString::Printf(TEXT("%s.%s"), *ItData.ObjectRef.PackageName.ToString(), *ItData.ObjectRef.ObjectName.ToString());
UObject* TheAsset = LoadObject<UObject>(nullptr, *PathToObj);
UE_CLOG(!TheAsset, LogBlueprintSupport, Error, TEXT("Could not find UDynamicClass dependent asset (non-EDL) %s in %s"), *ItData.ObjectRef.ObjectName.ToString(), *ItData.ObjectRef.PackageName.ToString());
DynamicClass->UsedAssets.Add(TheAsset);
}
else
{
DynamicClass->UsedAssets.Add(nullptr);
}
}
}
UObject* FConvertedBlueprintsDependencies::LoadObjectForStructConstructor(UScriptStruct* ScriptStruct, const TCHAR* ObjectPath)
{
if (GEventDrivenLoaderEnabled && EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME)
{
// Find Object should work here as the blueprints have scheduled it for load
return FindObject<UObject>(nullptr, ObjectPath);
}
return LoadObject<UObject>(nullptr, ObjectPath);
}
bool FBlueprintDependencyData::ContainsDependencyData(TArray<FBlueprintDependencyData>& Assets, int16 ObjectRefIndex)
{
return nullptr != Assets.FindByPredicate([=](const FBlueprintDependencyData& Data) -> bool
{
return Data.ObjectRefIndex == ObjectRefIndex;
});
};
void FBlueprintDependencyData::AppendUniquely(TArray<FBlueprintDependencyData>& Destination, const TArray<FBlueprintDependencyData>& AdditionalData)
{
for (const FBlueprintDependencyData& Data: AdditionalData)
{
Destination.AddUnique(Data);
}
}
#if WITH_EDITOR
/*******************************************************************************
* IBlueprintNativeCodeGenCore
******************************************************************************/
static const IBlueprintNativeCodeGenCore* CoordinatorInstance = nullptr;
const IBlueprintNativeCodeGenCore* IBlueprintNativeCodeGenCore::Get()
{
return CoordinatorInstance;
}
void IBlueprintNativeCodeGenCore::Register(const IBlueprintNativeCodeGenCore* Coordinator)
{
CoordinatorInstance = Coordinator;
}
#endif // WITH_EDITOR