// STAGE I: Add any related blueprints that were not compiled, then add any children so that they will be relinked: TArray<UBlueprint*> BlueprintsToRecompile; for(const FBPCompileRequestInternal& CompileJob : QueuedRequests) { UBlueprint* BP = CompileJob.UserData.BPToCompile;
// STAGE II: Filter out data only and interface blueprints: for(int32 I = 0; I < QueuedRequests.Num(); ++I) { FBPCompileRequestInternal& QueuedJob = QueuedRequests[I]; UBlueprint* QueuedBP = QueuedJob.UserData.BPToCompile;
if(bSkipCompile) { CurrentlyCompilingBPs.Emplace( FCompilerData( QueuedBP, ECompilationManagerJobType::SkeletonOnly, QueuedJob.UserData.ClientResultsLog, QueuedJob.UserData.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; }
// No actual compilation work to be done, but try to conform the class and fix up anything that might need to be updated if the native base class has changed in any way FKismetEditorUtilities::ConformBlueprintFlagsAndComponents(QueuedBP);
if (QueuedBP->GeneratedClass) { FBlueprintEditorUtils::RecreateClassMetaData(QueuedBP, QueuedBP->GeneratedClass, true); }
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);
// 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);
// 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); } }
// 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;
// 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 { // 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);
// 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) { constauto HasNoReferencesToChangedFunctions = [&OldFunctionsWithSignatureChanges](FCompilerData& Data) { if(!Data.ShouldSkipIfDependenciesAreUnchanged()) { returnfalse; }
// 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) constbool bIsAnimBlueprintClass = !!Cast<UAnimBlueprint>(Data.BP); if(bIsAnimBlueprintClass) { returnfalse; } // 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) { returnfalse; } } 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. 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); } }
// STAGE IX: Reconstruct nodes and replace deprecated nodes, then broadcast 'precompile for (FCompilerData& CompilerData : CurrentlyCompilingBPs) { if(!CompilerData.ShouldReconstructNodes()) { continue; }
UBlueprint* BP = CompilerData.BP;
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);
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); } } } BP->bHasBeenRegenerated = true; }
// 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! { 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; }
// STAGE XII: Recompile every blueprint bGeneratedClassLayoutReady = false; for (FCompilerData& CompilerData : CurrentlyCompilingBPs) { UBlueprint* BP = CompilerData.BP; if(CompilerData.ShouldCompileClassLayout()) { // default value propagation occurs in ReinstaneBatch, CDO will be created via CompileFunctions call: if(BP->ParentClass) { if(BP->GeneratedClass) { BP->GeneratedClass->ClassDefaultObject = 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(); } } else { CompilerData.ActiveResultsLog->Error(*LOCTEXT("KismetCompileError_MalformedParentClasss", "Blueprint @@ has missing or NULL parent class.").ToString(), BP); } } elseif(CompilerData.Compiler.IsValid() && BP->GeneratedClass) { CompilerData.Compiler->SetNewClass( CastChecked<UBlueprintGeneratedClass>(BP->GeneratedClass) ); } } FixupDelegateProperties(CurrentlyCompilingBPs); bGeneratedClassLayoutReady = true; ProcessExtensions(CurrentlyCompilingBPs);
// If applicable, register any delegate proxy functions and their captured actor variables RegisterClassDelegateProxiesFromBlueprint();
// Run thru the class defined variables first, get them registered CreateClassVariablesFromBlueprint();
// Add any interfaces that the blueprint implements to the class // (has to happen before we validate pin links in CreateFunctionList(), so that we can verify self/interface pins) AddInterfacesFromBlueprint(NewClass);
// Construct a context for each function, doing validation and building the function interface CreateFunctionList();
for (int32 i = 0; i < FunctionList.Num(); ++i) { if(FunctionList[i].IsDelegateSignature()) { PrecompileFunction(FunctionList[i], InternalFlags); } }
for (int32 i = 0; i < FunctionList.Num(); ++i) { if(!FunctionList[i].IsDelegateSignature()) { PrecompileFunction(FunctionList[i], InternalFlags); } } }
if(!CompilerData.ShouldCompileClassFunctions()) { // default value propagation occurs below: if(BPGC) { if (CompilerData.Reinstancer.IsValid()) { CompilerData.Reinstancer->PropagateSparseClassDataToNewClass(BPGC); }
if( BPGC->ClassDefaultObject && BPGC->ClassDefaultObject->GetClass() == BPGC) { BPGC->ClassDefaultObject->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 | REN_ForceNoResetLoaders ); } BPGC->ClassDefaultObject = nullptr;
// class layout is ready, we can clear bLayoutChanging and CompileFunctions can create the CDO: BPGC->bLayoutChanging = false;
// STAGE XIV: Now we can finish the first stage of the reinstancing operation, moving old classes to new classes: 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 ) ); } }
// 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)
for (FCompilerData& CompilerData : CurrentlyCompilingBPs) { if (!CompilerData.NewDefaultVariables.Num()) { continue; }