Files
UnrealEngine/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp
2025-05-18 13:04:45 +08:00

4316 lines
156 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BlueprintCompilationManager.h"
#include "Async/Async.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintClassFlagUtils.h"
#include "BlueprintCompilerExtension.h"
#include "BlueprintEditorSettings.h"
#include "Blueprint/BlueprintSupport.h"
#include "Kismet2/CompilerResultsLog.h"
#include "Components/TimelineComponent.h"
#include "Editor.h"
#include "Editor/UnrealEdEngine.h"
#include "Engine/Engine.h"
#include "Editor/Transactor.h"
#include "Engine/LevelScriptBlueprint.h"
#include "Engine/SCS_Node.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/TimelineTemplate.h"
#include "FileHelpers.h"
#include "FindInBlueprintManager.h"
#include "HAL/LowLevelMemTracker.h"
#include "IMessageLogListing.h"
#include "INotifyFieldValueChanged.h"
#include "K2Node_CreateDelegate.h"
#include "K2Node_CustomEvent.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_FunctionResult.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Kismet2/KismetReinstanceUtilities.h"
#include "KismetCompiler.h"
#include "Logging/StructuredLog.h"
#include "Misc/ScopedSlowTask.h"
#include "Misc/DataValidation.h"
#include "Misc/PackageAccessTrackingOps.h"
#include "Misc/PlayInEditorLoadingScope.h"
#include "ProfilingDebugging/ScopedTimers.h"
#include "Serialization/ArchiveHasReferences.h"
#include "Serialization/ArchiveReplaceObjectRef.h"
#include "ProfilingDebugging/LoadTimeTracker.h"
#include "TickableEditorObject.h"
#include "Trace/Trace.h"
#include "Trace/Trace.inl"
#include "UObject/FortniteMainBranchObjectVersion.h"
#include "UObject/MetaData.h"
#include "UObject/ReferenceChainSearch.h"
#include "UObject/UObjectHash.h"
#include "Kismet2/KismetDebugUtilities.h"
#include "BlueprintEditorModule.h"
#include "Algo/TopologicalSort.h"
#include "Animation/AnimBlueprint.h"
#include "Stats/StatsHierarchical.h"
#include "UObject/PropertyBagRepository.h"
#include "UObject/OverridableManager.h"
#include "UObject/UObjectArchetypeHelper.h"
extern UNREALED_API UUnrealEdEngine* GUnrealEd;
#define LOCTEXT_NAMESPACE "BlueprintCompilationManager"
/*
BLUEPRINT COMPILATION MANAGER IMPLEMENTATION NOTES
INPUTS: UBlueprint, UEdGraph, UEdGraphNode, UEdGraphPin, references to UClass, UProperties
INTERMEDIATES: Cloned Graph, Nodes, Pins
OUPUTS: UClass, UProperties
The blueprint compilation manager addresses shortcomings of compilation
behavior (performance, correctness) that occur when compiling blueprints
that are inter-dependent. If you are using blueprints and there are no dependencies
between blueprint compilation outputs and inputs, then this code is completely
unnecessary and you can directly interface with FKismetCompilerContext and its
derivatives.
In order to handle compilation correctly the manager splits compilation into
the following stages (implemented below in FlushCompilationQueueImpl):
STAGE I: GATHER
STAGE II: FILTER
STAGE III: SORT
STAGE IV: SET TEMPORARY BLUEPRINT FLAGS
STAGE V: VALIDATE
STAGE VI: PURGE (LOAD ONLY)
STAGE VII: DISCARD SKELETON CDO
STAGE VIII: RECOMPILE SKELETON
STAGE IX: RECONSTRUCT NODES, REPLACE DEPRECATED NODES (LOAD ONLY)
STAGE X: CREATE REINSTANCER (DISCARD 'OLD' CLASS)
STAGE XI: CREATE UPDATED CLASS HIERARCHY
STAGE XII: COMPILE CLASS LAYOUT
STAGE XIII: COMPILE CLASS FUNCTIONS
STAGE XIV: REINSTANCE
STAGE XV: POST CDO COMPILED
STAGE XVI: CLEAR TEMPORARY FLAGS
The code that implements these stages are labeled below. At some later point a final
reinstancing operation will occur, unless the client is using CompileSynchronously,
in which case the expensive object graph find and replace will occur immediately
*/
// Debugging switches:
#define VERIFY_NO_STALE_CLASS_REFERENCES 0
#define VERIFY_NO_BAD_SKELETON_REFERENCES 0
namespace UE::Kismet::BlueprintCompilationManager::Private
{
namespace ConsoleVariables
{
/** Flag to use the new FBlueprintCompileReinstancer::MoveDependentSkelToReinst and avoid problematic recursion during reinstancing */
static bool bEnableSkelReinstUpdate = true;
static FAutoConsoleVariableRef CVarEnableSkelReinstUpdate(
TEXT("BP.bEnableSkelReinstUpdate"), bEnableSkelReinstUpdate,
TEXT("If true the Reinstancing of SKEL classes will use the new FBlueprintCompileReinstancer::MoveDependentSkelToReinst(o(n)) instead of the old MoveSkelCDOAside (o(n^2))"),
ECVF_Default);
/** Flag to disable faster compiles for individual blueprints if they have no function signature changes */
static bool bForceAllDependenciesToRecompile = false;
static FAutoConsoleVariableRef CVarForceAllDependenciesToRecompile(
TEXT("BP.bForceAllDependenciesToRecompile"), bForceAllDependenciesToRecompile,
TEXT("If true all dependencies will be bytecode-compiled even when all referenced functions have no signature changes. Intended for compiler development/debugging purposes."),
ECVF_Default);
/**
* Flag to disable skipping of reinstancing logic when a class is not observably changed
* by compilation. This can only work when compiling in place (e.g. reusing UClasses) - so
* can't be done for asset reload, live coding, or verse
*/
static bool bAllowFastReinstancing = false;
static FAutoConsoleVariableRef CVarAllowFastReinstancing(
TEXT("BP.bAllowFastReinstancing"), bAllowFastReinstancing,
TEXT("True by default, this flag enables a fast path in the logic that updates class \
objects at runtime (Reinstancing). When this flag is set we check class layout and \
construction data for a match, if the layout and construction data match then we \
don't replace instances of the class."),
ECVF_Default);
/** Flag to choose between full Blueprint data validation at compile time, or just the CDO. */
static bool bDoFullDataValidationDuringCompilation = true;
static FAutoConsoleVariableRef CVarDoFullDataValidationDuringCompilation(
TEXT("BP.bDoFullDataValidationDuringCompilation"), bDoFullDataValidationDuringCompilation,
TEXT("If true, data validation will be performed on the entire BP when compiled. Otherwise, only the CDO will be validated. Consider disabling if errors or performance are a concern."),
ECVF_Default);
}
}
namespace UE::Private
{
/** Flag to run user PostLoad functions on archetypes before compiling the blueprint */
KISMET_API bool bRunArchetypePostLoadEarly = false;
static FAutoConsoleVariableRef CVarRunArchetypePostLoadEarly(
TEXT("BP.bRunArchetypePostLoadEarly"), bRunArchetypePostLoadEarly,
TEXT("If true, postload will be run on archetype objects before they are compiled, this reduces the possibility that instances are created before postload runs"),
ECVF_Default);
}
struct FReinstancingJob;
struct FSkeletonFixupData;
struct FCompilerData;
enum class EReparentClassOptions
{
None = 0x0,
ReplaceReferencesToOldClasses = 0x1,
};
ENUM_CLASS_FLAGS(EReparentClassOptions)
struct FBlueprintCompilationManagerImpl : public FGCObject
{
FBlueprintCompilationManagerImpl();
virtual ~FBlueprintCompilationManagerImpl();
// FGCObject:
virtual void AddReferencedObjects(FReferenceCollector& Collector);
virtual FString GetReferencerName() const override;
void RegisterCompilerExtension(TSubclassOf<UBlueprint> BlueprintType, UBlueprintCompilerExtension* Extension);
void QueueForCompilation(const FBPCompileRequest& CompileJob);
void CompileSynchronouslyImpl(const FBPCompileRequest& Request);
void FlushCompilationQueueImpl(bool bSuppressBroadcastCompiled, TArray<UBlueprint*>* BlueprintsCompiled, TArray<UBlueprint*>* BlueprintsCompiledOrSkeletonCompiled, FUObjectSerializeContext* InLoadContext, TMap<UClass*, TMap<UObject*, UObject*>>* OldToNewTemplates = nullptr);
void FixupDelegateProperties(const TArray<FCompilerData>& CurrentlyCompilingBPs);
void ProcessExtensions(const TArray<FCompilerData>& InCurrentlyCompilingBPs);
void FlushReinstancingQueueImpl(bool bFindAndReplaceCDOReferences = false, TMap<UClass*, TMap<UObject*, UObject*>>* OldToNewTemplates = nullptr);
bool HasBlueprintsToCompile() const;
bool IsGeneratedClassLayoutReady() const;
void GetDefaultValue(const UClass* ForClass, const FProperty* Property, FString& OutDefaultValueAsString) const;
void VerifyNoQueuedRequests(const TArray<FCompilerData>& CurrentlyCompilingBPs);
void SaveConstructionHashes(const TArray<FCompilerData>& CurrentlyCompilingBPs);
void EnsureArchetypesArePostLoaded();
static void GatherArchetypesRequiringPostload(TConstArrayView<FBPCompileRequest> FromRequests, TArray<UObject*>& OutArchetypesRequiringPostload);
static void ReparentHierarchies(const TMap<UClass*, UClass*>& OldClassToNewClass, EReparentClassOptions Options);
static void BuildDSOMap(UObject* OldObject, UObject* NewObject, const TMap<UClass*, UClass*>& InOldToNewClassMap, TMap<UObject*, UObject*>& OutOldToNewDSO);
static void ReinstanceBatch(TArray<FReinstancingJob>& Reinstancers, TMap<UClass*, UClass*>& InOutOldToNewClassMap, FUObjectSerializeContext* InLoadContext, TMap<UClass*, TMap<UObject*, UObject*>>* OldToNewTemplates = nullptr);
static UClass* FastGenerateSkeletonClass(UBlueprint* BP, FKismetCompilerContext& CompilerContext, bool bIsSkeletonOnly, TArray<FSkeletonFixupData>& OutSkeletonFixupData);
static bool IsQueuedForCompilation(UBlueprint* BP);
static void ConformToParentAndInterfaces(UBlueprint* BP);
static void RelinkSkeleton(UClass* SkeletonToRelink);
static void GatherOutOfDateDependenciesRecursive(TObjectPtr<UBlueprint> Gather, TSet<TObjectPtr<UBlueprint>>& OutOfDateDeps);
static void QueueOutOfDateDependencies( const TArray<FBPCompileRequest>& QueuedRequests, TArray<UBlueprint*>& OutBlueprintsToRecompile, TArray<FCompilerData>& CurrentlyCompilingBPs);
// Declaration of archive to fix up bytecode references of blueprints that are actively compiled:
class FFixupBytecodeReferences : public FArchiveUObject
{
public:
FFixupBytecodeReferences(UObject* InObject);
private:
virtual FArchive& operator<<(UObject*& Obj) override;
virtual FArchive& operator<<(FField*& Field) override;
};
// Extension data, could be organized in many ways, but this provides an easy way
// to extend blueprint compilation after the graph has been pruned and functions
// have been generated (but before code is generated):
TMap<TObjectPtr<UClass>, TArray<TObjectPtr<UBlueprintCompilerExtension>>> CompilerExtensions;
// Queued requests to be processed in the next FlushCompilationQueueImpl call:
TArray<FBPCompileRequest> QueuedRequests;
// Data stored for reinstancing, which finishes much later than compilation,
// populated by FlushCompilationQueueImpl, cleared by FlushReinstancingQueueImpl:
TMap<TObjectPtr<UClass>, TObjectPtr<UClass>> ClassesToReinstance;
TMap<TObjectPtr<const UClass>, FBlake3Hash> ClassHashes;
TSet<TObjectPtr<UObject>> ObjectsWithCachedArchetypesForReinstancing;
// Map to old default values, useful for providing access to this data throughout
// the compilation process:
TMap<TObjectPtr<UBlueprint>, TObjectPtr<UObject>> OldCDOs;
// Blueprints that should be saved after the compilation pass is complete:
TArray<UBlueprint*> CompiledBlueprintsToSave;
// State stored so that we can check what stage of compilation we're in:
bool bGeneratedClassLayoutReady;
#if WITH_EDITOR
// Used to avoid reinstantiation on the GT while compiling on the loading thread
FCriticalSection Lock;
#endif
};
// free function that we use to cross a module boundary (from CoreUObject to here)
void FlushReinstancingQueueImplWrapper();
void FlushCompilationQueueImplWrapper(FUObjectSerializeContext* InLoadContext);
void MoveSkelCDOAside(UClass* Class, TMap<UClass*, UClass*>& OldToNewMap);
void ReparentHierarchiesWrapper(const TMap<UClass*, UClass*>& OldToNewMap)
{
FBlueprintCompilationManagerImpl::ReparentHierarchies(OldToNewMap, EReparentClassOptions::ReplaceReferencesToOldClasses);
}
FBlueprintCompilationManagerImpl::FBlueprintCompilationManagerImpl()
{
FBlueprintSupport::SetFlushReinstancingQueueFPtr(&FlushReinstancingQueueImplWrapper);
FBlueprintSupport::SetFlushCompilationQueueFPtr(&FlushCompilationQueueImplWrapper);
FBlueprintSupport::SetClassReparentingFPtr(&ReparentHierarchiesWrapper);
bGeneratedClassLayoutReady = true;
}
FBlueprintCompilationManagerImpl::~FBlueprintCompilationManagerImpl()
{
FBlueprintSupport::SetFlushReinstancingQueueFPtr(nullptr);
FBlueprintSupport::SetFlushCompilationQueueFPtr(nullptr);
FBlueprintSupport::SetClassReparentingFPtr(nullptr);
}
void FBlueprintCompilationManagerImpl::AddReferencedObjects(FReferenceCollector& Collector)
{
for (auto& Extensions : CompilerExtensions)
{
Collector.AddReferencedObject(Extensions.Key);
Collector.AddReferencedObjects(Extensions.Value);
}
for (FBPCompileRequest& Job : QueuedRequests)
{
Collector.AddReferencedObject(Job.BPToCompile);
}
Collector.AddReferencedObjects(ClassesToReinstance);
Collector.AddReferencedObjects(ClassHashes);
Collector.AddReferencedObjects(ObjectsWithCachedArchetypesForReinstancing);
Collector.AddReferencedObjects(OldCDOs);
}
FString FBlueprintCompilationManagerImpl::GetReferencerName() const
{
return TEXT("FBlueprintCompilationManagerImpl");
}
void FBlueprintCompilationManagerImpl::RegisterCompilerExtension(TSubclassOf<UBlueprint> BlueprintType, UBlueprintCompilerExtension* Extension)
{
CompilerExtensions.FindOrAdd(BlueprintType).Emplace(Extension);
}
void FBlueprintCompilationManagerImpl::QueueForCompilation(const FBPCompileRequest& CompileJob)
{
#if WITH_EDITOR
FScopeLock ScopeLock(&Lock);
#endif
if (!CompileJob.BPToCompile->bQueuedForCompilation)
{
if (GCompilingBlueprint)
{
FString CurrentlyCompiling;
for (const TPair<TObjectPtr<UClass>, TObjectPtr<UClass>>& CompilerData : ClassesToReinstance)
{
if (!CompilerData.Value)
{
continue;
}
CurrentlyCompiling += CompilerData.Value->GetName() + TEXT(" ");
}
ensureMsgf(false,
TEXT("Attempting to enqueue %s for compile while compiling: %s"),
*CompileJob.BPToCompile->GetName(),
*CurrentlyCompiling);
}
CompileJob.BPToCompile->bQueuedForCompilation = true;
QueuedRequests.Add(CompileJob);
}
}
void FBlueprintCompilationManagerImpl::CompileSynchronouslyImpl(const FBPCompileRequest& Request)
{
TRACE_CPUPROFILER_EVENT_SCOPE(CompileSynchronouslyImpl);
#if WITH_EDITOR
FScopeLock ScopeLock(&Lock);
#endif
Request.BPToCompile->bQueuedForCompilation = true;
const bool bIsRegeneratingOnLoad = (Request.CompileOptions & EBlueprintCompileOptions::IsRegeneratingOnLoad ) != EBlueprintCompileOptions::None;
const bool bRegenerateSkeletonOnly = (Request.CompileOptions & EBlueprintCompileOptions::RegenerateSkeletonOnly ) != EBlueprintCompileOptions::None;
const bool bSkipGarbageCollection = (Request.CompileOptions & EBlueprintCompileOptions::SkipGarbageCollection ) != EBlueprintCompileOptions::None
|| bRegenerateSkeletonOnly;
const bool bBatchCompile = (Request.CompileOptions & EBlueprintCompileOptions::BatchCompile ) != EBlueprintCompileOptions::None;
const bool bSkipReinstancing = (Request.CompileOptions & EBlueprintCompileOptions::SkipReinstancing ) != EBlueprintCompileOptions::None;
const bool bSkipSaving = (Request.CompileOptions & EBlueprintCompileOptions::SkipSave ) != EBlueprintCompileOptions::None;
const bool bFindAndReplaceCDOReferences = (Request.CompileOptions & EBlueprintCompileOptions::IncludeCDOInReferenceReplacement ) != EBlueprintCompileOptions::None ||
// when fast reinstancing we want to always include CDO References as instances have a habit of caching object
// references from the CDO. This works fine when they are reconstructed any time the CDO is regenerated
// but fast reinstancing attempts to avoid expensive object recreation/reference replacment:
(UE::Kismet::BlueprintCompilationManager::Private::ConsoleVariables::bAllowFastReinstancing);
ensure(!bIsRegeneratingOnLoad); // unexpected code path, compile on load handled with different function call
ensure(!bSkipReinstancing); // This is an internal option, should not go through CompileSynchronouslyImpl
ensure(QueuedRequests.Num() == 0);
// Wipe the PreCompile log, any generated messages are now irrelevant
Request.BPToCompile->PreCompileLog.Reset();
// Reset the flag, so if the user tries to use PIE it will warn them if the BP did not compile
Request.BPToCompile->bDisplayCompilePIEWarning = true;
// Do not want to run this code without the editor present nor when running commandlets.
// We do not want to regenerate a search Guid during loads, nothing has changed in the Blueprint
// and it is cached elsewhere.
// We would like to regenerated it when a skeleton changes, but it is too expensive:
if (GEditor && GIsEditor && !bIsRegeneratingOnLoad && !bRegenerateSkeletonOnly)
{
FFindInBlueprintSearchManager::Get().AddOrUpdateBlueprintSearchMetadata(Request.BPToCompile);
}
QueuedRequests.Add(Request);
// We suppress normal compilation broadcasts because the old code path
// did this after GC and we want to match the old behavior:
const bool bSuppressBroadcastCompiled = true;
TMap<UClass*, TMap<UObject*, UObject*>> OldToNewTemplates;
TArray<UBlueprint*> CompiledBlueprints;
TArray<UBlueprint*> SkeletonCompiledBlueprints;
FlushCompilationQueueImpl(bSuppressBroadcastCompiled, &CompiledBlueprints, &SkeletonCompiledBlueprints, nullptr, bFindAndReplaceCDOReferences ? &OldToNewTemplates : nullptr);
FlushReinstancingQueueImpl(bFindAndReplaceCDOReferences, bFindAndReplaceCDOReferences ? &OldToNewTemplates : nullptr);
if (FBlueprintEditorUtils::IsLevelScriptBlueprint(Request.BPToCompile) && !bRegenerateSkeletonOnly)
{
// When the Blueprint is recompiled, then update the bound events for level scripting
ULevelScriptBlueprint* LevelScriptBP = CastChecked<ULevelScriptBlueprint>(Request.BPToCompile);
// ULevel::OnLevelScriptBlueprintChanged needs to be run after the CDO has
// been updated as it respawns the actor:
if (ULevel* BPLevel = LevelScriptBP->GetLevel())
{
// Newly created levels don't need this notification:
if (BPLevel->GetLevelScriptBlueprint(true))
{
BPLevel->OnLevelScriptBlueprintChanged(LevelScriptBP);
}
}
}
if (GEditor && !bRegenerateSkeletonOnly)
{
TRACE_CPUPROFILER_EVENT_SCOPE(BroadcastBlueprintReinstanced)
// Make sure clients know they're being reinstanced as part of blueprint compilation. After this point
// compilation is completely done:
TGuardValue<bool> GuardTemplateNameFlag(GCompilingBlueprint, true);
GEditor->BroadcastBlueprintReinstanced();
}
ensure(Request.BPToCompile->bQueuedForCompilation == false);
if (!bSkipGarbageCollection)
{
TGuardValue<bool> GuardTemplateNameFlag(GIsGCingAfterBlueprintCompile, true);
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
}
if (!bRegenerateSkeletonOnly)
{
TRACE_CPUPROFILER_EVENT_SCOPE(BroadcastChanged);
for(UBlueprint* BP : SkeletonCompiledBlueprints)
{
BP->BroadcastChanged();
}
}
else
{
ensure(SkeletonCompiledBlueprints.Num() == 1);
}
if (!bBatchCompile && !bRegenerateSkeletonOnly)
{
TRACE_CPUPROFILER_EVENT_SCOPE(BroadCastCompiled);
for (UBlueprint* BP : SkeletonCompiledBlueprints)
{
BP->BroadcastCompiled();
}
if (GEditor)
{
GEditor->BroadcastBlueprintCompiled();
}
}
if (CompiledBlueprintsToSave.Num() > 0 && !bRegenerateSkeletonOnly)
{
if (!bSkipSaving)
{
TArray<UPackage*> PackagesToSave;
for (UBlueprint* BP : CompiledBlueprintsToSave)
{
PackagesToSave.Add(BP->GetOutermost());
}
FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, /*bCheckDirty =*/true, /*bPromptToSave =*/false);
}
CompiledBlueprintsToSave.Empty();
}
// We've done our GC, so release old CDO references
OldCDOs.Empty();
}
enum class ECompilationManagerJobType
{
Normal,
SkeletonOnly,
RelinkOnly,
};
// Currently only used to fix up delegate parameters on skeleton ufunctions, resolving the cyclical dependency,
// could be augmented if similar cases arise:
struct FSkeletonFixupData
{
FSimpleMemberReference MemberReference;
FProperty* DelegateProperty;
template<typename T>
static void FixUpDelegateProperty(T* DelegateProperty, const FSimpleMemberReference& MemberReference, UClass* SkeletonGeneratedClass)
{
check(DelegateProperty);
UFunction* OldFunction = DelegateProperty->SignatureFunction;
DelegateProperty->SignatureFunction = FMemberReference::ResolveSimpleMemberReference<UFunction>(MemberReference, SkeletonGeneratedClass);
UStruct* Owner = DelegateProperty->template GetOwnerChecked<UStruct>();
int32 Index = Owner->ScriptAndPropertyObjectReferences.Find(OldFunction);
if (Index != INDEX_NONE)
{
Owner->ScriptAndPropertyObjectReferences[Index] = DelegateProperty->SignatureFunction.Get();
}
}
};
struct FCompilerData
{
explicit FCompilerData(
UBlueprint* InBP,
ECompilationManagerJobType InJobType,
FCompilerResultsLog* InResultsLogOverride,
EBlueprintCompileOptions UserOptions,
bool bBytecodeOnly)
{
check(InBP);
BP = InBP;
JobType = InJobType;
UPackage* Package = BP->GetOutermost();
bPackageWasDirty = Package ? Package->IsDirty() : false;
OriginalBPStatus = BP->Status;
ActiveResultsLog = InResultsLogOverride;
if(InResultsLogOverride == nullptr)
{
ResultsLog = MakeUnique<FCompilerResultsLog>();
ResultsLog->BeginEvent(TEXT("BlueprintCompilationManager Compile"));
ResultsLog->SetSourcePath(InBP->GetPathName());
ActiveResultsLog = ResultsLog.Get();
}
static const FBoolConfigValueHelper IgnoreCompileOnLoadErrorsOnBuildMachine(TEXT("Kismet"), TEXT("bIgnoreCompileOnLoadErrorsOnBuildMachine"), GEngineIni);
ActiveResultsLog->bLogInfoOnly = !BP->bHasBeenRegenerated && GIsBuildMachine && IgnoreCompileOnLoadErrorsOnBuildMachine;
InternalOptions.bRegenerateSkelton = false;
InternalOptions.bReinstanceAndStubOnFailure = false;
InternalOptions.bSaveIntermediateProducts = (UserOptions & EBlueprintCompileOptions::SaveIntermediateProducts) != EBlueprintCompileOptions::None;
InternalOptions.bSkipDefaultObjectValidation = (UserOptions & EBlueprintCompileOptions::SkipDefaultObjectValidation) != EBlueprintCompileOptions::None;
InternalOptions.bSkipFiBSearchMetaUpdate = (UserOptions & EBlueprintCompileOptions::SkipFiBSearchMetaUpdate) != EBlueprintCompileOptions::None;
InternalOptions.bUseDeltaSerializationDuringReinstancing = (UserOptions & EBlueprintCompileOptions::UseDeltaSerializationDuringReinstancing) != EBlueprintCompileOptions::None;
InternalOptions.bSkipNewVariableDefaultsDetection = (UserOptions & EBlueprintCompileOptions::SkipNewVariableDefaultsDetection) != EBlueprintCompileOptions::None;
InternalOptions.CompileType = bBytecodeOnly ? EKismetCompileType::BytecodeOnly : EKismetCompileType::Full;
Compiler = FKismetCompilerContext::GetCompilerForBP(BP, *ActiveResultsLog, InternalOptions);
}
bool IsSkeletonOnly() const { return JobType == ECompilationManagerJobType::SkeletonOnly; }
bool ShouldSetTemporaryBlueprintFlags() const { return JobType != ECompilationManagerJobType::RelinkOnly; }
bool ShouldResetErrorState() const { return JobType == ECompilationManagerJobType::Normal && InternalOptions.CompileType != EKismetCompileType::BytecodeOnly; }
bool ShouldResetAndPopulateGeneratedVariables() const { return JobType != ECompilationManagerJobType::RelinkOnly; }
bool ShouldValidate() const { return JobType == ECompilationManagerJobType::Normal; }
bool ShouldRegenerateSkeleton() const { return JobType != ECompilationManagerJobType::RelinkOnly; }
bool ShouldMarkUpToDateAfterSkeletonStage() const { return IsSkeletonOnly(); }
bool ShouldReconstructNodes() const { return JobType == ECompilationManagerJobType::Normal || (!IsSkeletonOnly() && BP->bIsRegeneratingOnLoad); }
bool ShouldSkipReinstancerCreation() const { return (IsSkeletonOnly() && (!BP->ParentClass || BP->ParentClass->IsNative())); }
bool ShouldInitiateReinstancing() const { return JobType == ECompilationManagerJobType::Normal || BP->bIsRegeneratingOnLoad; }
bool ShouldCompileClassLayout() const { return JobType == ECompilationManagerJobType::Normal; }
bool ShouldCompileClassFunctions() const { return JobType == ECompilationManagerJobType::Normal; }
bool ShouldRegisterCompilerResults() const { return JobType == ECompilationManagerJobType::Normal; }
bool ShouldSkipIfDependenciesAreUnchanged() const { return InternalOptions.CompileType == EKismetCompileType::BytecodeOnly || JobType == ECompilationManagerJobType::RelinkOnly; }
bool ShouldValidateClassDefaultObject() const { return JobType == ECompilationManagerJobType::Normal && !InternalOptions.bSkipDefaultObjectValidation; }
bool ShouldUpdateBlueprintSearchMetadata() const { return JobType == ECompilationManagerJobType::Normal && !InternalOptions.bSkipFiBSearchMetaUpdate; }
bool UseDeltaSerializationDuringReinstancing() const { return InternalOptions.bUseDeltaSerializationDuringReinstancing; }
bool ShouldSkipNewVariableDefaultsDetection() const { return InternalOptions.bSkipNewVariableDefaultsDetection; }
UBlueprint* BP;
FCompilerResultsLog* ActiveResultsLog;
TUniquePtr<FCompilerResultsLog> ResultsLog;
TSharedPtr<FKismetCompilerContext> Compiler;
FKismetCompilerOptions InternalOptions;
TSharedPtr<FBlueprintCompileReinstancer> Reinstancer;
TArray<FSkeletonFixupData> SkeletonFixupData;
/** variables that are new to the generated class and will need their default set (can occur when a new variable is added to a BP's ancestor class) */
TArray<FBPVariableDescription> NewDefaultVariables;
ECompilationManagerJobType JobType;
bool bPackageWasDirty;
EBlueprintStatus OriginalBPStatus;
};
struct FReinstancingJob
{
FReinstancingJob(TSharedPtr<FBlueprintCompileReinstancer> InReinstancer);
FReinstancingJob(TSharedPtr<FBlueprintCompileReinstancer> InReinstancer, TSharedPtr<FKismetCompilerContext> InCompiler);
FReinstancingJob(TPair<UClass*, UClass*> InOldToNew);
// optional:
TSharedPtr<FBlueprintCompileReinstancer> Reinstancer;
TSharedPtr<FKismetCompilerContext> Compiler;
// always set:
TPair<UClass*, UClass*> OldToNew;
struct FArchetypeInfo
{
FArchetypeInfo(UObject* Archetype)
: Archetype(Archetype)
, ArchetypeTemplate(Archetype ? Archetype->GetArchetype() : nullptr)
{}
bool operator==(const FArchetypeInfo& Other) const
{
return Archetype == Other.Archetype;
}
friend int32 GetTypeHash(const FArchetypeInfo& Info)
{
return GetTypeHash(Info.Archetype);
}
UObject* Archetype = nullptr;
UObject* ArchetypeTemplate;
};
// Old archetype to re-instantiate and its old associated template
TArray<FArchetypeInfo> OldArchetypeObjects;
// Keeps track of old archetype objects with cached archetypes which may be needed for reinstancing
// of non-archetypes to assist with archetype lookup when they have partially-reinstanced outer chains.
// We track these objects because they are annotated by the global archetype cache manager, and we'll
// need to clear out those annotations after we're done reinstancing all of the non-template instances.
TSet<UObject*> ArchetypeInstancesWithCachedArchetypes;
};
FReinstancingJob::FReinstancingJob(TSharedPtr<FBlueprintCompileReinstancer> InReinstancer)
: Reinstancer(InReinstancer)
, Compiler()
, OldToNew(nullptr, nullptr)
{
check(InReinstancer.IsValid());
OldToNew.Key = InReinstancer->DuplicatedClass;
OldToNew.Value = InReinstancer->ClassToReinstance;
}
FReinstancingJob::FReinstancingJob(TSharedPtr<FBlueprintCompileReinstancer> InReinstancer, TSharedPtr<FKismetCompilerContext> InCompiler)
: Reinstancer(InReinstancer)
, Compiler(InCompiler)
, OldToNew(nullptr, nullptr)
{
check(InReinstancer.IsValid());
OldToNew.Key = InReinstancer->DuplicatedClass;
OldToNew.Value = InReinstancer->ClassToReinstance;
}
FReinstancingJob::FReinstancingJob(TPair<UClass*, UClass*> InOldToNew)
: Reinstancer()
, Compiler()
, OldToNew(InOldToNew)
{
}
UE_TRACE_EVENT_BEGIN(Cpu, FlushCompilationQueue, NoSync)
UE_TRACE_EVENT_FIELD(UE::Trace::WideString, BlueprintPath)
UE_TRACE_EVENT_FIELD(int32, TotalBlueprints)
UE_TRACE_EVENT_END()
void FBlueprintCompilationManagerImpl::FlushCompilationQueueImpl(bool bSuppressBroadcastCompiled, TArray<UBlueprint*>* BlueprintsCompiled, TArray<UBlueprint*>* BlueprintsCompiledOrSkeletonCompiled, FUObjectSerializeContext* InLoadContext, TMap<UClass*, TMap<UObject*, UObject*>>* OldToNewTemplates /* = nullptr*/)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FlushCompilationQueueImpl);
#if WITH_EDITOR
FScopeLock ScopeLock(&Lock);
#endif
ensure(bGeneratedClassLayoutReady);
if( QueuedRequests.Num() == 0 )
{
return;
}
EnsureArchetypesArePostLoaded();
TGuardValue<bool> GuardTemplateNameFlag(GCompilingBlueprint, true);
FScopedSlowTask SlowTask(17.f /* Number of steps */, LOCTEXT("FlushCompilationQueue", "Compiling blueprints..."));
SlowTask.MakeDialogDelayed(1.0f);
TArray<FCompilerData> CurrentlyCompilingBPs;
double TimeCompiling = 0.0;
{ // begin TimeCompiling scope
FScopedDurationTimer SetupTimer(TimeCompiling);
// STAGE I: Add any related blueprints that were not compiled, then add any children so that they will be relinked:
TArray<UBlueprint*> BlueprintsToRecompile;
// Make sure that we attempt to compile any functions that aren't currently
// in existence - this also ensures function signature changes are handled
QueueOutOfDateDependencies(QueuedRequests, BlueprintsToRecompile, CurrentlyCompilingBPs);
SlowTask.EnterProgressFrame();
// then make sure any normal blueprints have their bytecode dependents recompiled, this is in case a function signature changes:
for (const FBPCompileRequest& CompileJob : QueuedRequests)
{
if ((CompileJob.CompileOptions & EBlueprintCompileOptions::RegenerateSkeletonOnly) != EBlueprintCompileOptions::None)
{
continue;
}
// Add any dependent blueprints for a bytecode compile, this is needed because we
// have no way to keep bytecode safe when a function is renamed or parameters are
// added or removed. Below (Stage VIII) we skip further compilation for blueprints
// that are being bytecode compiled, but their dependencies have not changed:
TArray<UBlueprint*> DependentBlueprints;
FBlueprintEditorUtils::GetDependentBlueprints(CompileJob.BPToCompile, DependentBlueprints);
for (UBlueprint* DependentBlueprint : DependentBlueprints)
{
if(!IsQueuedForCompilation(DependentBlueprint))
{
DependentBlueprint->bQueuedForCompilation = true;
// Because we're adding this as a bytecode only blueprint compile we don't need to
// recursively recompile dependencies. The assumption is that a bytecode only compile
// will not change the class layout. @todo: add an ensure to detect class layout changes
CurrentlyCompilingBPs.Emplace(
FCompilerData(
DependentBlueprint,
ECompilationManagerJobType::Normal,
nullptr,
EBlueprintCompileOptions::None,
true
)
);
BlueprintsToRecompile.Add(DependentBlueprint);
}
}
}
SlowTask.EnterProgressFrame();
// STAGE II: Filter out data only and interface blueprints:
for (int32 I = 0; I < QueuedRequests.Num(); ++I)
{
FBPCompileRequest& QueuedJob = QueuedRequests[I];
UBlueprint* QueuedBP = QueuedJob.BPToCompile;
ensure(!QueuedBP->GeneratedClass ||
!QueuedBP->GeneratedClass->GetDefaultObject(false) ||
!(QueuedBP->GeneratedClass->GetDefaultObject(false)->HasAnyFlags(RF_NeedLoad)));
bool bDefaultComponentMustBeAdded = false;
bool bHasPendingUberGraphFrame = false;
UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(QueuedBP->GeneratedClass);
if (BPGC)
{
if( BPGC->SimpleConstructionScript &&
BPGC->SimpleConstructionScript->GetSceneRootComponentTemplate(true) == nullptr)
{
bDefaultComponentMustBeAdded = FBlueprintEditorUtils::SupportsConstructionScript(QueuedBP);
}
bHasPendingUberGraphFrame = BPGC->UberGraphFramePointerProperty || BPGC->UberGraphFunction;
}
bool bSkipCompile = false;
const UClass* ParentClass = QueuedBP->ParentClass;
const bool bHasClassAndMatchesParent = BPGC && BPGC->GetSuperClass() == ParentClass;
if ( bHasClassAndMatchesParent &&
FBlueprintEditorUtils::IsDataOnlyBlueprint(QueuedBP) && !QueuedBP->bHasBeenRegenerated &&
QueuedBP->GetLinker() && !bDefaultComponentMustBeAdded && !bHasPendingUberGraphFrame )
{
// consider skipping the compile operation for this DOB:
if (ParentClass && ParentClass->HasAllClassFlags(CLASS_Native))
{
bSkipCompile = true;
}
else if (const UClass* CurrentClass = QueuedBP->GeneratedClass)
{
if(FStructUtils::TheSameLayout(CurrentClass, CurrentClass->GetSuperStruct()))
{
bSkipCompile = true;
}
}
}
if (bSkipCompile)
{
CurrentlyCompilingBPs.Emplace(
FCompilerData(
QueuedBP,
ECompilationManagerJobType::SkeletonOnly,
QueuedJob.ClientResultsLog,
QueuedJob.CompileOptions,
false
)
);
if (QueuedBP->GeneratedClass != nullptr)
{
// set bIsRegeneratingOnLoad so that we don't reset loaders:
QueuedBP->bIsRegeneratingOnLoad = true;
FBlueprintEditorUtils::RemoveStaleFunctions(Cast<UBlueprintGeneratedClass>(QueuedBP->GeneratedClass), QueuedBP);
QueuedBP->bIsRegeneratingOnLoad = false;
}
QueuedRequests.RemoveAtSwap(I);
--I;
}
else
{
ECompilationManagerJobType JobType = ECompilationManagerJobType::Normal;
if ((QueuedJob.CompileOptions & EBlueprintCompileOptions::RegenerateSkeletonOnly) != EBlueprintCompileOptions::None)
{
JobType = ECompilationManagerJobType::SkeletonOnly;
}
CurrentlyCompilingBPs.Emplace(
FCompilerData(
QueuedBP,
JobType,
QueuedJob.ClientResultsLog,
QueuedJob.CompileOptions,
false
)
);
BlueprintsToRecompile.Add(QueuedBP);
}
}
SlowTask.EnterProgressFrame();
for (UBlueprint* BP : BlueprintsToRecompile)
{
// make sure all children are at least re-linked:
if (UClass* OldSkeletonClass = BP->SkeletonGeneratedClass)
{
TArray<UClass*> SkeletonClassesToReparentList;
// Has to be recursive gather of children because instances of a UClass will cache information about
// classes that are above their immediate parent (e.g. ClassConstructor):
GetDerivedClasses(OldSkeletonClass, SkeletonClassesToReparentList);
for (UClass* ChildClass : SkeletonClassesToReparentList)
{
if (UBlueprint* ChildBlueprint = UBlueprint::GetBlueprintFromClass(ChildClass))
{
if (!IsQueuedForCompilation(ChildBlueprint))
{
ChildBlueprint->bQueuedForCompilation = true;
ensure(ChildBlueprint->bHasBeenRegenerated);
CurrentlyCompilingBPs.Emplace(
FCompilerData(
ChildBlueprint,
ECompilationManagerJobType::RelinkOnly,
nullptr,
EBlueprintCompileOptions::None,
false
)
);
}
}
}
}
// Don't report progress for substep but gives a chance to tick slate to improve the responsiveness of the
// progress bar being shown. We expect slate to be ticked at regular intervals throughout the loading.
SlowTask.TickProgress();
}
/* Prevent 'pending kill' blueprints from being recompiled. Dependency
gathering is currently done for the following reasons:
* Update a caller's called functions when they are recreated
* Update a child type's cached information about its superclass
* Update a child type's class layout when a parent type layout changes
* Update a reader/writers references to member variables when member variables are recreated
Pending kill objects do not need these updates and StaticDuplicateObject
cannot duplicate them - so they cannot be updated as normal, anyway.
Ultimately pending kill UBlueprintGeneratedClass instances rely on the GetDerivedClasses/ReparentChild
calls in FBlueprintCompileReinstancer() to maintain accurate class layouts so that we
don't leak or scribble memory.
*/
CurrentlyCompilingBPs.RemoveAll(
[](FCompilerData& Data)
{
if(!IsValid(Data.BP))
{
check(!Data.BP->bBeingCompiled);
check(Data.BP->CurrentMessageLog == nullptr);
if(UPackage* Package = Data.BP->GetOutermost())
{
Package->SetDirtyFlag(Data.bPackageWasDirty);
}
if(Data.ResultsLog)
{
Data.ResultsLog->EndEvent();
}
Data.BP->bQueuedForCompilation = false;
return true;
}
return false;
}
);
BlueprintsToRecompile.Empty();
QueuedRequests.Empty();
#if CPUPROFILERTRACE_ENABLED
const FString& FirstBlueprintName =
(CurrentlyCompilingBPs.Num() > 0) ? CurrentlyCompilingBPs[0].BP->GetFullName() : TEXT("<No Blueprints to compile>");
UE_TRACE_LOG_SCOPED_T(Cpu, FlushCompilationQueue, CpuChannel) <<
FlushCompilationQueue.BlueprintPath(*FirstBlueprintName) <<
FlushCompilationQueue.TotalBlueprints(CurrentlyCompilingBPs.Num());
#endif
SlowTask.EnterProgressFrame();
// STAGE III: Sort into correct compilation order. We want to compile root types before their derived (child) types:
auto HierarchyDepthSortFn = [](const FCompilerData& CompilerDataA, const FCompilerData& CompilerDataB)
{
UBlueprint& A = *(CompilerDataA.BP);
UBlueprint& B = *(CompilerDataB.BP);
bool bAIsInterface = FBlueprintEditorUtils::IsInterfaceBlueprint(&A);
bool bBIsInterface = FBlueprintEditorUtils::IsInterfaceBlueprint(&B);
if(bAIsInterface && !bBIsInterface)
{
return true;
}
else if(bBIsInterface && !bAIsInterface)
{
return false;
}
// BPs that do not yet have classes should be compiled last,
// so that we can guarantee their parent class is compiled
// before them. We know they do not yet have child types
// because they have no class themselves:
if(A.GeneratedClass == nullptr && B.GeneratedClass == nullptr)
{
return A.GetFName().LexicalLess(B.GetFName());
}
else if(A.GeneratedClass == nullptr)
{
return false;
}
else if(B.GeneratedClass == nullptr)
{
return true;
}
return FBlueprintCompileReinstancer::ReinstancerOrderingFunction(A.GeneratedClass, B.GeneratedClass);
};
CurrentlyCompilingBPs.Sort( HierarchyDepthSortFn );
SlowTask.EnterProgressFrame();
// store off construction hashes, in case we can do fast reinstancing:
SaveConstructionHashes(CurrentlyCompilingBPs);
// STAGE IV: Set UBlueprint flags (bBeingCompiled, bIsRegeneratingOnLoad)
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
if (!CompilerData.ShouldSetTemporaryBlueprintFlags())
{
continue;
}
UBlueprint* BP = CompilerData.BP;
BP->bBeingCompiled = true;
BP->CurrentMessageLog = CompilerData.ActiveResultsLog;
BP->bIsRegeneratingOnLoad = !BP->bHasBeenRegenerated && BP->GetLinker();
if(CompilerData.ShouldResetErrorState())
{
TArray<UEdGraph*> AllGraphs;
BP->GetAllGraphs(AllGraphs);
for (UEdGraph* Graph : AllGraphs )
{
for (UEdGraphNode* GraphNode : Graph->Nodes)
{
if (GraphNode)
{
GraphNode->ClearCompilerMessage();
}
}
}
}
}
SlowTask.EnterProgressFrame();
// STAGE V: Validate
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
if (CompilerData.ShouldResetAndPopulateGeneratedVariables())
{
CompilerData.Compiler->ResetAndPopulateBlueprintGeneratedVariables();
}
if(!CompilerData.ShouldValidate())
{
continue;
}
CompilerData.Compiler->ValidateVariableNames();
CompilerData.Compiler->ValidateClassPropertyDefaults();
}
SlowTask.EnterProgressFrame();
// STAGE V (phase 2): Give the blueprint the possibility for edits
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
UBlueprint* BP = CompilerData.BP;
if (BP->bIsRegeneratingOnLoad)
{
FKismetCompilerContext& CompilerContext = *(CompilerData.Compiler);
CompilerContext.PreCompileUpdateBlueprintOnLoad(BP);
}
}
SlowTask.EnterProgressFrame();
// STAGE VI: Purge null graphs, misc. data fixup
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
UBlueprint* BP = CompilerData.BP;
if(BP->bIsRegeneratingOnLoad)
{
FBlueprintEditorUtils::PurgeNullGraphs(BP);
BP->ConformNativeComponents();
if (FLinkerLoad* Linker = BP->GetLinker())
{
if (Linker->UEVer() < VER_UE4_EDITORONLY_BLUEPRINTS)
{
BP->ChangeOwnerOfTemplates();
}
}
}
}
SlowTask.EnterProgressFrame();
// STAGE VII: safely throw away old skeleton CDOs:
{
using namespace UE::Kismet::BlueprintCompilationManager;
TMap<UClass*, UClass*> NewSkeletonToOldSkeleton;
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
UBlueprint* BP = CompilerData.BP;
UClass* OldSkeletonClass = BP->SkeletonGeneratedClass;
if(OldSkeletonClass)
{
if (Private::ConsoleVariables::bEnableSkelReinstUpdate)
{
TRACE_CPUPROFILER_EVENT_SCOPE(MoveDependentSkelToReinst);
FBlueprintCompileReinstancer::MoveDependentSkelToReinst(OldSkeletonClass, NewSkeletonToOldSkeleton);
}
else
{
// Old code path
MoveSkelCDOAside(OldSkeletonClass, NewSkeletonToOldSkeleton);
}
}
}
// STAGE VIII: recompile skeleton
// if any function signatures have changed in this skeleton class we will need to recompile all dependencies, but if not
// then we can avoid dependency recompilation:
bool bSkipUnneededDependencyCompilation = !Private::ConsoleVariables::bForceAllDependenciesToRecompile;
TSet<UObject*> OldFunctionsWithSignatureChanges;
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
UBlueprint* BP = CompilerData.BP;
if(CompilerData.ShouldRegenerateSkeleton())
{
TRACE_CPUPROFILER_EVENT_SCOPE(RecompileSkeleton);
SCOPED_LOADTIMER_ASSET_TEXT(*BP->GetPathName());
if(BlueprintsCompiledOrSkeletonCompiled)
{
BlueprintsCompiledOrSkeletonCompiled->Add(BP);
}
BP->SkeletonGeneratedClass = FastGenerateSkeletonClass(BP, *(CompilerData.Compiler), CompilerData.IsSkeletonOnly(), CompilerData.SkeletonFixupData);
UBlueprintGeneratedClass* AuthoritativeClass = Cast<UBlueprintGeneratedClass>(BP->GeneratedClass);
if(AuthoritativeClass && bSkipUnneededDependencyCompilation)
{
if(CompilerData.InternalOptions.CompileType == EKismetCompileType::Full )
{
for (TFieldIterator<UFunction> FuncIt(AuthoritativeClass, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
{
UFunction* OldFunction = *FuncIt;
if(!OldFunction->HasAnyFunctionFlags(EFunctionFlags::FUNC_BlueprintCallable))
{
continue;
}
// We assume that if the func is FUNC_BlueprintCallable that it will be present in the Skeleton class.
// If it is not in the skeleton class we will always think that this blueprints public interface has
// changed. Not a huge deal, but will mean we recompile dependencies more often than necessary.
UFunction* NewFunction = BP->SkeletonGeneratedClass->FindFunctionByName((OldFunction)->GetFName());
if( NewFunction == nullptr ||
!NewFunction->IsSignatureCompatibleWith(OldFunction) ||
// If a function changes its net flags, callers may now need to do a full EX_FinalFunction/EX_VirtualFunction
// instead of a EX_LocalFinalFunction/EX_LocalVirtualFunction:
NewFunction->HasAnyFunctionFlags(FUNC_NetFuncFlags) != OldFunction->HasAnyFunctionFlags(FUNC_NetFuncFlags))
{
OldFunctionsWithSignatureChanges.Add(OldFunction);
break;
}
}
}
}
}
else
{
TRACE_CPUPROFILER_EVENT_SCOPE(RelinkSkeleton);
// Just relink, note that UProperties that reference *other* types may be stale until
// we fixup below:
RelinkSkeleton(BP->SkeletonGeneratedClass);
}
if(CompilerData.ShouldMarkUpToDateAfterSkeletonStage())
{
// Flag data only blueprints as being up-to-date
BP->Status = BP->bHasBeenRegenerated ? CompilerData.OriginalBPStatus : BS_UpToDate;
BP->bHasBeenRegenerated = true;
if (BP->GeneratedClass)
{
BP->GeneratedClass->ClearFunctionMapsCaches();
}
}
}
// Fix up delegate parameters on skeleton class UFunctions, as they have a direct reference to a UFunction*
// that may have been created as part of skeleton generation:
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FixUpDelegateParameters);
UBlueprint* BP = CompilerData.BP;
TArray<FSkeletonFixupData>& ParamsToFix = CompilerData.SkeletonFixupData;
for( const FSkeletonFixupData& FixupData : ParamsToFix )
{
if(FDelegateProperty* DelegateProperty = CastField<FDelegateProperty>(FixupData.DelegateProperty))
{
FSkeletonFixupData::FixUpDelegateProperty(DelegateProperty, FixupData.MemberReference, BP->SkeletonGeneratedClass);
}
else if(FMulticastDelegateProperty* MCDelegateProperty = CastField<FMulticastDelegateProperty>(FixupData.DelegateProperty))
{
FSkeletonFixupData::FixUpDelegateProperty(MCDelegateProperty, FixupData.MemberReference, BP->SkeletonGeneratedClass);
}
}
}
// Skip further compilation for blueprints that are being bytecode compiled as a dependency of something that has
// not had a change in its function parameters:
if(bSkipUnneededDependencyCompilation)
{
const auto HasNoReferencesToChangedFunctions = [&OldFunctionsWithSignatureChanges](FCompilerData& Data)
{
if(!Data.ShouldSkipIfDependenciesAreUnchanged())
{
return false;
}
// Anim BPs cannot skip un-needed dependency compilation as their property access bytecode
// will need refreshing due to external class layouts changing (they require at least a bytecode recompile or a relink)
const bool bIsAnimBlueprintClass = !!Cast<UAnimBlueprint>(Data.BP);
if(bIsAnimBlueprintClass)
{
return false;
}
// if our parent is still being compiled, then we still need to be compiled:
UClass* Iter = Data.BP->ParentClass;
while(Iter)
{
if(UBlueprint* BP = Cast<UBlueprint>(Iter->ClassGeneratedBy))
{
if(BP->bBeingCompiled)
{
return false;
}
}
Iter = Iter->GetSuperClass();
}
// look for references to a function with a signature change
// in the old class, if it has none, we can skip bytecode recompile:
bool bHasNoReferencesToChangedFunctions = true;
UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(Data.BP->GeneratedClass);
if(BPGC)
{
for(UFunction* CalledFunction : BPGC->CalledFunctions)
{
if(OldFunctionsWithSignatureChanges.Contains(CalledFunction))
{
bHasNoReferencesToChangedFunctions = false;
break;
}
}
}
if(bHasNoReferencesToChangedFunctions)
{
// This BP is not actually going to be compiled, clean it up:
Data.BP->bBeingCompiled = false;
Data.BP->CurrentMessageLog = nullptr;
if(UPackage* Package = Data.BP->GetOutermost())
{
Package->SetDirtyFlag(Data.bPackageWasDirty);
}
if(Data.ResultsLog)
{
Data.ResultsLog->EndEvent();
}
Data.BP->bQueuedForCompilation = false;
}
return bHasNoReferencesToChangedFunctions;
};
// Order very much matters, but we could RemoveAllSwap and re-sort:
CurrentlyCompilingBPs.RemoveAll(HasNoReferencesToChangedFunctions);
}
}
// Detect any variable-based properties that are not in the old generated class, save them for after reinstancing. This can occur
// when a new variable is introduced in an ancestor class, and we'll need to use its default as our generated class's initial value.
{
TRACE_CPUPROFILER_EVENT_SCOPE(DetectNewDefaultVariables);
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
if (CompilerData.JobType == ECompilationManagerJobType::Normal &&
CompilerData.BP->bHasBeenRegenerated && // Note: This ensures that we'll only do this after the Blueprint has been loaded/created; otherwise the class may not contain any properties to find.
CompilerData.BP->GeneratedClass &&
!CompilerData.ShouldSkipNewVariableDefaultsDetection())
{
const UClass* ParentClass = CompilerData.BP->ParentClass;
while (const UBlueprint* ParentBP = UBlueprint::GetBlueprintFromClass(ParentClass))
{
for (const FBPVariableDescription& ParentBPVarDesc : ParentBP->NewVariables)
{
if (!CompilerData.BP->GeneratedClass->FindPropertyByName(ParentBPVarDesc.VarName))
{
CompilerData.NewDefaultVariables.Add(ParentBPVarDesc);
}
}
ParentClass = ParentBP->ParentClass;
}
}
}
}
SlowTask.EnterProgressFrame();
// STAGE IX: Reconstruct nodes and replace deprecated nodes, then broadcast 'precompile
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
if(!CompilerData.ShouldReconstructNodes())
{
continue;
}
UBlueprint* BP = CompilerData.BP;
TRACE_CPUPROFILER_EVENT_SCOPE(ReconstructNodes);
SCOPED_LOADTIMER_ASSET_TEXT(*BP->GetPathName());
UE_TRACK_REFERENCING_PACKAGE_SCOPED(BP, PackageAccessTrackingOps::NAME_CookerBuildObject);
ConformToParentAndInterfaces(BP);
// Some nodes are set up to do things during reconstruction only when this flag is NOT set.
if(BP->bIsRegeneratingOnLoad)
{
FBlueprintEditorUtils::ReconstructAllNodes(BP);
FBlueprintEditorUtils::ReplaceDeprecatedNodes(BP);
}
else
{
// matching existing behavior, when compiling a BP not on load we refresh nodes
// before compiling:
FBlueprintCompileReinstancer::OptionallyRefreshNodes(BP);
TArray<UBlueprint*> DependentBlueprints;
FBlueprintEditorUtils::GetDependentBlueprints(BP, DependentBlueprints);
for (UBlueprint* CurrentBP : DependentBlueprints)
{
const EBlueprintStatus OriginalStatus = CurrentBP->Status;
UPackage* const Package = CurrentBP->GetOutermost();
const bool bStartedWithUnsavedChanges = Package != nullptr ? Package->IsDirty() : true;
FBlueprintEditorUtils::RefreshExternalBlueprintDependencyNodes(CurrentBP, BP->GeneratedClass);
CurrentBP->Status = OriginalStatus;
if(Package != nullptr && Package->IsDirty() && !bStartedWithUnsavedChanges)
{
Package->SetDirtyFlag(false);
}
}
}
// Broadcast pre-compile
{
if(GEditor && GIsEditor)
{
GEditor->BroadcastBlueprintPreCompile(BP);
}
}
if (CompilerData.ShouldUpdateBlueprintSearchMetadata())
{
// Do not want to run this code without the editor present nor when running commandlets.
if (GEditor && GIsEditor)
{
// We do not want to regenerate a search Guid during loads, nothing has changed in the Blueprint and it is cached elsewhere
if (!BP->bIsRegeneratingOnLoad)
{
FFindInBlueprintSearchManager::Get().AddOrUpdateBlueprintSearchMetadata(BP);
}
}
}
// we are regenerated, tag ourself as such so that
// old logic to 'fix' circular dependencies doesn't
// cause redundant regeneration (e.g. bForceRegenNodes
// in ExpandTunnelsAndMacros):
BP->bHasBeenRegenerated = true;
}
SlowTask.EnterProgressFrame();
// STAGE X: reinstance every blueprint that is queued, note that this means classes in the hierarchy that are *not* being
// compiled will be parented to REINST versions of the class, so type checks (IsA, etc) involving those types
// will be incoherent!
{
TRACE_CPUPROFILER_EVENT_SCOPE(ReinstanceQueued);
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
// we including skeleton only compilation jobs for reinstancing because we need UpdateCustomPropertyListForPostConstruction
// to happen (at the right time) for those generated classes as well. This means we *don't* need to reinstance if
// the parent is a native type (unless we hot reload, but that should not need to be handled here):
if(CompilerData.ShouldSkipReinstancerCreation())
{
continue;
}
// no need to reinstance skeleton or relink jobs that are not in a hierarchy that has had reinstancing initiated:
bool bRequiresReinstance = CompilerData.ShouldInitiateReinstancing();
if (!bRequiresReinstance)
{
UClass* Iter = CompilerData.BP->GeneratedClass;
if (!Iter)
{
bRequiresReinstance = true;
}
while (Iter)
{
if (Iter->HasAnyClassFlags(CLASS_NewerVersionExists))
{
bRequiresReinstance = true;
break;
}
Iter = Iter->GetSuperClass();
}
}
if (!bRequiresReinstance)
{
continue;
}
UBlueprint* BP = CompilerData.BP;
SCOPED_LOADTIMER_ASSET_TEXT(*BP->GetPathName());
if(BP->GeneratedClass)
{
OldCDOs.Add(BP, BP->GeneratedClass->GetDefaultObject(false));
}
EBlueprintCompileReinstancerFlags CompileReinstancerFlags =
EBlueprintCompileReinstancerFlags::AutoInferSaveOnCompile
| EBlueprintCompileReinstancerFlags::AvoidCDODuplication;
if (CompilerData.UseDeltaSerializationDuringReinstancing())
{
CompileReinstancerFlags |= EBlueprintCompileReinstancerFlags::UseDeltaSerialization;
}
CompilerData.Reinstancer = TSharedPtr<FBlueprintCompileReinstancer>(
new FBlueprintCompileReinstancer(
BP->GeneratedClass,
CompileReinstancerFlags
)
);
if(CompilerData.Compiler.IsValid())
{
CompilerData.Compiler->OldClass = Cast<UBlueprintGeneratedClass>(CompilerData.Reinstancer->DuplicatedClass);
}
if(BP->GeneratedClass)
{
BP->GeneratedClass->bLayoutChanging = true;
CompilerData.Reinstancer->SaveSparseClassData(BP->GeneratedClass);
}
}
}
SlowTask.EnterProgressFrame();
// STAGE XI: Reinstancing done, lets fix up child->parent pointers and take ownership of SCD:
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
UBlueprint* BP = CompilerData.BP;
if(BP->GeneratedClass && BP->GeneratedClass->GetSuperClass()->HasAnyClassFlags(CLASS_NewerVersionExists))
{
BP->GeneratedClass->SetSuperStruct(BP->GeneratedClass->GetSuperClass()->GetAuthoritativeClass());
}
if(BP->GeneratedClass && CompilerData.Reinstancer.IsValid())
{
CompilerData.Reinstancer->TakeOwnershipOfSparseClassData(BP->GeneratedClass);
}
}
SlowTask.EnterProgressFrame();
// STAGE XII: Recompile every blueprint
bGeneratedClassLayoutReady = false;
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
UBlueprint* BP = CompilerData.BP;
if(CompilerData.ShouldCompileClassLayout())
{
TRACE_CPUPROFILER_EVENT_SCOPE(CompileClassLayout);
SCOPED_LOADTIMER_ASSET_TEXT(*BP->GetPathName());
ensure( BP->GeneratedClass == nullptr ||
BP->GeneratedClass->GetDefaultObject(false) == nullptr ||
BP->GeneratedClass->GetDefaultObject(false)->GetClass() != BP->GeneratedClass);
// default value propagation occurs in ReinstaneBatch, CDO will be created via CompileFunctions call:
if(BP->ParentClass)
{
if(BP->GeneratedClass)
{
BP->GeneratedClass->SetDefaultObject(nullptr);
}
// Reset the flag, so if the user tries to use PIE it will warn them if the BP did not compile
BP->bDisplayCompilePIEWarning = true;
// this will create FProperties for the UClass and generate the sparse class data
// if the compiler in question wants to:
FKismetCompilerContext& CompilerContext = *(CompilerData.Compiler);
CompilerContext.CompileClassLayout(EInternalCompilerFlags::PostponeLocalsGenerationUntilPhaseTwo);
// We immediately relink children so that iterative compilation logic has an easier time:
TArray<UClass*> ClassesToRelink;
GetDerivedClasses(BP->GeneratedClass, ClassesToRelink, false);
for (UClass* ChildClass : ClassesToRelink)
{
ChildClass->Bind();
ChildClass->StaticLink();
ensure(ChildClass->GetDefaultObject(false) == nullptr);
}
}
else
{
CompilerData.ActiveResultsLog->Error(*LOCTEXT("KismetCompileError_MalformedParentClasss", "Blueprint @@ has missing or NULL parent class.").ToString(), BP);
}
}
else if(CompilerData.Compiler.IsValid() && BP->GeneratedClass)
{
CompilerData.Compiler->SetNewClass( CastChecked<UBlueprintGeneratedClass>(BP->GeneratedClass) );
}
}
// If we're compiling more than one Blueprint as part of a batch operation, we may have generated
// delegate properties (e.g. function parameters) that reference a function dependency in one of
// the other Blueprints in the batched set. Depending on the compile order, those functions may get
// regenerated in a subsequent pass above, invalidating the delegate property references as a result.
// Invalidating references that exist outside of the class layout (e.g. in bytecode) is fine, as
// those will be fixed up after we've finished compiling. However, the next stage (compiling functions)
// will attempt to validate generated fields for function parameters against their source pin types,
// and that logic will throw an error for generated delegate properties if they reference a signature
// function that has not yet been replaced. As a result, we run a small pass here to find/fix those up.
//
// Note: It is *not* necessary to also verify ScriptAndPropertyObjectReferences here, as that will be
// updated in the next stage via reference collection (@see FKismetCompilerContext::CompileFunctions).
FixupDelegateProperties(CurrentlyCompilingBPs);
bGeneratedClassLayoutReady = true;
ProcessExtensions(CurrentlyCompilingBPs);
SlowTask.EnterProgressFrame();
// STAGE XIII: Compile functions
UBlueprintEditorSettings* Settings = GetMutableDefault<UBlueprintEditorSettings>();
const bool bSaveBlueprintsAfterCompile = Settings->SaveOnCompile == SoC_Always;
const bool bSaveBlueprintAfterCompileSucceeded = Settings->SaveOnCompile == SoC_SuccessOnly;
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
UBlueprint* BP = CompilerData.BP;
UClass* BPGC = BP->GeneratedClass;
if(!CompilerData.ShouldCompileClassFunctions())
{
if( BPGC &&
( BPGC->GetDefaultObject(false) == nullptr ||
BPGC->GetDefaultObject(false)->GetClass() != BPGC) )
{
if (CompilerData.Reinstancer.IsValid())
{
CompilerData.Reinstancer->PropagateSparseClassDataToNewClass(BPGC);
}
// relink, generate CDO:
BPGC->bLayoutChanging = false;
BPGC->Bind();
BPGC->StaticLink(true);
// When compiling skeleton only, it's possible that a non-skeleton BPGC
// has not been compiled yet and has uninitialized class flags. Some class
// flags must be set in order for the CDO construction to work correctly,
// like for instancing subobjects. Make sure that BPGC has at least its
// inherited and property based class flags.
FBlueprintClassFlagUtils::PropagateParentClassFlags_CompileClassLayout(BPGC);
FBlueprintClassFlagUtils::PropagateParentClassFlags_FinishCompilingClass(BPGC);
FBlueprintClassFlagUtils::AppendPropertyBasedClassFlags(BPGC);
// Force recreate the CDO
BPGC->SetDefaultObject(nullptr);
BPGC->GetDefaultObject(true);
}
if(CompilerData.IsSkeletonOnly() && BP->bIsRegeneratingOnLoad)
{
#if DO_GUARD_SLOW
ensure(FBlueprintEditorUtils::IsDataOnlyBlueprint(BP));
#endif
FKismetEditorUtilities::ConformBlueprintFlagsAndComponents(BP);
if (BP->GeneratedClass)
{
FBlueprintEditorUtils::RecreateClassMetaData(BP, BP->GeneratedClass, true);
}
}
}
else
{
TRACE_CPUPROFILER_EVENT_SCOPE(CompileClassFunctions);
SCOPED_LOADTIMER_ASSET_TEXT(*BP->GetPathName());
// default value propagation occurs below:
if(BPGC)
{
if (CompilerData.Reinstancer.IsValid())
{
CompilerData.Reinstancer->PropagateSparseClassDataToNewClass(BPGC);
}
if( BPGC->GetDefaultObject(false) &&
BPGC->GetDefaultObject(false)->GetClass() == BPGC)
{
// the CDO has been created early, it is possible that the reflection data was still
// being mutated by CompileClassLayout. Warn the user and and move the CDO aside:
ensureAlwaysMsgf(false,
TEXT("ClassDefaultObject for %s created at the wrong time - it may be corrupt. It is recommended that you save all data and restart the editor session"),
*BP->GetName()
);
BPGC->GetDefaultObject(false)->Rename(
nullptr,
// destination - this is the important part of this call. Moving the object
// out of the way so we can reuse its name:
GetTransientPackage(),
// Rename options:
REN_DoNotDirty | REN_DontCreateRedirectors
);
}
BPGC->SetDefaultObject(nullptr);
// class layout is ready, we can clear bLayoutChanging and CompileFunctions can create the CDO:
BPGC->bLayoutChanging = false;
FKismetCompilerContext& CompilerContext = *(CompilerData.Compiler);
CompilerContext.CompileFunctions(
EInternalCompilerFlags::PostponeLocalsGenerationUntilPhaseTwo
|EInternalCompilerFlags::PostponeDefaultObjectAssignmentUntilReinstancing
|EInternalCompilerFlags::SkipRefreshExternalBlueprintDependencyNodes
);
}
if (CompilerData.ActiveResultsLog->NumErrors == 0)
{
// Blueprint is error free. Go ahead and fix up debug info
BP->Status = (0 == CompilerData.ActiveResultsLog->NumWarnings) ? BS_UpToDate : BS_UpToDateWithWarnings;
BP->BlueprintSystemVersion = UBlueprint::GetCurrentBlueprintSystemVersion();
// Reapply breakpoints to the bytecode of the new class
FKismetDebugUtilities::ForeachBreakpoint(
BP,
[](FBlueprintBreakpoint& Breakpoint)
{
FKismetDebugUtilities::ReapplyBreakpoint(Breakpoint);
}
);
}
else
{
BP->Status = BS_Error; // do we still have the old version of the class?
}
// SOC settings only apply after compile on load:
if(!BP->bIsRegeneratingOnLoad)
{
if(bSaveBlueprintsAfterCompile || (bSaveBlueprintAfterCompileSucceeded && BP->Status == BS_UpToDate))
{
CompiledBlueprintsToSave.Add(BP);
}
}
}
if(BPGC)
{
BPGC->ClassFlags &= ~CLASS_ReplicationDataIsSetUp;
BPGC->SetUpRuntimeReplicationData();
}
FKismetCompilerUtilities::UpdateDependentBlueprints(BP);
ensure(BPGC == nullptr || BPGC->GetDefaultObject(false)->GetClass() == BPGC);
}
} // end TimeCompiling scope
SlowTask.EnterProgressFrame();
// STAGE XIV: Now we can finish the first stage of the reinstancing operation, moving old classes to new classes:
double TimeReinstancing = 0.0;
{
{
TRACE_CPUPROFILER_EVENT_SCOPE(MoveOldClassesToNewClasses);
TArray<FReinstancingJob> Reinstancers;
// Set up reinstancing jobs - we need a reference to the compiler in order to honor
// CopyTermDefaultsToDefaultObject
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
if(CompilerData.Reinstancer.IsValid() && CompilerData.Reinstancer->ClassToReinstance)
{
Reinstancers.Push(
FReinstancingJob( CompilerData.Reinstancer, CompilerData.Compiler )
);
}
}
FScopedDurationTimer ReinstTimer(TimeReinstancing);
ReinstanceBatch(Reinstancers, MutableView(ClassesToReinstance), InLoadContext, OldToNewTemplates);
// Take ownership of objects with cached archetypes and defer cleanup until after reinstancing is complete
for (FReinstancingJob& ReinstancingJob : Reinstancers)
{
if (ReinstancingJob.ArchetypeInstancesWithCachedArchetypes.Num() > 0)
{
ObjectsWithCachedArchetypesForReinstancing.Append(ObjectPtrWrap(MoveTemp(ReinstancingJob.ArchetypeInstancesWithCachedArchetypes)));
ReinstancingJob.ArchetypeInstancesWithCachedArchetypes = {};
}
}
// We purposefully do not remove the OldCDOs yet, need to keep them in memory past first GC
}
// Set default values on any newly-introduced variables (from ancestor BPs)
{
TRACE_CPUPROFILER_EVENT_SCOPE(SetNewDefaultVariables);
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
if (!CompilerData.NewDefaultVariables.Num())
{
continue;
}
const UBlueprintGeneratedClass* GenClass = Cast<UBlueprintGeneratedClass>(CompilerData.BP->GeneratedClass);
if (GenClass && GenClass->GetDefaultObject(false))
{
for (const FBPVariableDescription& NewInheritedVar : CompilerData.NewDefaultVariables)
{
if (const FProperty* MatchingProperty = GenClass->FindPropertyByName(NewInheritedVar.VarName))
{
FBlueprintEditorUtils::PropertyValueFromString(MatchingProperty, NewInheritedVar.DefaultValue, reinterpret_cast<uint8*>(GenClass->GetDefaultObject(false)));
}
}
}
}
}
// STAGE XV: POST CDO COMPILED
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
TRACE_CPUPROFILER_EVENT_SCOPE(PostCDOCompiled);
if (CompilerData.Compiler.IsValid())
{
SCOPED_LOADTIMER_ASSET_TEXT(*CompilerData.BP->GetPathName());
UObject::FPostCDOCompiledContext PostCDOCompiledContext;
PostCDOCompiledContext.bIsRegeneratingOnLoad = CompilerData.BP->bIsRegeneratingOnLoad;
PostCDOCompiledContext.bIsSkeletonOnly = CompilerData.IsSkeletonOnly();
if( UE::Private::bRunArchetypePostLoadEarly &&
PostCDOCompiledContext.bIsSkeletonOnly)
{
// when compiling on load, we want to run the normal user code, even if we
// can skip the bytecode compile:
PostCDOCompiledContext.bIsSkeletonOnly &= !PostCDOCompiledContext.bIsRegeneratingOnLoad;
}
CompilerData.Compiler->PostCDOCompiled(PostCDOCompiledContext);
}
}
// STAGE XVI: CLEAR TEMPORARY FLAGS
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
TRACE_CPUPROFILER_EVENT_SCOPE(ClearTemporaryFlags);
UBlueprint* BP = CompilerData.BP;
// Ensure that delegate nodes/pins conform to compiled function names/signatures. In general,
// these will rely on the skeleton class, so we must also include it for a skeleton-only pass.
FBlueprintEditorUtils::UpdateDelegatesInBlueprint(BP);
// These are post-compile validations that generally rely on a fully-generated up-to-date class,
// so we defer it on skeleton-only passes as well as during compile-on-load, where it's generally
// going to be handled during the PostLoad() phase after all dependencies have been loaded/compiled.
if (!CompilerData.IsSkeletonOnly() && !BP->bIsRegeneratingOnLoad && BP->GeneratedClass)
{
FKismetEditorUtilities::StripExternalComponents(BP);
if (BP->SimpleConstructionScript)
{
BP->SimpleConstructionScript->FixupRootNodeParentReferences();
}
if (BP->Status != BS_Error)
{
if (CompilerData.Compiler.IsValid())
{
// Route through the compiler context in order to perform type-specific Blueprint class validation.
CompilerData.Compiler->ValidateGeneratedClass(CastChecked<UBlueprintGeneratedClass>(BP->GeneratedClass));
if (CompilerData.ShouldValidateClassDefaultObject())
{
// Our CDO should be properly constructed by this point and should always exist
const UObject* ClassDefaultObject = BP->GeneratedClass->GetDefaultObject(false);
if (ensureAlways(ClassDefaultObject))
{
FKismetCompilerUtilities::ValidateEnumProperties(ClassDefaultObject, *CompilerData.ActiveResultsLog);
FDataValidationContext Context;
// Make sure any class-specific validation passes
if (UE::Kismet::BlueprintCompilationManager::Private::ConsoleVariables::bDoFullDataValidationDuringCompilation)
{
EDataValidationResult ValidateBlueprintResult = BP->IsDataValid(/*out*/ Context);
}
else
{
EDataValidationResult ValidateCDOResult = ClassDefaultObject->IsDataValid(/*out*/ Context);
}
if (Context.GetNumErrors() + Context.GetNumWarnings() > 0)
{
TArray<FText> Warnings, Errors;
Context.SplitIssues(Warnings, Errors);
for (const FText& Warning : Warnings)
{
CompilerData.ActiveResultsLog->Warning(*Warning.ToString());
}
for (const FText& Error : Errors)
{
CompilerData.ActiveResultsLog->Error(*Error.ToString());
}
}
// Adjust Blueprint status to match anything new that was found during validation.
if (CompilerData.ActiveResultsLog->NumErrors > 0)
{
BP->Status = BS_Error;
}
else if (BP->Status == BS_UpToDate && CompilerData.ActiveResultsLog->NumWarnings > 0)
{
BP->Status = BS_UpToDateWithWarnings;
}
}
}
}
else
{
UBlueprint::ValidateGeneratedClass(BP->GeneratedClass);
}
}
}
if(CompilerData.ShouldRegisterCompilerResults())
{
if (IsInGameThread())
{
// This helper structure registers the results log messages with the UI control that displays them:
FScopedBlueprintMessageLog MessageLog(BP);
MessageLog.Log->ClearMessages();
MessageLog.Log->AddMessages(CompilerData.ActiveResultsLog->Messages, false);
}
else
{
Async(EAsyncExecution::TaskGraphMainThread,
[Messages = CompilerData.ActiveResultsLog->Messages, WeakBP = TWeakObjectPtr<UBlueprint>(BP)]()
{
if (WeakBP.IsValid())
{
FScopedBlueprintMessageLog MessageLog(WeakBP.Get());
MessageLog.Log->ClearMessages();
MessageLog.Log->AddMessages(Messages, false);
}
}
);
}
}
if(CompilerData.ShouldSetTemporaryBlueprintFlags())
{
BP->bBeingCompiled = false;
BP->CurrentMessageLog = nullptr;
BP->bIsRegeneratingOnLoad = false;
}
if(UPackage* Package = BP->GetOutermost())
{
Package->SetDirtyFlag(CompilerData.bPackageWasDirty);
}
}
// Make sure no junk in bytecode, this can happen only for blueprints that were in CurrentlyCompilingBPs because
// the reinstancer can detect all other references (see UpdateBytecodeReferences):
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
if(CompilerData.ShouldCompileClassFunctions())
{
if(BlueprintsCompiled)
{
BlueprintsCompiled->Add(CompilerData.BP);
}
if(!bSuppressBroadcastCompiled)
{
// Some logic (e.g. UObject::ProcessInternal) uses this flag to suppress warnings:
TGuardValue<std::atomic<bool>, bool> ReinstancingGuard(GIsReinstancing, true);
CompilerData.BP->BroadcastCompiled();
}
continue;
}
UBlueprint* BP = CompilerData.BP;
for( TFieldIterator<UFunction> FuncIter(BP->GeneratedClass, EFieldIteratorFlags::ExcludeSuper); FuncIter; ++FuncIter )
{
UFunction* CurrentFunction = *FuncIter;
if( CurrentFunction->Script.Num() > 0 )
{
FFixupBytecodeReferences ValidateAr(CurrentFunction);
}
}
}
if (!bSuppressBroadcastCompiled)
{
TRACE_CPUPROFILER_EVENT_SCOPE(BroadcastBlueprintCompiled);
if(GEditor)
{
GEditor->BroadcastBlueprintCompiled();
}
}
}
SlowTask.EnterProgressFrame();
for (FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
if(CompilerData.ResultsLog)
{
CompilerData.ResultsLog->EndEvent();
}
CompilerData.BP->bQueuedForCompilation = false;
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(UEdGraphPin::Purge);
UEdGraphPin::Purge();
}
SlowTask.EnterProgressFrame();
UE_LOGFMT(LogBlueprint, Display, "Finished compiling {0} Blueprint(s):", CurrentlyCompilingBPs.Num());
for (const FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
UE_LOGFMT(LogBlueprint, Display, "\t{0}", CompilerData.BP->GetFullName());
}
UE_LOGFMT(LogBlueprint, Display, "\tTime Compiling: {0} ms. Time Reinstancing: {1} ms.", TimeCompiling * 1000.0, TimeReinstancing * 1000.0);
VerifyNoQueuedRequests(CurrentlyCompilingBPs);
}
void FBlueprintCompilationManagerImpl::FixupDelegateProperties(const TArray<FCompilerData>& InCurrentlyCompilingBPs)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FixupDelegateProperties);
if (InCurrentlyCompilingBPs.Num() <= 1)
{
// It's safe to bypass the fixup logic in this case.
return;
}
// Gather old->new field mappings for each regenerated class.
TMap<FFieldVariant, FFieldVariant> AllFieldMappings;
for (const FCompilerData& CompilerData : InCurrentlyCompilingBPs)
{
// If there's no reinstancer, then we don't need to include it (e.g. skeleton-only compiles and/or when the layout wasn't regenerated).
if (CompilerData.Reinstancer.IsValid())
{
TMap<FFieldVariant, FFieldVariant> FieldMappings;
CompilerData.Reinstancer->GenerateFieldMappings(FieldMappings);
AllFieldMappings.Append(MoveTemp(FieldMappings));
}
}
auto FixupDelegateProperty = [&AllFieldMappings](FDelegateProperty* DelegateProperty)
{
// See if the current signature references a stale function (i.e. one that has been regenerated).
auto* PossiblyStaleFunctionPtr = &DelegateProperty->SignatureFunction;
if (FFieldVariant* GeneratedFunctionMapping = AllFieldMappings.Find(*PossiblyStaleFunctionPtr))
{
// Update the stale reference to point to the regenerated function instead.
*PossiblyStaleFunctionPtr = CastChecked<UFunction>(GeneratedFunctionMapping->ToUObject());
}
};
for (const TPair<FFieldVariant, FFieldVariant>& FieldMapping : AllFieldMappings)
{
// Check signatures for any delegate properties owned by the regenerated class.
if (FDelegateProperty* ClassDelegateProperty = CastField<FDelegateProperty>(FieldMapping.Value.ToField()))
{
FixupDelegateProperty(ClassDelegateProperty);
}
else if (UFunction* Function = Cast<UFunction>(FieldMapping.Value.ToUObject()))
{
// Check signatures for any delegate properties owned by a regenerated class function (e.g. parameters).
for (FDelegateProperty* LocalDelegateProperty : TFieldRange<FDelegateProperty>(Function, EFieldIterationFlags::IncludeDeprecated))
{
FixupDelegateProperty(LocalDelegateProperty);
}
}
}
}
void FBlueprintCompilationManagerImpl::ProcessExtensions(const TArray<FCompilerData>& InCurrentlyCompilingBPs)
{
TRACE_CPUPROFILER_EVENT_SCOPE(ProcessExtensions);
if(CompilerExtensions.Num() == 0)
{
return;
}
for (const FCompilerData& CompilerData : InCurrentlyCompilingBPs)
{
UBlueprint* BP = CompilerData.BP;
FBlueprintCompiledData CompiledData; // populate only if we find an extension:
if(CompilerData.ShouldCompileClassLayout())
{
// give extension a chance to raise errors, or save off data:
UClass* Iter = BP->GetClass();
while(Iter != UBlueprint::StaticClass()->GetSuperClass())
{
auto* Extensions = CompilerExtensions.Find(Iter);
if(Extensions)
{
// extension found, store off data from compiler that we want to expose to extensions:
if(CompiledData.IntermediateGraphs.Num() == 0)
{
if(CompilerData.Compiler->ConsolidatedEventGraph)
{
CompiledData.IntermediateGraphs.Emplace(CompilerData.Compiler->ConsolidatedEventGraph);
}
for(const FKismetFunctionContext& Fn : CompilerData.Compiler->FunctionList)
{
if(Fn.SourceGraph == CompilerData.Compiler->ConsolidatedEventGraph)
{
continue;
}
CompiledData.IntermediateGraphs.Emplace(Fn.SourceGraph);
}
}
for(UBlueprintCompilerExtension* Extension : *Extensions)
{
Extension->BlueprintCompiled(*CompilerData.Compiler, CompiledData);
}
}
Iter = Iter->GetSuperClass();
}
}
}
}
void FBlueprintCompilationManagerImpl::FlushReinstancingQueueImpl(bool bFindAndReplaceCDOReferences, TMap<UClass*, TMap<UObject*, UObject*>>* OldToNewTemplates /* = nullptr*/)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FlushReinstancingQueueImpl);
#if WITH_EDITOR
FScopeLock ScopeLock(&Lock);
#endif
if (GCompilingBlueprint)
{
return;
}
TGuardValue<bool> GuardTemplateNameFlag(GCompilingBlueprint, true);
// we can finalize reinstancing now:
if (ClassesToReinstance.Num() == 0)
{
ClassHashes.Reset();
return;
}
double TimeReinstancing = 0.0;
int NumClassesToReinstance = ClassesToReinstance.Num();
{
FScopedDurationTimer ReinstTimer(TimeReinstancing);
TGuardValue<std::atomic<bool>, bool> ReinstancingGuard(GIsReinstancing, true);
TMap<UClass*, UClass*> ClassesToReinstanceOwned = ObjectPtrDecay(MoveTemp(ClassesToReinstance));
ClassesToReinstance = {};
FReplaceInstancesOfClassParameters Options;
Options.bArchetypesAreUpToDate = true;
Options.bReplaceReferencesToOldCDOs = bFindAndReplaceCDOReferences;
Options.OldToNewTemplates = OldToNewTemplates;
Options.ConstructionVersioningData = ClassHashes.Num() > 0 ? &ClassHashes : nullptr;
FBlueprintCompileReinstancer::BatchReplaceInstancesOfClass(ClassesToReinstanceOwned, Options);
ClassHashes.Reset();
// Clean up objects with cached archetypes now that reinstancing is complete - these will have been
// carried over from any archetype objects we reinstanced during the compile phase
if (ObjectsWithCachedArchetypesForReinstancing.Num() > 0)
{
TSet<UObject*> ObjectsWithCachedArchetypes = ObjectPtrDecay(MoveTemp(ObjectsWithCachedArchetypesForReinstancing));
ObjectsWithCachedArchetypesForReinstancing = {};
FEditorCacheArchetypeManager& CacheManager = FEditorCacheArchetypeManager::Get();
for (UObject* ObjectWithCachedArchetype : ObjectsWithCachedArchetypes)
{
// It's possible that references to old instances for which we've cached archetypes can get replaced with
// NULL in the source map via ARO during reference replacement, which could otherwise assert here, because
// this API requires the input reference to be valid as a precondition.
if (ObjectWithCachedArchetype)
{
CacheManager.ResetCacheArchetype(ObjectWithCachedArchetype);
}
}
}
// Special case when we run on ALT, we want to cleanup all classes flagged for reinstantiation right away.
const bool bIsInActualAsyncLoadingThread = IsInAsyncLoadingThread() && !IsInGameThread();
if (IsAsyncLoading() && (!IsAsyncLoadingMultithreaded() || !bIsInActualAsyncLoadingThread))
{
// While async loading we only remove classes that have no instances being
// async loaded. Those instances will need to be reinstanced once they finish
// loading, there's no race here because if any instances are created after
// we check ClassHasInstancesAsyncLoading they will be created with the new class:
for (TMap<UClass*, UClass*>::TIterator It(ClassesToReinstanceOwned); It; ++It)
{
if (!ClassHasInstancesAsyncLoading(It->Key))
{
// Make sure to cleanup all properties that couldn't be destroyed in PurgeClass
It->Key->DestroyPropertiesPendingDestruction();
It->Value->DestroyPropertiesPendingDestruction();
It.RemoveCurrent();
}
}
// Preserve any pairs that are currently loading:
ClassesToReinstance = ObjectPtrWrap(MoveTemp(ClassesToReinstanceOwned));
}
else
{
// Make sure to cleanup all properties that couldn't be destroyed in PurgeClass
for (decltype(ClassesToReinstanceOwned)::TIterator It(ClassesToReinstanceOwned); It; ++It)
{
It->Key->DestroyPropertiesPendingDestruction();
It->Value->DestroyPropertiesPendingDestruction();
}
}
}
#if VERIFY_NO_STALE_CLASS_REFERENCES
FBlueprintSupport::ValidateNoRefsToOutOfDateClasses();
#endif
#if VERIFY_NO_BAD_SKELETON_REFERENCES
FBlueprintSupport::ValidateNoExternalRefsToSkeletons();
#endif
UE_LOGFMT(LogBlueprint, Display, "Finished reinstancing for {0} class(es):", NumClassesToReinstance);
UE_LOGFMT(LogBlueprint, Display, "\tTime Reinstancing: {0} ms.", TimeReinstancing * 1000.0);
}
bool FBlueprintCompilationManagerImpl::HasBlueprintsToCompile() const
{
return QueuedRequests.Num() != 0;
}
bool FBlueprintCompilationManagerImpl::IsGeneratedClassLayoutReady() const
{
return bGeneratedClassLayoutReady;
}
void FBlueprintCompilationManagerImpl::GetDefaultValue(const UClass* ForClass, const FProperty* Property, FString& OutDefaultValueAsString) const
{
if(!ForClass || !Property)
{
return;
}
if (ForClass->GetDefaultObject(false))
{
FBlueprintEditorUtils::PropertyValueToString(Property, (uint8*)ForClass->GetDefaultObject(false), OutDefaultValueAsString);
}
else
{
UBlueprint* BP = Cast<UBlueprint>(ForClass->ClassGeneratedBy);
if(ensure(BP))
{
const auto* OldCDO = OldCDOs.Find(BP);
if(OldCDO && *OldCDO)
{
const UClass* OldClass = (*OldCDO)->GetClass();
const FProperty* OldProperty = OldClass->FindPropertyByName(Property->GetFName());
if(OldProperty)
{
FBlueprintEditorUtils::PropertyValueToString(OldProperty, (uint8*)OldCDO->Get(), OutDefaultValueAsString);
}
}
}
}
}
void FBlueprintCompilationManagerImpl::VerifyNoQueuedRequests(const TArray<FCompilerData>& CurrentlyCompilingBPs)
{
if (QueuedRequests.Num() != 0)
{
FString QueuedBlueprints = TEXT("");
for (const FBPCompileRequest& Request: QueuedRequests)
{
QueuedBlueprints += Request.BPToCompile->GetName() + TEXT(" ");
}
FString CompilingBlueprints = TEXT("");
for (const FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
CompilingBlueprints += CompilerData.BP->GetName() + TEXT(" ");
}
ensureMsgf(false,
TEXT("Blueprints requested compilation while compiling other blueprints: %s\nWhile Compiling: %s"),
*QueuedBlueprints, *CompilingBlueprints);
}
}
void FBlueprintCompilationManagerImpl::SaveConstructionHashes(const TArray<FCompilerData>& CurrentlyCompilingBPs)
{
if( !UE::Kismet::BlueprintCompilationManager::Private::ConsoleVariables::bAllowFastReinstancing )
{
return;
}
// Don't do fast reinstancing for override serialization types, they
// are currently relying on reinstancing to propagate edits. This
// requires that we do slow reconstruction/replacement:
for (const FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
UBlueprint* BP = CompilerData.BP;
if(const UClass* OriginalClass = BP->GeneratedClass)
{
if( const UObject* CDO = OriginalClass->GetDefaultObject(false) )
{
if( FOverridableManager::Get().IsEnabled(CDO) )
{
return;
}
}
}
}
for (const FCompilerData& CompilerData : CurrentlyCompilingBPs)
{
UBlueprint* BP = CompilerData.BP;
if(const UClass* OriginalClass = BP->GeneratedClass)
{
if(OriginalClass->GetDefaultObject(false))
{
// if there are no instances, no need to calculate the class hash:
FBlake3Hash ConstructionHash = FBlueprintCompileReinstancer::CalculateConstructionHashIfNecessary(OriginalClass);
if(ConstructionHash != FBlake3Hash())
{
ClassHashes.Add(OriginalClass, ConstructionHash);
}
}
}
}
}
void FBlueprintCompilationManagerImpl::EnsureArchetypesArePostLoaded()
{
if(!UE::Private::bRunArchetypePostLoadEarly)
{
return;
}
// run postload on CDOs and archetypes:
int32 NumProcessed = 0;
TConstArrayView<FBPCompileRequest> QueuedRequestsView = QueuedRequests;
while(QueuedRequestsView.Num())
{
TArray<UObject*> ArchetypesToPreload;
GatherArchetypesRequiringPostload(QueuedRequestsView, ArchetypesToPreload);
if(ArchetypesToPreload.Num() == 0)
{
return;
}
const int32 NumRequests = QueuedRequests.Num();
for (UObject* Archetype : ArchetypesToPreload)
{
if(Archetype->HasAnyFlags(RF_NeedPostLoad))
{
// We can't use ConditionalPostLoad here because that will
// invoke UAnimBlueprintGeneratedClass::PostLoadDefaultObject,
// which has an implementation which assumes it's running
// at a specific point in compile. Doing so will cause problems
// with e.g. HuskyHusk_Sheild_AnimBlueprint_Child
Archetype->ClearFlags(RF_NeedPostLoad);
Archetype->PostLoad();
}
}
NumProcessed += QueuedRequestsView.Num();
const int32 NumNew = QueuedRequests.Num() - NumProcessed;
if(NumNew != 0)
{
QueuedRequestsView = TConstArrayView<FBPCompileRequest>(&QueuedRequests[NumProcessed], NumNew);
}
else
{
QueuedRequestsView = TConstArrayView<FBPCompileRequest>();
}
}
// @todo: record side effects in user postload, especially when there are other archetypes that are dependent
// upon the mutating archetype
}
void FBlueprintCompilationManagerImpl::GatherArchetypesRequiringPostload(TConstArrayView<FBPCompileRequest> FromRequests, TArray<UObject*>& OutArchetypesRequiringPostload)
{
for (const FBPCompileRequest& Request : FromRequests)
{
UClass* Class = Request.BPToCompile->GeneratedClass;
if(!Class)
{
continue;
}
TArray<UObject*> ArchetypeObjects;
GetObjectsOfClass(
Class,
ArchetypeObjects,
/*bIncludeDerivedClasses*/ false,
RF_NewerVersionExists|RF_Transient,
EInternalObjectFlags::Garbage);
// intentional append, inner objects will form a dag:
for(int32 I = 0; I < ArchetypeObjects.Num(); ++I)
{
GetObjectsWithOuter(
ArchetypeObjects[I],
ArchetypeObjects,
true,
RF_NewerVersionExists|RF_Transient,
EInternalObjectFlags::Garbage);
}
// filter out non-archetype instances, note that WidgetTrees and some component
// archetypes do not have RF_ArchetypeObject or RF_InheritableComponentTemplate so
// we simply detect that they are outered to a UClass or UBlueprint and assume that
// they are archetype objects in practice. Similarly if an object is outered to a CDO
// or archetype we assume that it's an archetype in practice:
ArchetypeObjects.RemoveAllSwap(
[](UObject* Obj)
{
const bool bNeedsPostLoad = Obj->HasAnyFlags(RF_WasLoaded) && Obj->HasAnyFlags(RF_NeedPostLoad);
if(!bNeedsPostLoad)
{
return true;
}
bool bIsArchetype =
Obj->HasAnyFlags(RF_ArchetypeObject|RF_InheritableComponentTemplate|RF_ClassDefaultObject)
|| Obj->GetTypedOuter<UClass>()
|| Obj->GetTypedOuter<UBlueprint>();
if(!bIsArchetype)
{
const UObject* OuterIter = Obj->GetOuter();
while(OuterIter)
{
if(OuterIter->HasAnyFlags(RF_ArchetypeObject|RF_InheritableComponentTemplate|RF_ClassDefaultObject))
{
bIsArchetype = true;
break;
}
OuterIter = OuterIter->GetOuter();
}
}
return !bIsArchetype;
}
);
OutArchetypesRequiringPostload.Append(ArchetypeObjects);
}
}
void FBlueprintCompilationManagerImpl::ReparentHierarchies(const TMap<UClass*, UClass*>& OldToNewClasses, EReparentClassOptions Options)
{
const bool bReplaceReferencesToOldClasses = (Options & EReparentClassOptions::ReplaceReferencesToOldClasses) != EReparentClassOptions::None;
TRACE_CPUPROFILER_EVENT_SCOPE(ReparentHierarchies);
TSet<UObject*> OldObjects;
// something has decided to replace instances of a class. We need to update all the children of those types:
TArray<UClass*> ClassesOrdered;
// Map used to distinguish between new classes and classes that need to be reinstanced (reparented) via a new reinstancer:
TMap<UClass*, UClass*> NewToOldClasses;
{
TSet<UClass*> Classes;
for (const TPair<UClass*, UClass*>& OldToNewClass : OldToNewClasses)
{
// classes with no CDO do not need to be reinstanced:
if (OldToNewClass.Key->GetDefaultObject(false) == nullptr)
{
continue;
}
Classes.Add(OldToNewClass.Value);
NewToOldClasses.Add(OldToNewClass.Value, OldToNewClass.Key);
TArray<UClass*> DerivedClasses;
// Just like when compiling we have to gather all children, not just immediate children. This is so that we can
// update things like the ClassConstructor pointer in case it changed:
GetDerivedClasses(OldToNewClass.Key, DerivedClasses);
for (UClass* DerivedClass : DerivedClasses)
{
if (DerivedClass->GetDefaultObject(false) == nullptr &&
DerivedClass->GetSparseClassData(EGetSparseClassDataMethod::ReturnIfNull) == nullptr)
{
// no CDO->no other instances->no need to reinstance..
// and we have no sparse classs data to manage...
continue;
}
if (DerivedClass->HasAnyClassFlags(CLASS_NewerVersionExists))
{
// Don't reinstance artifacts from classes that have been previously been reinstanced/trashed but aren't cleaned up yet.
// Include class in old objects that don't require reference replacement; otherwise, we might unintentionally invalidate
// parts of its linked structure (e.g. w/ a reinstanced base) which would no longer correlate to artifacts of the old subtype.
OldObjects.Add(DerivedClass);
continue;
}
if (OldToNewClasses.Find(DerivedClass) == nullptr)
{
Classes.Add(DerivedClass);
}
}
}
ClassesOrdered = Classes.Array();
}
// Order the classes we're about to reinstance by hierarchy depth. This will improve determinism
// and is as logical an order as I can come up with:
ClassesOrdered.Sort( [](UClass& A, UClass& B)->bool { return FBlueprintCompileReinstancer::ReinstancerOrderingFunction(&A, &B); } );
// create reinstancing jobs, no need to create a reinstancer when there is a new UClass* available (e.g. asset reload, hot reload):
TArray<FReinstancingJob> Reinstancers;
for (UClass* Class : ClassesOrdered)
{
UClass* const* OldClass = NewToOldClasses.Find(Class);
if (OldClass)
{
Reinstancers.Push( FReinstancingJob( TPair<UClass*, UClass*>(*OldClass, Class) ) );
}
else
{
Reinstancers.Push( FReinstancingJob(
TSharedPtr<FBlueprintCompileReinstancer>(
new FBlueprintCompileReinstancer(
Class,
EBlueprintCompileReinstancerFlags::AutoInferSaveOnCompile | EBlueprintCompileReinstancerFlags::AvoidCDODuplication
)
),
TSharedPtr<FKismetCompilerContext>()
) );
FReinstancingJob& ReinstancingJob = Reinstancers.Last();
ReinstancingJob.Reinstancer->SaveSparseClassData(Class);
ensure(ReinstancingJob.Reinstancer->DuplicatedClass && ReinstancingJob.Reinstancer->ClassToReinstance);
}
}
for (const FReinstancingJob& ReinstancingJob : Reinstancers)
{
if (ReinstancingJob.Reinstancer.IsValid())
{
ReinstancingJob.Reinstancer->TakeOwnershipOfSparseClassData(ReinstancingJob.OldToNew.Value);
}
}
// Reparent and Link - this is .. kind of pointless.. ReinstanceBatch should
// be doing this
TMap<UClass*, UClass*> OldClassToNewClassIncludingChildren = OldToNewClasses;
for (const FReinstancingJob& ReinstancingJob : Reinstancers)
{
UClass* ClassToReinstance = ReinstancingJob.OldToNew.Value;
// We only need to relink if we've reparented the class to a new type:
if (ReinstancingJob.Reinstancer.IsValid())
{
ClassToReinstance->ClassConstructor = nullptr;
ClassToReinstance->ClassVTableHelperCtorCaller = nullptr;
ClassToReinstance->CppClassStaticFunctions.Reset();
// check to see if we're a direct parent of one of the new classes:
UClass* const* NewParent = OldToNewClasses.Find(ClassToReinstance->GetSuperClass());
if(NewParent)
{
ClassToReinstance->SetSuperStruct(*NewParent);
}
ClassToReinstance->Bind();
ClassToReinstance->ClearFunctionMapsCaches();
ClassToReinstance->StaticLink(true);
}
OldClassToNewClassIncludingChildren.Add(ReinstancingJob.OldToNew.Key, ClassToReinstance);
}
// Reparenting done, reinstance the hierarchy and update archetypes:
TMap<UClass*, TMap<UObject*, UObject*>> OldToNewTemplates;
ReinstanceBatch(Reinstancers, OldClassToNewClassIncludingChildren, nullptr, &OldToNewTemplates);
// Reinstance (non archetype) instances
TMap<UClass*, UClass*> OldClassToNewClassDerivedTypes;
if (bReplaceReferencesToOldClasses)
{
OldClassToNewClassDerivedTypes = OldClassToNewClassIncludingChildren;
}
TSet<UObject*> ObjectsWithCachedArchetypes;
for (FReinstancingJob& ReinstancingJob : Reinstancers)
{
OldClassToNewClassDerivedTypes.Add(ReinstancingJob.OldToNew);
// Consolidate the set of objects with cached archetypes that we need for reinstancing
if (ReinstancingJob.ArchetypeInstancesWithCachedArchetypes.Num() > 0)
{
ObjectsWithCachedArchetypes.Append(MoveTemp(ReinstancingJob.ArchetypeInstancesWithCachedArchetypes));
ReinstancingJob.ArchetypeInstancesWithCachedArchetypes = {};
}
}
TGuardValue<std::atomic<bool>, bool> ReinstancingGuard(GIsReinstancing, true);
FReplaceInstancesOfClassParameters BatchOptions;
BatchOptions.bArchetypesAreUpToDate = true;
BatchOptions.bReplaceReferencesToOldClasses = bReplaceReferencesToOldClasses;
// Make sure we don't replace old instances that are in the *callers* old to new TMap!
for (TPair<UClass*, UClass*> OldToNew : OldClassToNewClassDerivedTypes)
{
ensure(OldToNew.Value->HasAnyClassFlags(CLASS_TokenStreamAssembled));
TArray< UObject* > OldObjectsOfType;
GetObjectsOfClass(OldToNew.Key, OldObjectsOfType);
for (UObject* Obj : OldObjectsOfType)
{
if (Obj->HasAnyFlags(RF_NewerVersionExists))
{
OldObjects.Add(Obj);
}
}
}
BatchOptions.ObjectsThatShouldUseOldStuff = &OldObjects;
BatchOptions.InstancesThatShouldUseOldClass = &OldObjects;
BatchOptions.bReplaceReferencesToOldCDOs = true;
BatchOptions.OldToNewTemplates = &OldToNewTemplates;
FBlueprintCompileReinstancer::BatchReplaceInstancesOfClass(OldClassToNewClassDerivedTypes, BatchOptions );
// Clean up cached archetype objects now that reinstancing is complete
if (ObjectsWithCachedArchetypes.Num() > 0)
{
FEditorCacheArchetypeManager& CacheManager = FEditorCacheArchetypeManager::Get();
for (UObject* ObjectWithCachedArchetype : ObjectsWithCachedArchetypes)
{
CacheManager.ResetCacheArchetype(ObjectWithCachedArchetype);
}
}
}
void FBlueprintCompilationManagerImpl::BuildDSOMap(UObject* OldObject, UObject* NewObject, const TMap<UClass*, UClass*>& InOldToNewClassMap, TMap<UObject*, UObject*>& OutOldToNewDSO)
{
// IsDefaultSubObject() unfortunately cannot be relied upon for archetypes, so we explicitly search for the flag:
TArray<UObject*> OldSubobjects;
ForEachObjectWithOuter(OldObject, [&OldSubobjects](UObject* Object)
{
if (Object->HasAnyFlags(RF_DefaultSubObject|RF_ArchetypeObject))
{
OldSubobjects.Add(Object);
}
}, /*bIncludeNestedObjects*/ false, /*ExclusionFlags*/ RF_MirroredGarbage);
for(UObject* OldSubobject : OldSubobjects)
{
UObject* NewSubobject = NewObject ? StaticFindObjectFast(UObject::StaticClass(), NewObject, OldSubobject->GetFName()) : nullptr;
// Don't map an archetype instance if we haven't yet reinstanced it (this can happen during batched compile/reinstance operations).
// Archetype instances (along with their own default subobjects) are added to the instance map elsewhere, when they are reinstanced.
if (NewSubobject && InOldToNewClassMap.Contains(NewSubobject->GetClass()))
{
continue;
}
// It may seem aggressive to ensure here, but we should always have a new version of the DSO. If that's
// not the case then some testing will need to be done on client of OutOldToNewDSO
// If the new archetype does not exist, do not add it into the map. Otherwise we might reset the value inside a undo/redo transaction
if (NewSubobject)
{
OutOldToNewDSO.Add(OldSubobject, NewSubobject);
}
BuildDSOMap(OldSubobject, NewSubobject, InOldToNewClassMap, OutOldToNewDSO);
}
}
void FBlueprintCompilationManagerImpl::ReinstanceBatch(TArray<FReinstancingJob>& Reinstancers, TMap< UClass*, UClass* >& InOutOldToNewClassMap, FUObjectSerializeContext* InLoadContext, TMap<UClass*, TMap<UObject*, UObject*>>* OldToNewTemplates /* = nullptr*/)
{
TGuardValue<std::atomic<bool>, bool> ReinstancingGuard(GIsReinstancing, true);
// This is only needed when using the legacy editor loader (ie: FLinkerLoad)
// Zen loader already uses FPlayInEditorLoadingScope
UE::Core::Private::FPlayInEditorLoadingScope PlayInEditorIDScope(INDEX_NONE);
const auto FilterOutOfDateClasses = [](TArray<UClass*>& ClassList)
{
// Old versions of classes can be abandoned, classes without CDOs have no instances and don't require reinstancing
// but they may still require reparenting..
ClassList.RemoveAllSwap( [](UClass* Class) { return Class->HasAnyClassFlags(CLASS_NewerVersionExists); } );
};
const auto HasChildren = [FilterOutOfDateClasses](UClass* InClass) -> bool
{
TArray<UClass*> ChildTypes;
GetDerivedClasses(InClass, ChildTypes, false);
FilterOutOfDateClasses(ChildTypes);
return ChildTypes.Num() > 0;
};
TSet<UClass*> ClassesToReparentSet;
TArray<UClass*> ClassesToReparent;
TSet<UClass*> ClassesToReinstance;
// Reinstancers may contain *part* of a class hierarchy, so we first need to reparent any child types that
// haven't already been reinstanced:
for (const FReinstancingJob& ReinstancingJob : Reinstancers)
{
UClass* OldClass = ReinstancingJob.OldToNew.Key;
if(!OldClass)
{
continue;
}
UClass* NewClass = ReinstancingJob.OldToNew.Value;
InOutOldToNewClassMap.Add(OldClass, NewClass);
if(!HasChildren(OldClass))
{
continue;
}
bool bParentLayoutChanged = !FStructUtils::TheSameLayout(OldClass, NewClass);
if(!bParentLayoutChanged)
{
// make sure uber graph didn't change, if present:
UBlueprintGeneratedClass* OldParent = Cast<UBlueprintGeneratedClass>(OldClass);
UBlueprintGeneratedClass* NewBPGC = Cast<UBlueprintGeneratedClass>(NewClass);
if(OldParent && NewBPGC && OldParent->UberGraphFunction)
{
bParentLayoutChanged = !FStructUtils::TheSameLayout(OldParent->UberGraphFunction, NewBPGC->UberGraphFunction);
}
}
if(bParentLayoutChanged)
{
// we need *all* derived types:
TArray<UClass*> ClassesToReinstanceList;
GetDerivedClasses(OldClass, ClassesToReinstanceList);
FilterOutOfDateClasses(ClassesToReinstanceList);
for(UClass* ClassToReinstance : ClassesToReinstanceList)
{
if(IsValid(ClassToReinstance))
{
if (ClassToReinstance->GetDefaultObject(false))
{
ClassesToReinstance.Add(ClassToReinstance);
}
else
{
bool bAlreadyInSet = false;
ClassesToReparentSet.Add(ClassToReinstance, &bAlreadyInSet);
if (!bAlreadyInSet)
{
ClassesToReparent.Add(ClassToReinstance);
}
}
}
}
}
else
{
// parent layout did not change, we can just relink the direct children:
TArray<UClass*> ClassesToReparentList;
GetDerivedClasses(OldClass, ClassesToReparentList, false);
FilterOutOfDateClasses(ClassesToReparentList);
for(UClass* ClassToReparent : ClassesToReparentList)
{
if(IsValid(ClassToReparent))
{
bool bAlreadyInSet = false;
ClassesToReparentSet.Add(ClassToReparent, &bAlreadyInSet);
if (!bAlreadyInSet)
{
ClassesToReparent.Add(ClassToReparent);
}
}
}
}
}
Algo::TopologicalSort(ClassesToReparent, [&InOutOldToNewClassMap](UClass* Class)
{
TArray<UClass*> Dependencies;
UClass* CurrentClass = Class;
while (UClass* SuperClass = CurrentClass->GetSuperClass())
{
if(UClass** NewSuperClass = InOutOldToNewClassMap.Find(SuperClass))
{
Dependencies.Add(*NewSuperClass);
}
else
{
Dependencies.Add(SuperClass);
}
CurrentClass = SuperClass;
}
return Dependencies;
});
for(UClass* Class : ClassesToReparent)
{
UClass** NewParent = InOutOldToNewClassMap.Find(Class->GetSuperClass());
check(NewParent && *NewParent);
Class->SetSuperStruct(*NewParent);
Class->Bind();
Class->StaticLink(true);
Class->ClassFlags &= ~CLASS_ReplicationDataIsSetUp;
Class->SetUpRuntimeReplicationData();
}
// make new hierarchy
for(UClass* Class : ClassesToReinstance)
{
Reinstancers.Emplace(
FReinstancingJob (
TSharedPtr<FBlueprintCompileReinstancer>(
new FBlueprintCompileReinstancer(
Class,
EBlueprintCompileReinstancerFlags::AutoInferSaveOnCompile|EBlueprintCompileReinstancerFlags::AvoidCDODuplication
)
),
TSharedPtr<FKismetCompilerContext>()
)
);
// make sure we have the newest parent now that CDO has been moved to duplicate class:
TSharedPtr<FBlueprintCompileReinstancer>& NewestReinstancer = Reinstancers.Last().Reinstancer;
ensure(NewestReinstancer->DuplicatedClass && NewestReinstancer->ClassToReinstance);
UClass* SuperClass = NewestReinstancer->ClassToReinstance->GetSuperClass();
if(ensure(SuperClass))
{
if(SuperClass->HasAnyClassFlags(CLASS_NewerVersionExists))
{
NewestReinstancer->ClassToReinstance->SetSuperStruct(SuperClass->GetAuthoritativeClass());
}
}
// relink the new class:
NewestReinstancer->ClassToReinstance->Bind();
NewestReinstancer->ClassToReinstance->StaticLink(true);
NewestReinstancer->ClassToReinstance->ClassFlags &= ~CLASS_ReplicationDataIsSetUp;
NewestReinstancer->ClassToReinstance->SetUpRuntimeReplicationData();
}
// run UpdateBytecodeReferences:
{
TSet<UBlueprint*> DependentBPs;
TMap<FFieldVariant, FFieldVariant> FieldMappings;
for (FReinstancingJob& ReinstancingJob : Reinstancers)
{
if (ReinstancingJob.OldToNew.Key)
{
InOutOldToNewClassMap.Add(ReinstancingJob.OldToNew.Key, ReinstancingJob.OldToNew.Value);
}
if (ReinstancingJob.Reinstancer.IsValid())
{
UBlueprint* CompiledBlueprint = UBlueprint::GetBlueprintFromClass(ReinstancingJob.OldToNew.Value);
ReinstancingJob.Reinstancer->UpdateBytecodeReferences(DependentBPs, FieldMappings);
}
}
FBlueprintCompileReinstancer::FinishUpdateBytecodeReferences(DependentBPs, FieldMappings);
}
// Now we can update templates and archetypes - note that we don't look for direct references to archetypes - doing
// so is very expensive and it will be much faster to directly update anything that cares to cache direct references
// to an archetype here (e.g. a UClass::ClassDefaultObject member):
TArray<FReinstancingJob*> ReinstancersPtr;
ReinstancersPtr.Reset(Reinstancers.Num());
for (FReinstancingJob& ReinstancingJob : Reinstancers)
{
ReinstancersPtr.Add(&ReinstancingJob);
}
Algo::TopologicalSort(ReinstancersPtr, [&Reinstancers](FReinstancingJob* ReinstancingJob)
{
TArray<FReinstancingJob*> Dependencies;
auto AddDependentClass = [&Dependencies,&Reinstancers](UClass* DependentClass)
{
if (FReinstancingJob* ReinstancingJob = Reinstancers.FindByPredicate([DependentClass](FReinstancingJob& ReinstancingJob) { return ReinstancingJob.OldToNew.Key == DependentClass;}))
{
Dependencies.Add(ReinstancingJob);
}
};
if (UClass* OldClass = ReinstancingJob->OldToNew.Key)
{
AddDependentClass(OldClass->GetSuperClass());
if (const UObject* CDO = OldClass->GetDefaultObject(false))
{
TArray<UObject*> ContainedOldObjects;
GetObjectsWithOuter(CDO, ContainedOldObjects);
for (const UObject* OldObject : ContainedOldObjects)
{
AddDependentClass(OldObject->GetClass());
}
}
}
return Dependencies;
});
// 2. Update Sparse Class Data
for (const FReinstancingJob* ReinstancingJobPtr : ReinstancersPtr)
{
TSharedPtr<FBlueprintCompileReinstancer> const& Reinstancer = ReinstancingJobPtr->Reinstancer;
if (!Reinstancer.IsValid())
{
continue; // no reinstancer, we're not responsible for recreating this class (e.g. it came from verse compile or asset reload)
}
// if the new class already has a sparse class data, that indicates
// it was generated by its compiler and we can discard the old data:
UClass* NewClass = ReinstancingJobPtr->OldToNew.Value;
if (!NewClass || NewClass->GetSparseClassData(EGetSparseClassDataMethod::ReturnIfNull) != nullptr)
{
continue;
}
// New class has not created a sparse class data, if the old class had one copy it over:
Reinstancer->PropagateSparseClassDataToNewClass(NewClass);
}
// 3. Copy defaults from old CDO - CDO may be missing if this class was reinstanced and relinked here,
// so use GetDefaultObject(true):
for (const FReinstancingJob* ReinstancingJobPtr : ReinstancersPtr)
{
const FReinstancingJob& ReinstancingJob = *ReinstancingJobPtr;
UObject* OldCDO = nullptr;
UClass* OldClass = ReinstancingJob.OldToNew.Key;
if (OldClass)
{
OldCDO = OldClass->GetDefaultObject(false);
if (OldCDO && ReinstancingJob.Reinstancer.IsValid())
{
// Object using overridable serialization need to use delta serialization for it do work appropriately
// Eventually we should do this to all BP classes CDO no matter what
const bool bUsingOverrideSerialization = FOverridableManager::Get().IsEnabled(OldCDO);
const bool bUseDeltaSerialization = bUsingOverrideSerialization ? true : (ReinstancingJob.Reinstancer.IsValid() ? ReinstancingJob.Reinstancer->bUseDeltaSerializationToCopyProperties : false);
UClass* NewClass = ReinstancingJob.OldToNew.Value;
// We do not expect the CDO to be already created at this point.
// Who ever bother to create it before didn't look if the parent was ready or not.
if (NewClass->GetDefaultObject(false) != nullptr && bUsingOverrideSerialization)
{
// Override serialization does not currently want to use any CDO created before this
// point. I do not have a test case justifying this need, but renaming the cdo here
// causes various regressions for existing blueprints, because their compilers have put
// needed data on the generated CDO. If we want to use override serialization broadly
// we will have to address these shortcomings (likely by ordering compilation itself,
// rather than reinstancing, more carefully). Discarding the CDO here is also wasteful
// - if we cannot use the objects, why create them at all?
NewClass->GetDefaultObject(false)->Rename(nullptr, GetTransientPackage(), REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional);
NewClass->SetDefaultObject(nullptr);
}
UObject* NewCDO = NewClass->GetDefaultObject(true);
UBlueprint* CompiledBlueprint = UBlueprint::GetBlueprintFromClass(OldClass);
if (CompiledBlueprint && CompiledBlueprint->bIsRegeneratingOnLoad)
{
// This is a catch-all for any deferred dependencies that didn't get resolved during loading/linking (that system is not bulletproof for complex circular dependencies)
FBlueprintSupport::RepairDeferredDependenciesInObject(OldCDO);
}
TMap<UObject*, UObject*> CreatedInstanceMap;
TArray< TTuple<UObject*, UObject*>> OrderedListOfObjectToCopy;
FBlueprintCompileReinstancer::PreCreateSubObjectsForReinstantiation(InOutOldToNewClassMap, OldCDO, NewCDO, CreatedInstanceMap, nullptr, &OrderedListOfObjectToCopy);
// We only need to copy properties of the pre-created instances, the rest of the default sub object is done inside the UEditorEngine::CopyPropertiesForUnrelatedObjects
TMap<UObject*, UObject*> OldToNewInstanceMap(CreatedInstanceMap);
if (TMap<UObject*, UObject*>* OldToNewTemplateMap = OldToNewTemplates ? OldToNewTemplates->Find(OldClass->GetSuperClass()) : nullptr)
{
OldToNewInstanceMap.Append(*OldToNewTemplateMap);
}
for(const auto& Pair : OrderedListOfObjectToCopy)
{
FBlueprintCompileReinstancer::CopyPropertiesForUnrelatedObjects(Pair.Key, Pair.Value, /*bClearExternalReferences*/true, bUseDeltaSerialization, /*bOnlyHandleDirectSubObjects*/true, &OldToNewInstanceMap, &InOutOldToNewClassMap);
}
if (ReinstancingJob.Compiler.IsValid())
{
ReinstancingJob.Compiler->PropagateValuesToCDO(NewCDO, OldCDO);
}
// PropagateValuesToCDO may 'reuse' objects from the old CDO (e.g.
// FDisplayClusterConfiguratorKismetCompilerContext::CopyTermDefaultsToDefaultObject,
// others that make use of SaveSubObjectsFromCleanAndSanitizeClass)
// detect this and avoid creating mappings from 'old to new' when
// an object has been moved to the new CDO:
if (OldToNewTemplates && !OldToNewInstanceMap.IsEmpty())
{
TMap<UObject*, UObject*>& OldToNewMap = OldToNewTemplates->FindOrAdd(OldClass);
for(const TPair<UObject*, UObject*> OldToNew : OldToNewInstanceMap)
{
// dead objects are definitely old, as are objects that aren't within the NewCDO
// hopefully there is no user logic attempting further subobject reuse after
// PropagateValuesToCDO
if(!IsValid(OldToNew.Key) || !OldToNew.Key->IsIn(NewCDO) )
{
OldToNewMap.Add(OldToNew.Key, OldToNew.Value);
}
}
}
}
}
if (UBlueprintGeneratedClass* BPGClass = Cast<UBlueprintGeneratedClass>(ReinstancingJob.OldToNew.Value))
{
BPGClass->UpdateCustomPropertyListForPostConstruction();
// patch new class and cdo into linker table:
if (ReinstancingJob.Reinstancer.IsValid())
{
UBlueprint* CurrentBP = Cast<UBlueprint>(BPGClass->ClassGeneratedBy);
UObject* NewCDO = nullptr;
FLinkerLoad* CurrentLinker = nullptr;
if(CurrentBP)
{
NewCDO = CurrentBP->GeneratedClass->GetDefaultObject(false);
CurrentLinker = CurrentBP->GetLinker();
}
else if(ReinstancingJob.Reinstancer->ClassToReinstance)
{
NewCDO = ReinstancingJob.Reinstancer->ClassToReinstance->GetDefaultObject(false);
CurrentLinker = ReinstancingJob.Reinstancer->ClassToReinstance->GetLinker();
}
if (CurrentLinker)
{
int32 OldCDOIndex = INDEX_NONE;
int32 OldClassIndex = INDEX_NONE;
for (int32 i = 0; i < CurrentLinker->ExportMap.Num(); i++)
{
FObjectExport& ThisExport = CurrentLinker->ExportMap[i];
// In addition to checking for RF_ClassDefaultObject, we also need to check if the object names
// match to for Control Rig BP use case
//
// In a normal Blueprint, there is usually just 1 CDO in the package, so name checking was not necessary.
//
// But in a Control Rig BP, multiple CDO can be in a package because custom UClasses are used to describe
// the layout of RigVM Memory Storage. These UClasses and their CDO are serialzed with the package as well.
// Thus, we have to ensure that the export is not just a CDO, but also a CDO with the matching name
if ((ThisExport.ObjectFlags & RF_ClassDefaultObject) && (ThisExport.ObjectName == NewCDO->GetFName()))
{
OldCDOIndex = i;
}
// Likewise we have to search by name for the class.
if (ThisExport.ObjectName == BPGClass->GetFName()
&& ThisExport.OuterIndex.IsNull() /* The class has to be a direct subobject of the package */
)
{
OldClassIndex = i;
}
}
if (OldCDOIndex != INDEX_NONE)
{
CurrentLinker->PRIVATE_PatchNewObjectIntoExport(OldCDOIndex, NewCDO, InLoadContext);
NewCDO->CheckDefaultSubobjects();
}
if (OldClassIndex != INDEX_NONE)
{
CurrentLinker->PRIVATE_PatchNewObjectIntoExport(OldClassIndex, BPGClass, InLoadContext);
}
}
}
}
}
TMap<UObject*, UObject*> OldArchetypeToNewArchetype;
// 4. Update any remaining instances that are tagged as RF_ArchetypeObject or RF_InheritableComponentTemplate -
// we may need to do further sorting to ensure that interdependent archetypes are initialized correctly:
TSet<UObject*> ArchetypeReferencers;
// The transaction buffer could reference archetypes, and tag serialization
// will be simpler if we update the instance:
if (IsInGameThread() && GUnrealEd && GUnrealEd->Trans)
{
ArchetypeReferencers.Add(GUnrealEd->Trans);
}
for (FReinstancingJob* ReinstancingJobPtr : ReinstancersPtr)
{
FReinstancingJob& ReinstancingJob = *ReinstancingJobPtr;
UClass* OldClass = ReinstancingJob.OldToNew.Key;
if (OldClass)
{
SCOPED_LOADTIMER_ASSET_TEXT(*WriteToString<256>(TEXT("Reinstancing "), *GetPathNameSafe(ReinstancingJob.OldToNew.Value)));
UClass* NewClass = ReinstancingJob.OldToNew.Value;
if (NewClass &&
OldClass->GetDefaultObject(false) &&
NewClass->GetDefaultObject(false) &&
OldClass->GetDefaultObject(false) != NewClass->GetDefaultObject(false))
{
OldArchetypeToNewArchetype.Add(OldClass->GetDefaultObject(false), NewClass->GetDefaultObject(false));
// also map old *default* subobjects to new default subobjects:
BuildDSOMap(OldClass->GetDefaultObject(false), NewClass->GetDefaultObject(false), InOutOldToNewClassMap, OldArchetypeToNewArchetype);
}
TArray<UObject*> ArchetypeObjects;
GetObjectsOfClass(OldClass, ArchetypeObjects, /*bIncludeDerivedClasses*/false, RF_NoFlags, EInternalObjectFlags::Garbage);
// filter out non-archetype instances, note that WidgetTrees and some component
// archetypes do not have RF_ArchetypeObject or RF_InheritableComponentTemplate so
// we simply detect that they are outered to a UBPGC or UBlueprint and assume that
// they are archetype objects in practice:
ArchetypeObjects.RemoveAllSwap(
[&InOutOldToNewClassMap](UObject* Obj)
{
bool bIsArchetype =
Obj->HasAnyFlags(RF_ArchetypeObject|RF_InheritableComponentTemplate)
|| (FOverridableManager::Get().IsEnabled(Obj) && Obj->GetOutermostObject()->HasAnyFlags(RF_ClassDefaultObject))
|| Obj->GetTypedOuter<UBlueprintGeneratedClass>()
|| Obj->GetTypedOuter<UBlueprint>();
// remove if this is not an archetype or its already in the transient package, note
// that things that are not directly outered to the transient package will be
// 'reinst'd', this is specifically to handle components, which need to be up to date
// on the REINST_ actor class:
// Also no need to reinstantiate if our outer is also being reinstantiated as an archetype.
return !bIsArchetype ||
Obj->GetOutermost() == GetTransientPackage() ||
Obj->HasAnyFlags(RF_NewerVersionExists) ||
InOutOldToNewClassMap.Find(Obj->GetOuter()->GetClass());
}
);
// for each archetype:
for (UObject* Archetype : ArchetypeObjects)
{
ReinstancingJob.OldArchetypeObjects.Add(Archetype);
// make sure we fix up references in the owner:
{
UObject* Iter = Archetype->GetOuter();
while(Iter)
{
UBlueprintGeneratedClass* IterAsBPGC = Cast<UBlueprintGeneratedClass>(Iter);
UBlueprint* IterAsBP = Cast<UBlueprint>(Iter);
if(Iter->HasAnyFlags(RF_ClassDefaultObject)
|| (IterAsBPGC && !IterAsBPGC->HasAnyClassFlags(CLASS_NewerVersionExists))
|| IterAsBP)
{
ArchetypeReferencers.Add(Iter);
// Component templates are referenced by the UBlueprint, but are outered to the UBPGC. Both
// will need to be updated. Realistically there is no reason to refernce these in the
// UBlueprint, so there is no reason to generalize this behavior:
if(IterAsBPGC)
{
ArchetypeReferencers.Add(IterAsBPGC->ClassGeneratedBy);
IterAsBP = Cast<UBlueprint>(IterAsBPGC->ClassGeneratedBy);
}
if (IterAsBP)
{
if (IterAsBP->SkeletonGeneratedClass)
{
ArchetypeReferencers.Add(IterAsBP->SkeletonGeneratedClass);
}
}
// this handles nested subobjects:
if (FOverridableManager::Get().IsEnabled(Archetype))
{
ForEachObjectWithOuter(Iter, [Archetype, &ArchetypeReferencers](UObject* SubObject)
{
if (SubObject != Archetype && !SubObject->IsIn(Archetype))
{
ArchetypeReferencers.Add(SubObject);
}
});
}
else
{
TArray<UObject*> ContainedObjects;
GetObjectsWithOuter(Iter, ContainedObjects);
ArchetypeReferencers.Append(ContainedObjects);
}
}
Iter = Iter->GetOuter();
}
}
}
// Sort all archetype between themselves, as one might depends on an other
// This happens when the class containing the archetype has derived classes.
Algo::TopologicalSort(ReinstancingJob.OldArchetypeObjects, [&ArchetypeObjects](const FReinstancingJob::FArchetypeInfo& OldArchetypeInfo)
{
TArray<UObject*> Dependencies;
if (OldArchetypeInfo.ArchetypeTemplate && !OldArchetypeInfo.ArchetypeTemplate->HasAnyFlags(RF_ClassDefaultObject))
{
if(ArchetypeObjects.Contains(OldArchetypeInfo.ArchetypeTemplate))
{
Dependencies.Add(OldArchetypeInfo.ArchetypeTemplate);
}
else
{
UE_LOG(LogBlueprint, Warning, TEXT("Expecting the template object (%s) of archetype (%s) to already be in the list of archetypes"), *GetNameSafe(OldArchetypeInfo.ArchetypeTemplate), *GetNameSafe(OldArchetypeInfo.Archetype));
}
}
return Dependencies;
});
}
}
FEditorCacheArchetypeManager& CacheManager = FEditorCacheArchetypeManager::Get();
// This loop finishes the reinstancing of archetypes after the entire Outer hierarchy has been updated with new instances:
for (FReinstancingJob* ReinstancingJobPtr : ReinstancersPtr)
{
FReinstancingJob& ReinstancingJob = *ReinstancingJobPtr;
UClass* OldClass = ReinstancingJob.OldToNew.Key;
if(OldClass)
{
SCOPED_LOADTIMER_ASSET_TEXT(*WriteToString<256>(TEXT("FinishReinstancing "), *GetPathNameSafe(ReinstancingJob.OldToNew.Value)));
UClass* NewClass = ReinstancingJob.OldToNew.Value;
TMap<UObject*, UObject*>* OldToNewTemplatesForClass = OldToNewTemplates ? &OldToNewTemplates->FindOrAdd(OldClass) : nullptr;
for(const FReinstancingJob::FArchetypeInfo& OldArchetypeInfo : ReinstancingJob.OldArchetypeObjects)
{
UObject* OldInstance = OldArchetypeInfo.Archetype;
// move aside:
FName OriginalName = OldInstance->GetFName();
UObject* OriginalOuter = OldInstance->GetOuter();
EObjectFlags OriginalFlags = OldInstance->GetFlags();
// We need to cache the archetype of the instances of this archetype
// as it will not be possible to get them afterwards as it gets renamed
// These cached archetypes will not be updated if they were set earlier
// Note we might need these archetypes to reinstance non-archetypes later
TArray<UObject*> ArchetypeInstances;
OldInstance->GetArchetypeInstances(ArchetypeInstances);
FBlueprintCompileReinstancer::CacheArchetypes(
ArchetypeInstances,
CacheManager,
ReinstancingJob.ArchetypeInstancesWithCachedArchetypes);
UObject* Template = nullptr;
if (UObject** NewArchetypeTemplate = OldArchetypeInfo.ArchetypeTemplate ? OldArchetypeToNewArchetype.Find(OldArchetypeInfo.ArchetypeTemplate) : nullptr)
{
Template = *NewArchetypeTemplate;
}
FObjectInstancingGraph InstancingGraph;
const bool bUseInstancingGraph = FBlueprintCompileReinstancer::PrePopulateInstancingGraphForArchetype(InstancingGraph, OldInstance, Template, &OldArchetypeToNewArchetype);
UObject* Destination = GetTransientOuterForRename(OldInstance->GetClass());
OldInstance->Rename(
nullptr,
// destination - this is the important part of this call. Moving the object
// out of the way so we can reuse its name:
Destination,
// Rename options:
REN_DoNotDirty | REN_DontCreateRedirectors | REN_AllowPackageLinkerMismatch);
// reconstruct
FMakeClassSpawnableOnScope TemporarilySpawnable(NewClass);
const EObjectFlags FlagMask = RF_Public | RF_ArchetypeObject | RF_Transactional | RF_Transient | RF_TextExportTransient | RF_InheritableComponentTemplate | RF_Standalone; //TODO: what about RF_RootSet?
UObject* NewArchetype = NewObject<UObject>(OriginalOuter, NewClass, OriginalName, OriginalFlags & FlagMask, Template, false, bUseInstancingGraph ? &InstancingGraph : nullptr);
OldArchetypeToNewArchetype.Add(OldInstance, NewArchetype);
if (OldToNewTemplatesForClass)
{
OldToNewTemplatesForClass->Add(OldInstance, NewArchetype);
}
// also map old *default* subobjects to new default subobjects:
BuildDSOMap(OldInstance, NewArchetype, InOutOldToNewClassMap, OldArchetypeToNewArchetype);
ArchetypeReferencers.Add(NewArchetype);
FLinkerLoad::PRIVATE_PatchNewObjectIntoExport(OldInstance, NewArchetype);
// NewArchetype may be null in the case of deleted or EditorOnly (in -game) DSOs:
if(NewArchetype)
{
// The new object hierarchy has been created, all of the old instances are in the transient package and new
// ones have taken their place. Reference members will mostly be pointing at *old* instances, and will get fixed
// up below:
const bool bUseDeltaSerialization = ReinstancingJob.Reinstancer.IsValid() ? ReinstancingJob.Reinstancer->bUseDeltaSerializationToCopyProperties : false;
TMap<UObject*, UObject*> CreatedInstanceMap;
TArray< TTuple<UObject*, UObject*>> OrderedListOfObjectToCopy;
FBlueprintCompileReinstancer::PreCreateSubObjectsForReinstantiation(InOutOldToNewClassMap, OldInstance, NewArchetype, CreatedInstanceMap, nullptr, &OrderedListOfObjectToCopy);
// We only need to copy properties of the pre-created instances, the rest of the default sub object is done inside the UEditorEngine::CopyPropertiesForUnrelatedObjects
TMap<UObject*, UObject*> OldToNewInstanceMap(CreatedInstanceMap);
if (TMap<UObject*, UObject*>* OldToNewTemplateMap = OldToNewTemplates ? OldToNewTemplates->Find(OldClass->GetSuperClass()) : nullptr)
{
OldToNewInstanceMap.Append(*OldToNewTemplateMap);
}
for (const TTuple<UObject*, UObject*>& Pair : OrderedListOfObjectToCopy)
{
FBlueprintCompileReinstancer::CopyPropertiesForUnrelatedObjects(Pair.Key, Pair.Value, /*bClearExternalReferences*/false, bUseDeltaSerialization, /*bOnlyHandleDirectSubObjects*/true, &OldToNewInstanceMap, &InOutOldToNewClassMap);
}
if (OldToNewTemplates && !OldToNewInstanceMap.IsEmpty())
{
OldToNewTemplates->FindOrAdd(OldClass).Append(OldToNewInstanceMap);
}
// Ensure that our archetype instance mapping is up to date, as this will be used for reference replacement below. Note that this
// might be populated by BuildDSO() initially to contain instance mapping pairs for which the new instance won't be valid until we
// run through each of the PreCreateSubObjectsForReinstantiation() and subsequent CopyPropertiesForUnrelatedObjects() steps above.
for (const TPair<UObject*, UObject*>& Pair : OldToNewInstanceMap)
{
if (UObject** NewArchetypeMapping = OldArchetypeToNewArchetype.Find(Pair.Key))
{
*NewArchetypeMapping = Pair.Value;
}
}
OldInstance->RemoveFromRoot();
OldInstance->MarkAsGarbage();
}
}
}
}
// Reassociate relevant property bags
UE::FPropertyBagRepository::Get().ReassociateObjects(OldArchetypeToNewArchetype);
// 5. update known references to archetypes (e.g. component templates, WidgetTree). We don't want to run the normal
// reference finder to update these because searching the entire object graph is time consuming. Instead we just replace
// all references in our UBlueprint and its generated class:
for (const FReinstancingJob* ReinstancingJobPtr : ReinstancersPtr)
{
const FReinstancingJob& ReinstancingJob = *ReinstancingJobPtr;
ArchetypeReferencers.Add(ReinstancingJob.OldToNew.Value);
ArchetypeReferencers.Add(ReinstancingJob.OldToNew.Value->ClassGeneratedBy);
if(!ReinstancingJob.Reinstancer.IsValid())
{
continue;
}
if(UBlueprint* BP = Cast<UBlueprint>(ReinstancingJob.OldToNew.Value->ClassGeneratedBy))
{
// The only known way to cause this ensure to trip is to enqueue blueprints for compilation
// while blueprints are already compiling:
if( ensure(BP->SkeletonGeneratedClass) )
{
ArchetypeReferencers.Add(BP->SkeletonGeneratedClass);
}
for(const TWeakObjectPtr<UBlueprint>& Dependency : BP->CachedDependencies)
{
if (UBlueprint* DependencyBP = Dependency.Get())
{
ArchetypeReferencers.Add(DependencyBP);
}
}
}
}
// Data loading may be in flight, lets immediately patch existing redirectors -
// we may want to search the entire graph some day, but that would be expensive:
for (TObjectIterator<UObjectRedirector> Itr; Itr; ++Itr)
{
if (Itr->DestinationObject &&
Itr->DestinationObject->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject))
{
ArchetypeReferencers.Add(*Itr);
}
}
for(UObject* ArchetypeReferencer : ArchetypeReferencers)
{
// Do not bother trying to replace references in referencers that are not valid
if (IsValid(ArchetypeReferencer))
{
FArchiveReplaceObjectRef<UObject> ReplaceInCDOAr(ArchetypeReferencer, OldArchetypeToNewArchetype);
}
}
}
/*
This function completely replaces the 'skeleton only' compilation pass in the Kismet compiler. Long
term that code path will be removed and clients will be redirected to this function.
Notes to maintainers: any UObject created here and outered to the resulting class must be marked as transient
or you will create a cook error!
*/
UClass* FBlueprintCompilationManagerImpl::FastGenerateSkeletonClass(UBlueprint* BP, FKismetCompilerContext& CompilerContext, bool bIsSkeletonOnly, TArray<FSkeletonFixupData>& OutSkeletonFixupData)
{
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
FCompilerResultsLog MessageLog;
UClass* ParentClass = BP->ParentClass;
if(ParentClass == nullptr)
{
return nullptr;
}
if(ParentClass->ClassGeneratedBy)
{
if(UBlueprint* ParentBP = Cast<UBlueprint>(ParentClass->ClassGeneratedBy))
{
if(ParentBP->SkeletonGeneratedClass)
{
ParentClass = ParentBP->SkeletonGeneratedClass;
}
}
}
UBlueprintGeneratedClass* Ret = nullptr;
UBlueprintGeneratedClass* OriginalNewClass = CompilerContext.NewClass;
FString SkelClassName = FString::Printf(TEXT("SKEL_%s_C"), *BP->GetName());
// Temporarily set the compile type to indicate that we're generating the skeleton class.
TGuardValue<EKismetCompileType::Type> GuardCompileType(CompilerContext.CompileOptions.CompileType, EKismetCompileType::SkeletonOnly);
if (BP->SkeletonGeneratedClass == nullptr)
{
// This might exist in the package because we are being reloaded in place
BP->SkeletonGeneratedClass = FindObject<UBlueprintGeneratedClass>(BP->GetOutermost(), *SkelClassName);
}
if (BP->SkeletonGeneratedClass == nullptr)
{
CompilerContext.SpawnNewClass(SkelClassName);
Ret = CompilerContext.NewClass;
Ret->SetFlags(RF_Transient);
CompilerContext.NewClass = OriginalNewClass;
}
else
{
Ret = CastChecked<UBlueprintGeneratedClass>(*(BP->SkeletonGeneratedClass));
// If we're changing the parent class, first validate variable names against the inherited set to avoid collisions when we create properties.
if (Ret->GetSuperClass() != ParentClass)
{
CompilerContext.ValidateVariableNames();
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
CompilerContext.CleanAndSanitizeClass(Ret, MutableView(Ret->ClassDefaultObject));
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
Ret->ClassGeneratedBy = BP;
// This is a version of PrecompileFunction that does not require 'terms' and graph cloning:
const auto MakeFunction = [Ret, ParentClass, Schema, BP, &MessageLog, &OutSkeletonFixupData]
( FName FunctionNameFName,
UField::FLinkedListBuilder& UFieldListBuilder,
EFunctionFlags InFunctionFlags,
const TArray<UK2Node_FunctionResult*>& ReturnNodes,
const TArray<UEdGraphPin*>& InputPins,
bool bIsStaticFunction,
bool bForceArrayStructRefsConst,
UFunction* SignatureOverride) -> UFunction*
{
if(!ensure(FunctionNameFName != FName())
|| FindObjectFast<UField>(Ret, FunctionNameFName))
{
return nullptr;
}
UFunction* NewFunction = NewObject<UFunction>(Ret, FunctionNameFName, RF_Public|RF_Transient);
Ret->AddFunctionToFunctionMap(NewFunction, NewFunction->GetFName());
UFieldListBuilder.AppendNoTerminate(*NewFunction);
if(bIsStaticFunction)
{
NewFunction->FunctionFlags |= FUNC_Static;
}
UFunction* ParentFn = ParentClass->FindFunctionByName(NewFunction->GetFName());
// Set the parent function prior to searching for a corresponding interface class function. This matches what
// is done in the full path (@see FKismetCompilerContext::PrecompileFunction). It is ok for this to be NULL.
NewFunction->SetSuperStruct(ParentFn);
if(ParentFn == nullptr)
{
// check for function in implemented interfaces:
for(const FBPInterfaceDescription& BPID : BP->ImplementedInterfaces)
{
// we only want the *skeleton* version of the function:
UClass* InterfaceClass = BPID.Interface;
// We need to null check because FBlueprintEditorUtils::ConformImplementedInterfaces won't run until
// after the skeleton classes have been generated:
if(InterfaceClass)
{
if(UBlueprint* Owner = Cast<UBlueprint>(InterfaceClass->ClassGeneratedBy))
{
if( ensure(Owner->SkeletonGeneratedClass) )
{
InterfaceClass = Owner->SkeletonGeneratedClass;
}
}
if(UFunction* ParentInterfaceFn = InterfaceClass->FindFunctionByName(NewFunction->GetFName()))
{
ParentFn = ParentInterfaceFn;
break;
}
}
}
}
FField::FLinkedListBuilder PropertyListBuilder(&NewFunction->ChildProperties);
// params:
if(ParentFn || SignatureOverride)
{
UFunction* SignatureFn = ParentFn ? ParentFn : SignatureOverride;
NewFunction->FunctionFlags |= (SignatureFn->FunctionFlags & (FUNC_FuncInherit | FUNC_Public | FUNC_Protected | FUNC_Private | FUNC_BlueprintPure));
for (TFieldIterator<FProperty> PropIt(SignatureFn); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
{
FProperty* ClonedParam = CastField<FProperty>(FField::Duplicate(*PropIt, NewFunction, PropIt->GetFName(), RF_AllFlags, EInternalObjectFlags_AllFlags & ~(EInternalObjectFlags::Native)));
check(ClonedParam);
ClonedParam->PropertyFlags |= CPF_BlueprintVisible|CPF_BlueprintReadOnly;
ClonedParam->Next = nullptr;
PropertyListBuilder.AppendNoTerminate(*ClonedParam);
}
FMetaData::CopyMetadata(SignatureFn, NewFunction);
}
else
{
NewFunction->FunctionFlags |= InFunctionFlags;
for(UEdGraphPin* Pin : InputPins)
{
if(Pin->Direction == EEdGraphPinDirection::EGPD_Output && !Schema->IsExecPin(*Pin) && Pin->ParentPin == nullptr && Pin->GetFName() != UK2Node_Event::DelegateOutputName)
{
// Reimplementation of FKismetCompilerContext::CreatePropertiesFromList without dependence on 'terms'
FProperty* Param = FKismetCompilerUtilities::CreatePropertyOnScope(NewFunction, Pin->PinName, Pin->PinType, Ret, CPF_BlueprintVisible|CPF_BlueprintReadOnly, Schema, MessageLog);
if(Param)
{
Param->SetFlags(RF_Transient);
Param->PropertyFlags |= CPF_Parm;
if(Pin->PinType.bIsReference)
{
Param->PropertyFlags |= CPF_ReferenceParm | CPF_OutParm;
}
if(Pin->PinType.bIsConst || (bForceArrayStructRefsConst && (Pin->PinType.IsArray() || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct) && Pin->PinType.bIsReference))
{
Param->PropertyFlags |= CPF_ConstParm;
}
if (FObjectProperty* ObjProp = CastField<FObjectProperty>(Param))
{
UClass* EffectiveClass = nullptr;
if (ObjProp->PropertyClass != nullptr)
{
EffectiveClass = ObjProp->PropertyClass;
}
else if (FClassProperty* ClassProp = CastField<FClassProperty>(ObjProp))
{
EffectiveClass = ClassProp->MetaClass;
}
if ((EffectiveClass != nullptr) && (EffectiveClass->HasAnyClassFlags(CLASS_Const)))
{
Param->PropertyFlags |= CPF_ConstParm;
}
}
else if (FArrayProperty* ArrayProp = CastField<FArrayProperty>(Param))
{
Param->PropertyFlags |= CPF_ReferenceParm;
// ALWAYS pass array parameters as out params, so they're set up as passed by ref
Param->PropertyFlags |= CPF_OutParm;
}
// Delegate properties have a direct reference to a UFunction that we may currently be generating, so we're going
// to track them and fix them after all UFunctions have been generated. As you can tell we're tightly coupled
// to the implementation of CreatePropertyOnScope
else if( FDelegateProperty* DelegateProp = CastField<FDelegateProperty>(Param))
{
OutSkeletonFixupData.Add( {
Pin->PinType.PinSubCategoryMemberReference,
DelegateProp
} );
}
else if( FMulticastDelegateProperty* MCDelegateProp = CastField<FMulticastDelegateProperty>(Param))
{
OutSkeletonFixupData.Add( {
Pin->PinType.PinSubCategoryMemberReference,
MCDelegateProp
} );
}
PropertyListBuilder.AppendNoTerminate(*Param);
}
}
}
if(ReturnNodes.Num() > 0)
{
// Gather all input pins on these nodes, these are
// the outputs of the function:
TSet<FName> UsedPinNames;
for(UK2Node_FunctionResult* Node : ReturnNodes)
{
for(UEdGraphPin* Pin : Node->Pins)
{
if(!Schema->IsExecPin(*Pin) && Pin->ParentPin == nullptr)
{
if(!UsedPinNames.Contains(Pin->PinName))
{
UsedPinNames.Add(Pin->PinName);
FProperty* Param = FKismetCompilerUtilities::CreatePropertyOnScope(NewFunction, Pin->PinName, Pin->PinType, Ret, CPF_None, Schema, MessageLog);
if(Param)
{
Param->SetFlags(RF_Transient);
// we only tag things as CPF_ReturnParm if the value is named ReturnValue.... this is *terrible* behavior:
if(Param->GetFName() == UEdGraphSchema_K2::PN_ReturnValue)
{
Param->PropertyFlags |= CPF_ReturnParm;
}
Param->PropertyFlags |= CPF_Parm|CPF_OutParm;
PropertyListBuilder.AppendNoTerminate(*Param);
}
}
}
}
}
}
}
// We're linking the skeleton function because TProperty::LinkInternal
// will assign add TTypeFundamentals::GetComputedFlagsPropertyFlags()
// to PropertyFlags. PropertyFlags must (mostly) match in order for
// functions to be compatible:
NewFunction->StaticLink(true);
return NewFunction;
};
// helpers:
const auto AddFunctionForGraphs = [Schema, &MessageLog, ParentClass, Ret, BP, MakeFunction, &CompilerContext](const TCHAR* FunctionNamePostfix, const TArray<UEdGraph*>& Graphs, UField::FLinkedListBuilder& UFieldListBuilder, bool bIsStaticFunction, bool bAreDelegateGraphs)
{
for( const UEdGraph* Graph : Graphs )
{
TArray<UK2Node_FunctionEntry*> EntryNodes;
Graph->GetNodesOfClass(EntryNodes);
if(EntryNodes.Num() > 0)
{
TArray<UK2Node_FunctionResult*> ReturnNodes;
Graph->GetNodesOfClass(ReturnNodes);
UK2Node_FunctionEntry* EntryNode = EntryNodes[0];
FName NewFunctionName = (EntryNode->CustomGeneratedFunctionName != NAME_None) ? EntryNode->CustomGeneratedFunctionName : Graph->GetFName();
UFunction* NewFunction = MakeFunction(
FName(*(NewFunctionName.ToString() + FunctionNamePostfix)),
UFieldListBuilder,
(EFunctionFlags)(EntryNode->GetFunctionFlags() & ~FUNC_Native),
ReturnNodes,
EntryNode->Pins,
bIsStaticFunction,
false,
nullptr
);
if(NewFunction)
{
FField::FLinkedListBuilder PropertyListBuilder(&NewFunction->ChildProperties);
PropertyListBuilder.MoveToEnd();
if(bAreDelegateGraphs)
{
NewFunction->FunctionFlags |= FUNC_Delegate;
}
// locals:
for( const FBPVariableDescription& BPVD : EntryNode->LocalVariables )
{
if(FProperty* LocalVariable = FKismetCompilerContext::CreateUserDefinedLocalVariableForFunction(BPVD, NewFunction, Ret, PropertyListBuilder, Schema, MessageLog) )
{
LocalVariable->SetFlags(RF_Transient);
}
}
// __WorldContext:
if(bIsStaticFunction)
{
if( FindFProperty<FObjectProperty>(NewFunction, TEXT("__WorldContext")) == nullptr )
{
FEdGraphPinType WorldContextPinType(UEdGraphSchema_K2::PC_Object, NAME_None, UObject::StaticClass(), EPinContainerType::None, false, FEdGraphTerminalType());
FProperty* Param = FKismetCompilerUtilities::CreatePropertyOnScope(NewFunction, TEXT("__WorldContext"), WorldContextPinType, Ret, CPF_None, Schema, MessageLog);
if(Param)
{
Param->SetFlags(RF_Transient);
Param->PropertyFlags |= CPF_Parm;
PropertyListBuilder.AppendNoTerminate(*Param);
}
}
// set the metdata:
NewFunction->SetMetaData(FBlueprintMetadata::MD_WorldContext, TEXT("__WorldContext"));
}
CompilerContext.SetCalculatedMetaDataAndFlags(NewFunction, EntryNode, Schema);
if (EntryNode->MetaData.HasMetaData(FBlueprintMetadata::MD_FieldNotify) && Ret->ImplementsInterface(UNotifyFieldValueChanged::StaticClass()))
{
ensure(!Ret->FieldNotifies.Contains(FFieldNotificationId(NewFunction->GetFName())));
Ret->FieldNotifies.Add(FFieldNotificationId(NewFunction->GetFName()));
}
}
if (BP->bIsRegeneratingOnLoad)
{
// Ensure that the function's variable cache is up-to-date after property creation. Note that
// this may incur a load if the variable's default value (a string) refers to an external asset;
// that won't result in a package import until after the BP is compiled, and sometimes users will
// save the Blueprint after having set the variable's default value without also recompiling it.
// In that case we want to ensure these assets are loaded as part of regenerating classes on load.
EntryNode->RefreshFunctionVariableCache();
}
}
}
};
auto RetChildrenScope = MutableView(Ret->Children);
UField::FLinkedListBuilder UFieldListBuilder(ToRawPtr(RetChildrenScope));
// Helper function for making UFunctions generated for 'event' nodes, e.g. custom event and timelines
const auto MakeEventFunction = [&UFieldListBuilder, MakeFunction, Schema]( FName InName, EFunctionFlags ExtraFnFlags, const TArray<UEdGraphPin*>& InputPins, const TArray< TSharedPtr<FUserPinInfo> >& UserPins, UFunction* InSourceFN, bool bInCallInEditor, bool bIsDeprecated, const FString& DeprecationMessage, FKismetUserDeclaredFunctionMetadata* UserDefinedMetaData = nullptr)
{
UFunction* NewFunction = MakeFunction(
InName,
UFieldListBuilder,
ExtraFnFlags|FUNC_BlueprintCallable|FUNC_BlueprintEvent,
TArray<UK2Node_FunctionResult*>(),
InputPins,
false,
true,
InSourceFN
);
if(NewFunction)
{
FKismetCompilerContext::SetDefaultInputValueMetaData(NewFunction, UserPins);
if (bIsDeprecated)
{
NewFunction->SetMetaData(FBlueprintMetadata::MD_DeprecatedFunction, TEXT("true"));
if (!DeprecationMessage.IsEmpty())
{
NewFunction->SetMetaData(FBlueprintMetadata::MD_DeprecationMessage, *DeprecationMessage);
}
}
if(bInCallInEditor)
{
NewFunction->SetMetaData(FBlueprintMetadata::MD_CallInEditor, TEXT( "true" ));
}
if (UserDefinedMetaData)
{
NewFunction->SetMetaData(FBlueprintMetadata::MD_FunctionKeywords, *(UserDefinedMetaData->Keywords).ToString());
}
NewFunction->Bind();
NewFunction->StaticLink(true);
}
};
const auto CreateDelegateProxyFunctions = [Ret, &CompilerContext](UField::FLinkedListBuilder& UFieldListBuilder)
{
for (const auto& DelegateInfo : CompilerContext.ConvertibleDelegates)
{
check(DelegateInfo.Key);
const UFunction* DelegateSignature = DelegateInfo.Key->GetDelegateSignature();
// The original signature function is actually a UDelegateFunction type.
// When we create our own function from the original, we need to ensure that it's a standard UFunction.
UObject* NewObject =
StaticDuplicateObject(DelegateSignature, Ret, DelegateInfo.Value.ProxyFunctionName, RF_AllFlags, UFunction::StaticClass());
UFunction* NewFunction = CastChecked<UFunction>(NewObject);
NewFunction->FunctionFlags &= ~(FUNC_Delegate | FUNC_MulticastDelegate);
UFieldListBuilder.AppendNoTerminate(*NewFunction);
}
};
Ret->SetSuperStruct(ParentClass);
Ret->ClassFlags |= (ParentClass->ClassFlags & CLASS_Inherit);
Ret->ClassCastFlags |= ParentClass->ClassCastFlags;
if (FBlueprintEditorUtils::IsInterfaceBlueprint(BP))
{
Ret->ClassFlags |= CLASS_Interface;
}
// link in delegate signatures, variables will reference these
AddFunctionForGraphs(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX, BP->DelegateSignatureGraphs, UFieldListBuilder, false, true);
// handle event entry ponts (mostly custom events) - this replaces
// the skeleton compile pass CreateFunctionStubForEvent call:
TArray<UEdGraph*> AllEventGraphs;
for (UEdGraph* UberGraph : BP->UbergraphPages)
{
AllEventGraphs.Add(UberGraph);
UberGraph->GetAllChildrenGraphs(AllEventGraphs);
}
for( const UEdGraph* Graph : AllEventGraphs )
{
TArray<UK2Node_Event*> EventNodes;
Graph->GetNodesOfClass(EventNodes);
for( UK2Node_Event* Event : EventNodes )
{
FString DeprecationMessage;
bool bIsDeprecated = false;
bool bCallInEditor = false;
FKismetUserDeclaredFunctionMetadata* UserMetaData = nullptr;
if(UK2Node_CustomEvent* CustomEvent = Cast<UK2Node_CustomEvent>(Event))
{
bCallInEditor = CustomEvent->bCallInEditor;
bIsDeprecated = CustomEvent->bIsDeprecated;
if (bIsDeprecated)
{
DeprecationMessage = CustomEvent->DeprecationMessage;
}
UserMetaData = &(CustomEvent->GetUserDefinedMetaData());
}
MakeEventFunction(
CompilerContext.GetEventStubFunctionName(Event),
(EFunctionFlags)Event->FunctionFlags,
Event->Pins,
Event->UserDefinedPins,
Event->FindEventSignatureFunction(),
bCallInEditor,
bIsDeprecated,
DeprecationMessage,
UserMetaData
);
}
}
for (UTimelineTemplate* Timeline : BP->Timelines)
{
if(Timeline)
{
// If the timeline hasn't gone through post load that means that the cache isn't up to date, so force an update on it
if (Timeline->HasAllFlags(RF_NeedPostLoad) && Timeline->GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::StoreTimelineNamesInTemplate)
{
FUpdateTimelineCachedNames::Execute(Timeline);
}
for (const FTTEventTrack& EventTrack : Timeline->EventTracks)
{
MakeEventFunction(EventTrack.GetFunctionName(), EFunctionFlags::FUNC_None, TArray<UEdGraphPin*>(), TArray< TSharedPtr<FUserPinInfo> >(), nullptr, false, false, FString());
}
MakeEventFunction(Timeline->GetUpdateFunctionName(), EFunctionFlags::FUNC_None, TArray<UEdGraphPin*>(), TArray< TSharedPtr<FUserPinInfo> >(), nullptr, false, false, FString());
MakeEventFunction(Timeline->GetFinishedFunctionName(), EFunctionFlags::FUNC_None, TArray<UEdGraphPin*>(), TArray< TSharedPtr<FUserPinInfo> >(), nullptr, false, false, FString());
}
}
{
CompilerContext.NewClass = Ret;
CompilerContext.RegisterClassDelegateProxiesFromBlueprint();
CompilerContext.NewClass = OriginalNewClass;
}
{
CompilerContext.NewClass = Ret;
TGuardValue<bool> GuardAssignDelegateSignatureFunction( CompilerContext.bAssignDelegateSignatureFunction, true);
CompilerContext.CreateClassVariablesFromBlueprint();
CompilerContext.NewClass = OriginalNewClass;
}
// keeping for compat, is this still needed?
UFieldListBuilder.Restart();
UFieldListBuilder.MoveToEnd();
CreateDelegateProxyFunctions(UFieldListBuilder);
AddFunctionForGraphs(TEXT(""), BP->FunctionGraphs, UFieldListBuilder, BPTYPE_FunctionLibrary == BP->BlueprintType, false);
AddFunctionForGraphs(TEXT(""), CompilerContext.GeneratedFunctionGraphs, UFieldListBuilder, BPTYPE_FunctionLibrary == BP->BlueprintType, false);
// Add interface functions, often these are added by normal detection of implemented functions, but they won't be
// if the interface is added but the function is not implemented:
for(const FBPInterfaceDescription& BPID : BP->ImplementedInterfaces)
{
UClass* InterfaceClass = BPID.Interface;
// Again, once the skeleton has been created we will purge null ImplementedInterfaces entries,
// but not yet:
if(InterfaceClass)
{
if(UBlueprint* Owner = Cast<UBlueprint>(InterfaceClass->ClassGeneratedBy))
{
if( ensure(Owner->SkeletonGeneratedClass) )
{
InterfaceClass = Owner->SkeletonGeneratedClass;
}
}
AddFunctionForGraphs(TEXT(""), BPID.Graphs, UFieldListBuilder, BPTYPE_FunctionLibrary == BP->BlueprintType, false);
for (TFieldIterator<UFunction> FunctionIt(InterfaceClass, EFieldIteratorFlags::ExcludeSuper); FunctionIt; ++FunctionIt)
{
UFunction* Fn = *FunctionIt;
// Note that MakeFunction will early out if the function was created above:
MakeFunction(
Fn->GetFName(),
UFieldListBuilder,
Fn->FunctionFlags & ~FUNC_Native,
TArray<UK2Node_FunctionResult*>(),
TArray<UEdGraphPin*>(),
false,
false,
nullptr
);
}
}
}
// Add the uber graph frame just so that we match the old skeleton class's layout. This will be removed in 4.20:
if (CompilerContext.UsePersistentUberGraphFrame() && AllEventGraphs.Num() != 0)
{
//UBER GRAPH PERSISTENT FRAME
FEdGraphPinType Type(TEXT("struct"), NAME_None, FPointerToUberGraphFrame::StaticStruct(), EPinContainerType::None, false, FEdGraphTerminalType());
CompilerContext.NewClass = Ret;
FProperty* Property = CompilerContext.CreateVariable(UBlueprintGeneratedClass::GetUberGraphFrameName(), Type);
CompilerContext.NewClass = OriginalNewClass;
Property->SetPropertyFlags(CPF_DuplicateTransient | CPF_Transient);
}
CompilerContext.NewClass = Ret;
CompilerContext.bAssignDelegateSignatureFunction = true;
CompilerContext.FinishCompilingClass(Ret);
CompilerContext.bAssignDelegateSignatureFunction = false;
CompilerContext.NewClass = OriginalNewClass;
Ret->GetDefaultObject()->SetFlags(RF_Transient);
return Ret;
}
bool FBlueprintCompilationManagerImpl::IsQueuedForCompilation(UBlueprint* BP)
{
return BP->bQueuedForCompilation;
}
void FBlueprintCompilationManagerImpl::ConformToParentAndInterfaces(UBlueprint* BP)
{
// If graphs are renamed the blueprint will be marked as not 'bCachedDependenciesUpToDate', but
// because we're conforming an existing dependency we don't need to change bCachedDependenciesUpToDate:
TGuardValue<bool> LockDependenciesUpToDate(BP->bCachedDependenciesUpToDate, BP->bCachedDependenciesUpToDate);
// Make sure that this blueprint is up-to-date with regards to its parent functions
FBlueprintEditorUtils::ConformCallsToParentFunctions(BP);
// Conform implemented events here, to ensure we generate custom events if necessary after reparenting
FBlueprintEditorUtils::ConformImplementedEvents(BP);
// Conform implemented interfaces here, to ensure we generate all functions required by the interface as stubs
FBlueprintEditorUtils::ConformImplementedInterfaces(BP);
// Make sure we don't have any signature graphs with no corresponding variable - some assets have
// managed to get into this state - the UI does not provide a way to fix these objects manually
FBlueprintEditorUtils::ConformDelegateSignatureGraphs(BP);
}
void FBlueprintCompilationManagerImpl::RelinkSkeleton(UClass* SkeletonToRelink)
{
// CDO needs to be moved aside already:
ensure(SkeletonToRelink->GetDefaultObject(false) == nullptr);
ensure(!SkeletonToRelink->GetSuperClass()->HasAnyClassFlags(CLASS_NewerVersionExists));
SkeletonToRelink->ClassConstructor = nullptr;
SkeletonToRelink->ClassVTableHelperCtorCaller = nullptr;
SkeletonToRelink->CppClassStaticFunctions.Reset();
SkeletonToRelink->Bind();
SkeletonToRelink->ClearFunctionMapsCaches();
SkeletonToRelink->StaticLink(true);
SkeletonToRelink->GetDefaultObject()->SetFlags(RF_Transient);
// Update UFunction SuperStruct pointers, which are typically set to their overridden function.
// For non-skeleton classes these are reassigned by the 'bytecode' recompile that we run on all
// referencing classes...
for (TFieldIterator<UFunction> FuncIter(SkeletonToRelink, EFieldIteratorFlags::ExcludeSuper); FuncIter; ++FuncIter)
{
if (UFunction* SuperFunction = SkeletonToRelink->GetSuperClass()->FindFunctionByName(FuncIter->GetFName()))
{
FuncIter->SetSuperStruct(SuperFunction);
}
}
}
void FBlueprintCompilationManagerImpl::GatherOutOfDateDependenciesRecursive(TObjectPtr<UBlueprint> Gather, TSet<TObjectPtr<UBlueprint>>& OutOfDateDeps)
{
FBlueprintEditorUtils::EnsureCachedDependenciesUpToDate(Gather);
// make sure any dirty dependencies are also compiled:
for (TWeakObjectPtr<UBlueprint> BPWeak : Gather->CachedDependencies)
{
if (UBlueprint* BP = BPWeak.Get())
{
if (BP->Status == BS_Dirty && BP->bQueuedForCompilation == false)
{
if(!OutOfDateDeps.Contains(BP))
{
OutOfDateDeps.Add(BP);
GatherOutOfDateDependenciesRecursive(BP, OutOfDateDeps);
}
}
}
}
}
void FBlueprintCompilationManagerImpl::QueueOutOfDateDependencies( const TArray<FBPCompileRequest>& QueuedRequests, TArray<UBlueprint*>& OutBlueprintsToRecompile, TArray<FCompilerData>& CurrentlyCompilingBPs)
{
TArray<TObjectPtr<UBlueprint>> RootCompilationRequests;
// we don't care about 'skeleton only' regeneration, filter those, and operate
// only on 'full compilation' requests - we also don't need to gather
// dependencies when a BP does not yet have a generated class or CDO, as it has
// nothing anyone could be dependent upon:
Algo::TransformIf(QueuedRequests, RootCompilationRequests,
[](const FBPCompileRequest& CompileRequest) -> bool
{
return
CompileRequest.BPToCompile->GeneratedClass != nullptr &&
CompileRequest.BPToCompile->GeneratedClass->GetDefaultObject(false) != nullptr &&
(CompileRequest.CompileOptions & EBlueprintCompileOptions::RegenerateSkeletonOnly)
== EBlueprintCompileOptions::None;
},
[](const FBPCompileRequest& CompileRequest)
{
return CompileRequest.BPToCompile;
}
);
// Build up full compilation requests, including any
// BPs that are using a macro lib that has had compilation
// requested (this occurs on PIE):
TArray<TObjectPtr<UBlueprint>> FullCompilationRequests;
FullCompilationRequests.Reserve(RootCompilationRequests.Num());
for(TObjectPtr<UBlueprint> BP : RootCompilationRequests)
{
FullCompilationRequests.Add(BP);
if(!BP->bHasBeenRegenerated && BP->GetLinker())
{
// we may have cached dependencies before being fully loaded:
BP->bCachedDependenciesUpToDate = false;
}
const bool bWasDependencyCacheOutOfDate = !BP->bCachedDependenciesUpToDate;
if(BP->BlueprintType == BPTYPE_MacroLibrary)
{
TArray<UBlueprint*> DependentBlueprints;
FBlueprintEditorUtils::GetDependentBlueprints(BP, DependentBlueprints);
for(UBlueprint* DependentBlueprint : DependentBlueprints)
{
// if the macro is out of date, transitively dirty dependencies for dependents:
DependentBlueprint->bCachedDependenciesUpToDate &= !bWasDependencyCacheOutOfDate;
FullCompilationRequests.Add(DependentBlueprint);
}
}
}
// Gather dependencies that are also out of date - these must be recompiled
// in case a function signature has changed
TSet<TObjectPtr<UBlueprint>> DependenciesToRecompile;
for(TObjectPtr<UBlueprint> BP : FullCompilationRequests)
{
if(!BP->bQueuedForCompilation)
{
if(!DependenciesToRecompile.Contains(BP))
{
DependenciesToRecompile.Add(BP);
GatherOutOfDateDependenciesRecursive(BP, DependenciesToRecompile);
}
}
else
{
// root compilation request (queued by caller), just look for its
// out of date dependencies
GatherOutOfDateDependenciesRecursive(BP, DependenciesToRecompile);
}
}
for(TObjectPtr<UBlueprint> BP : DependenciesToRecompile)
{
ensure(!BP->bQueuedForCompilation);
BP->bQueuedForCompilation = true;
CurrentlyCompilingBPs.Emplace(
FCompilerData(
BP,
ECompilationManagerJobType::Normal,
nullptr,
EBlueprintCompileOptions::None,
false // full compile
)
);
OutBlueprintsToRecompile.Add(BP);
}
}
// FFixupBytecodeReferences Implementation:
FBlueprintCompilationManagerImpl::FFixupBytecodeReferences::FFixupBytecodeReferences(UObject* InObject)
{
ArIsObjectReferenceCollector = true;
InObject->Serialize(*this);
class FArchiveProxyCollector : public FReferenceCollector
{
/** Archive we are a proxy for */
FArchive& Archive;
public:
FArchiveProxyCollector(FArchive& InArchive)
: Archive(InArchive)
{
}
virtual void HandleObjectReference(UObject*& Object, const UObject* ReferencingObject, const FProperty* ReferencingProperty) override
{
Archive << Object;
}
virtual void HandleObjectReferences(UObject** InObjects, const int32 ObjectNum, const UObject* InReferencingObject, const FProperty* InReferencingProperty) override
{
for (int32 ObjectIndex = 0; ObjectIndex < ObjectNum; ++ObjectIndex)
{
UObject*& Object = InObjects[ObjectIndex];
Archive << Object;
}
}
virtual bool IsIgnoringArchetypeRef() const override
{
return false;
}
virtual bool IsIgnoringTransient() const override
{
return false;
}
} ArchiveProxyCollector(*this);
InObject->GetClass()->CallAddReferencedObjects(InObject, ArchiveProxyCollector);
}
FArchive& FBlueprintCompilationManagerImpl::FFixupBytecodeReferences::operator<<( UObject*& Obj )
{
if (Obj != NULL)
{
if(UClass* RelatedClass = Cast<UClass>(Obj))
{
UClass* NewClass = RelatedClass->GetAuthoritativeClass();
ensure(NewClass);
if(NewClass != RelatedClass)
{
Obj = NewClass;
}
}
else if(UField* AsField = Cast<UField>(Obj))
{
UClass* OwningClass = AsField->GetOwnerClass();
if(OwningClass)
{
UClass* NewClass = OwningClass->GetAuthoritativeClass();
ensure(NewClass);
if(NewClass != OwningClass)
{
// drill into new class finding equivalent object:
TArray<FName> Names;
UObject* Iter = Obj;
while (Iter && Iter != OwningClass)
{
Names.Add(Iter->GetFName());
Iter = Iter->GetOuter();
}
UObject* Owner = NewClass;
UObject* Match = nullptr;
for(int32 I = Names.Num() - 1; I >= 0; --I)
{
UObject* Next = StaticFindObjectFast( UObject::StaticClass(), Owner, Names[I]);
if( Next )
{
if(I == 0)
{
Match = Next;
}
else
{
Owner = Match;
}
}
else
{
break;
}
}
if(Match)
{
Obj = Match;
}
}
}
}
}
return *this;
}
FArchive& FBlueprintCompilationManagerImpl::FFixupBytecodeReferences::operator<<(FField*& Field)
{
if (Field != nullptr)
{
UClass* OwningClass = Field->GetOwnerClass();
if (OwningClass)
{
UClass* NewClass = OwningClass->GetAuthoritativeClass();
ensure(NewClass);
if (NewClass && NewClass != OwningClass)
{
// drill into new class finding equivalent object:
TArray<FFieldVariant> OwnerChain;
for (FFieldVariant Iter = Field; Iter.IsValid() && Iter.Get<UObject>(); Iter = Iter.GetOwnerVariant())
{
OwnerChain.Add(Iter);
}
FString MatchPath = NewClass->GetPathName();
for (int32 ChainIndex = OwnerChain.Num() - 1; ChainIndex >= 0; --ChainIndex)
{
MatchPath += SUBOBJECT_DELIMITER_CHAR;
MatchPath += OwnerChain[ChainIndex].GetName();
}
TFieldPath<FField> Match;
Match.Generate(*MatchPath);
if (Match.Get())
{
Field = Match.Get();
}
}
}
}
return *this;
}
// Singleton boilerplate, simply forwarding to the implementation above:
FBlueprintCompilationManagerImpl* BPCMImpl = nullptr;
void FlushReinstancingQueueImplWrapper()
{
BPCMImpl->FlushReinstancingQueueImpl();
}
void FlushCompilationQueueImplWrapper(FUObjectSerializeContext* InLoadContext)
{
FBlueprintCompilationManager::FlushCompilationQueue(InLoadContext);
}
// Recursive function to move CDOs aside to immutable versions of classes
// so that CDOs can be safely GC'd. Recursion is necessary to find REINST_ classes
// that are still parented to a valid SKEL (e.g. from MarkBlueprintAsStructurallyModified)
// and therefore need to be REINST_'d again before the SKEL is mutated... Normally
// these old REINST_ classes are GC'd but, there is no guarantee of that:
void MoveSkelCDOAside(UClass* Class, TMap<UClass*, UClass*>& OutOldToNewMap)
{
UClass* CopyOfOldClass = FBlueprintCompileReinstancer::MoveCDOToNewClass(Class, OutOldToNewMap, true);
OutOldToNewMap.Add(Class, CopyOfOldClass);
// Child types that are associated with a BP will be compiled by the compilation
// manager, but old REINST_ or TRASH_ types need to be handled explicitly:
TArray<UClass*> Children;
GetDerivedClasses(Class, Children);
for(UClass* Child : Children)
{
if(UBlueprint* BP = Cast<UBlueprint>(Child->ClassGeneratedBy))
{
if(BP->SkeletonGeneratedClass != Child)
{
if( ensureMsgf (
BP->GeneratedClass != Child,
TEXT("Class in skeleton hierarchy is cached as GeneratedClass"))
)
{
MoveSkelCDOAside(Child, OutOldToNewMap);
}
}
}
}
};
// ***************************************************************
// FBPCompileRequest
// ***************************************************************
FBPCompileRequest::FBPCompileRequest(UBlueprint* InBPToCompile, EBlueprintCompileOptions InCompileOptions, FCompilerResultsLog* InClientResultsLog): BPToCompile(InBPToCompile)
, CompileOptions(InBPToCompile ? InCompileOptions | InBPToCompile->GetDefaultCompileOptions() : InCompileOptions)
, ClientResultsLog(InClientResultsLog)
{
}
// ***************************************************************
// FBlueprintCompilationManager
// ***************************************************************
void FBlueprintCompilationManager::Initialize()
{
if(!BPCMImpl)
{
BPCMImpl = new FBlueprintCompilationManagerImpl();
}
}
void FBlueprintCompilationManager::Shutdown()
{
delete BPCMImpl;
BPCMImpl = nullptr;
}
// Forward to impl:
void FBlueprintCompilationManager::FlushCompilationQueue(FUObjectSerializeContext* InLoadContext)
{
if(BPCMImpl)
{
LLM_SCOPE_BYNAME(TEXT("Blueprints"));
BPCMImpl->FlushCompilationQueueImpl(false, nullptr, nullptr, InLoadContext);
// We can't support save on compile or keeping old CDOs from GCing when reinstancing is deferred:
BPCMImpl->CompiledBlueprintsToSave.Empty();
BPCMImpl->OldCDOs.Empty();
}
}
void FBlueprintCompilationManager::FlushCompilationQueueAndReinstance()
{
if(BPCMImpl)
{
BPCMImpl->FlushCompilationQueueImpl(false, nullptr, nullptr, nullptr);
BPCMImpl->FlushReinstancingQueueImpl();
BPCMImpl->OldCDOs.Empty();
}
}
void FBlueprintCompilationManager::CompileSynchronously(const FBPCompileRequest& Request)
{
if(BPCMImpl)
{
BPCMImpl->CompileSynchronouslyImpl(Request);
}
}
void FBlueprintCompilationManager::NotifyBlueprintLoaded(UBlueprint* BPLoaded)
{
// Blueprints can be loaded before editor modules are on line:
if(!BPCMImpl)
{
FBlueprintCompilationManager::Initialize();
}
if(FBlueprintEditorUtils::IsCompileOnLoadDisabled(BPLoaded))
{
return;
}
check(BPLoaded->GetLinker());
BPCMImpl->QueueForCompilation(FBPCompileRequest(BPLoaded, EBlueprintCompileOptions::IsRegeneratingOnLoad, nullptr));
}
void FBlueprintCompilationManager::QueueForCompilation(UBlueprint* BPLoaded)
{
BPCMImpl->QueueForCompilation(FBPCompileRequest(BPLoaded, EBlueprintCompileOptions::None, nullptr));
}
bool FBlueprintCompilationManager::IsGeneratedClassLayoutReady()
{
if(!BPCMImpl)
{
// legacy behavior: always assume generated class layout is good:
return true;
}
return BPCMImpl->IsGeneratedClassLayoutReady();
}
bool FBlueprintCompilationManager::GetDefaultValue(const UClass* ForClass, const FProperty* Property, FString& OutDefaultValueAsString)
{
if(!BPCMImpl)
{
// legacy behavior: can't provide CDO for classes currently being compiled
return false;
}
BPCMImpl->GetDefaultValue(ForClass, Property, OutDefaultValueAsString);
return true;
}
void FBlueprintCompilationManager::ReparentHierarchies(const TMap<UClass*, UClass*>& OldClassToNewClass)
{
FBlueprintCompilationManagerImpl::ReparentHierarchies(OldClassToNewClass, EReparentClassOptions::None);
}
void FBlueprintCompilationManager::RegisterCompilerExtension(TSubclassOf<UBlueprint> BlueprintType, UBlueprintCompilerExtension* Extension)
{
Initialize();
BPCMImpl->RegisterCompilerExtension(BlueprintType, Extension);
}
#undef LOCTEXT_NAMESPACE