void FBlueprintCompileReinstancer::UpdateBytecodeReferences() { BP_SCOPED_COMPILER_EVENT_STAT(EKismetReinstancerStats_UpdateBytecodeReferences); if(ClassToReinstance != NULL) { TMap<UObject*, UObject*> FieldMappings; GenerateFieldMappings(FieldMappings); for( auto DependentBP = Dependencies.CreateIterator(); DependentBP; ++DependentBP ) { UClass* BPClass = (*DependentBP)->GeneratedClass; // Skip cases where the class is junk, or haven't finished serializing in yet if( (BPClass == ClassToReinstance) || (BPClass->GetOutermost() == GetTransientPackage()) || BPClass->HasAnyClassFlags(CLASS_NewerVersionExists) || (BPClass->ClassGeneratedBy && BPClass->ClassGeneratedBy->HasAnyFlags(RF_NeedLoad|RF_BeingRegenerated)) ) { continue; } // For each function defined in this blueprint, run through the bytecode, and update any refs from the old properties to the new for( TFieldIterator<UFunction> FuncIter(BPClass, EFieldIteratorFlags::ExcludeSuper); FuncIter; ++FuncIter ) { UFunction* CurrentFunction = *FuncIter; if( CurrentFunction->Script.Num() > 0 ) { FArchiveReplaceObjectRef<UObject> ReplaceAr(CurrentFunction, FieldMappings, /*bNullPrivateRefs=*/ false, /*bIgnoreOuterRef=*/ true, /*bIgnoreArchetypeRef=*/ true); } } } } }
void FBlueprintCompileReinstancer::ReinstanceObjects(bool bForceAlwaysReinstance) { BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_ReinstanceObjects); static const FBoolConfigValueHelper FirstCompileChildrenThenReinstance(TEXT("Kismet"), TEXT("bFirstCompileChildrenThenReinstance"), GEngineIni); if (FirstCompileChildrenThenReinstance) { // Make sure we only reinstance classes once! static TArray<TSharedRef<FBlueprintCompileReinstancer>> QueueToReinstance; TSharedRef<FBlueprintCompileReinstancer> SharedThis = AsShared(); const bool bAlreadyQueued = QueueToReinstance.Contains(SharedThis); if (!bAlreadyQueued) { QueueToReinstance.Push(SharedThis); if (ClassToReinstance && DuplicatedClass) { CompileChildren(); } if (QueueToReinstance.Num() && (QueueToReinstance[0] == SharedThis)) { // All children were recompiled. It's safe to reinstance. for (int32 Idx = 0; Idx < QueueToReinstance.Num(); ++Idx) { QueueToReinstance[Idx]->ReinstanceInner(bForceAlwaysReinstance); } QueueToReinstance.Empty(); } } } else { //THE OLD WAY if (bHasReinstanced) { return; } bHasReinstanced = true; if (ClassToReinstance && DuplicatedClass) { ReinstanceInner(bForceAlwaysReinstance); CompileChildren(); } } }
void FBlueprintCompileReinstancer::CompileChildren() { BP_SCOPED_COMPILER_EVENT_STAT(EKismetReinstancerStats_RecompileChildClasses); // Reparent all dependent blueprints, and recompile to ensure that they get reinstanced with the new memory layout for (auto ChildBP = Children.CreateIterator(); ChildBP; ++ChildBP) { UBlueprint* BP = *ChildBP; if (BP->ParentClass == ClassToReinstance || BP->ParentClass == DuplicatedClass) { ReparentChild(BP); FKismetEditorUtilities::CompileBlueprint(BP, false, bSkipGarbageCollection); } } }
void FBlueprintCompileReinstancer::ReinstanceInner(bool bForceAlwaysReinstance) { if (ClassToReinstance && DuplicatedClass) { static const FBoolConfigValueHelper ReinstanceOnlyWhenNecessary(TEXT("Kismet"), TEXT("bReinstanceOnlyWhenNecessary"), GEngineIni); bool bShouldReinstance = true; // See if we need to do a full reinstance or can do the faster refresh path (when enabled or no values were modified, and the structures match) if (ReinstanceOnlyWhenNecessary && !bForceAlwaysReinstance) { BP_SCOPED_COMPILER_EVENT_STAT(EKismetReinstancerStats_ReplaceClassNoReinsancing); const UBlueprintGeneratedClass* BPClassA = Cast<const UBlueprintGeneratedClass>(DuplicatedClass); const UBlueprintGeneratedClass* BPClassB = Cast<const UBlueprintGeneratedClass>(ClassToReinstance); const UBlueprint* BP = Cast<const UBlueprint>(ClassToReinstance->ClassGeneratedBy); static const FBoolConfigValueHelper ChangeDefaultValueWithoutReinstancing(TEXT("Kismet"), TEXT("bChangeDefaultValueWithoutReinstancing"), GEngineIni); const bool bTheSameDefaultValues = (BP != nullptr) && (ClassToReinstanceDefaultValuesCRC != 0) && (BP->CrcPreviousCompiledCDO == ClassToReinstanceDefaultValuesCRC); const bool bTheSameLayout = (BPClassA != nullptr) && (BPClassB != nullptr) && FStructUtils::TheSameLayout(BPClassA, BPClassB, true); const bool bAllowedToDoFastPath = (ChangeDefaultValueWithoutReinstancing || bTheSameDefaultValues) && bTheSameLayout; if (bAllowedToDoFastPath) { ReinstanceFast(); bShouldReinstance = false; } } if (bShouldReinstance) { UE_LOG(LogBlueprint, Log, TEXT("BlueprintCompileReinstancer: Doing a full reinstance on class '%s'"), *GetPathNameSafe(ClassToReinstance)); ReplaceInstancesOfClass(DuplicatedClass, ClassToReinstance, OriginalCDO, &ObjectsThatShouldUseOldStuff); } else if (ClassToReinstance->IsChildOf<UActorComponent>()) { // ReplaceInstancesOfClass() handles this itself, if we had to re-instance ReconstructOwnerInstances(ClassToReinstance); } } }
void FBlueprintCompileReinstancer::ReplaceInstancesOfClass(UClass* OldClass, UClass* NewClass, UObject* OriginalCDO, TSet<UObject*>* ObjectsThatShouldUseOldStuff) { USelection* SelectedActors; bool bSelectionChanged = false; TArray<UObject*> ObjectsToReplace; const bool bLogConversions = false; // for debugging // Map of old objects to new objects TMap<UObject*, UObject*> OldToNewInstanceMap; TMap<UClass*, UClass*> OldToNewClassMap; OldToNewClassMap.Add(OldClass, NewClass); TMap<FStringAssetReference, UObject*> ReinstancedObjectsWeakReferenceMap; // actors being replace TArray<FActorReplacementHelper> ReplacementActors; // A list of objects (e.g. Blueprints) that potentially have editors open that we need to refresh TArray<UObject*> PotentialEditorsForRefreshing; // A list of component owners that need their construction scripts re-ran (because a component of theirs has been reinstanced) TSet<AActor*> OwnersToReconstruct; // Set global flag to let system know we are reconstructing blueprint instances TGuardValue<bool> GuardTemplateNameFlag(GIsReconstructingBlueprintInstances, true); struct FObjectRemappingHelper { void OnObjectsReplaced(const TMap<UObject*, UObject*>& InReplacedObjects) { ReplacedObjects.Append(InReplacedObjects); } TMap<UObject*, UObject*> ReplacedObjects; } ObjectRemappingHelper; FDelegateHandle OnObjectsReplacedHandle = GEditor->OnObjectsReplaced().AddRaw(&ObjectRemappingHelper,&FObjectRemappingHelper::OnObjectsReplaced); { BP_SCOPED_COMPILER_EVENT_STAT(EKismetReinstancerStats_ReplaceInstancesOfClass); const bool bIncludeDerivedClasses = false; GetObjectsOfClass(OldClass, ObjectsToReplace, bIncludeDerivedClasses); SelectedActors = GEditor->GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); // Then fix 'real' (non archetype) instances of the class for (UObject* OldObject : ObjectsToReplace) { // Skip non-archetype instances, EXCEPT for component templates const bool bIsComponent = NewClass->IsChildOf(UActorComponent::StaticClass()); if ((!bIsComponent && OldObject->IsTemplate()) || OldObject->IsPendingKill()) { continue; } UBlueprint* CorrespondingBlueprint = Cast<UBlueprint>(OldObject->GetClass()->ClassGeneratedBy); UObject* OldBlueprintDebugObject = nullptr; // If this object is being debugged, cache it off so we can preserve the 'object being debugged' association if ((CorrespondingBlueprint != nullptr) && (CorrespondingBlueprint->GetObjectBeingDebugged() == OldObject)) { OldBlueprintDebugObject = OldObject; } AActor* OldActor = Cast<AActor>(OldObject); UObject* NewUObject = nullptr; // if the object to replace is an actor... if (OldActor != nullptr) { FVector Location = FVector::ZeroVector; FRotator Rotation = FRotator::ZeroRotator; if (USceneComponent* OldRootComponent = OldActor->GetRootComponent()) { Location = OldActor->GetActorLocation(); Rotation = OldActor->GetActorRotation(); } // If this actor was spawned from an Archetype, we spawn the new actor from the new version of that archetype UObject* OldArchetype = OldActor->GetArchetype(); UWorld* World = OldActor->GetWorld(); AActor* NewArchetype = Cast<AActor>(OldToNewInstanceMap.FindRef(OldArchetype)); // Check that either this was an instance of the class directly, or we found a new archetype for it check(OldArchetype == OldClass->GetDefaultObject() || NewArchetype); // Spawn the new actor instance, in the same level as the original, but deferring running the construction script until we have transferred modified properties ULevel* ActorLevel = OldActor->GetLevel(); UClass** MappedClass = OldToNewClassMap.Find(OldActor->GetClass()); UClass* SpawnClass = MappedClass ? *MappedClass : NewClass; FActorSpawnParameters SpawnInfo; SpawnInfo.OverrideLevel = ActorLevel; SpawnInfo.Template = NewArchetype; SpawnInfo.bNoCollisionFail = true; SpawnInfo.bDeferConstruction = true; // Temporarily remove the deprecated flag so we can respawn the Blueprint in the level const bool bIsClassDeprecated = SpawnClass->HasAnyClassFlags(CLASS_Deprecated); SpawnClass->ClassFlags &= ~CLASS_Deprecated; AActor* NewActor = World->SpawnActor(SpawnClass, &Location, &Rotation, SpawnInfo); // Reassign the deprecated flag if it was previously assigned if (bIsClassDeprecated) { SpawnClass->ClassFlags |= CLASS_Deprecated; } check(NewActor != nullptr); NewUObject = NewActor; // store the new actor for the second pass (NOTE: this detaches // OldActor from all child/parent attachments) // // running the NewActor's construction-script is saved for that // second pass (because the construction-script may reference // another instance that hasn't been replaced yet). ReplacementActors.Add(FActorReplacementHelper(NewActor, OldActor)); ReinstancedObjectsWeakReferenceMap.Add(OldObject, NewUObject); OldActor->DestroyConstructedComponents(); // don't want to serialize components from the old actor // Unregister native components so we don't copy any sub-components they generate for themselves (like UCameraComponent does) OldActor->UnregisterAllComponents(); // Unregister any native components, might have cached state based on properties we are going to overwrite NewActor->UnregisterAllComponents(); UEditorEngine::CopyPropertiesForUnrelatedObjects(OldActor, NewActor); // reset properties/streams NewActor->ResetPropertiesForConstruction(); // register native components NewActor->RegisterAllComponents(); // // clean up the old actor (unselect it, remove it from the world, etc.)... if (OldActor->IsSelected()) { GEditor->SelectActor(OldActor, /*bInSelected =*/false, /*bNotify =*/false); bSelectionChanged = true; } if (GEditor->Layers.IsValid()) // ensure(NULL != GEditor->Layers) ?? While cooking the Layers is NULL. { GEditor->Layers->DisassociateActorFromLayers(OldActor); } World->EditorDestroyActor(OldActor, /*bShouldModifyLevel =*/true); OldToNewInstanceMap.Add(OldActor, NewActor); } else { FName OldName(OldObject->GetFName()); OldObject->Rename(NULL, OldObject->GetOuter(), REN_DoNotDirty | REN_DontCreateRedirectors); NewUObject = NewObject<UObject>(OldObject->GetOuter(), NewClass, OldName); check(NewUObject != nullptr); UEditorEngine::CopyPropertiesForUnrelatedObjects(OldObject, NewUObject); if (UAnimInstance* AnimTree = Cast<UAnimInstance>(NewUObject)) { // Initialising the anim instance isn't enough to correctly set up the skeletal mesh again in a // paused world, need to initialise the skeletal mesh component that contains the anim instance. if (USkeletalMeshComponent* SkelComponent = Cast<USkeletalMeshComponent>(AnimTree->GetOuter())) { SkelComponent->InitAnim(true); } } OldObject->RemoveFromRoot(); OldObject->MarkPendingKill(); OldToNewInstanceMap.Add(OldObject, NewUObject); if (bIsComponent) { UActorComponent* Component = Cast<UActorComponent>(NewUObject); AActor* OwningActor = Component->GetOwner(); if (OwningActor) { OwningActor->ResetOwnedComponents(); // Check to see if they have an editor that potentially needs to be refreshed if (OwningActor->GetClass()->ClassGeneratedBy) { PotentialEditorsForRefreshing.AddUnique(OwningActor->GetClass()->ClassGeneratedBy); } // we need to keep track of actor instances that need // their construction scripts re-ran (since we've just // replaced a component they own) OwnersToReconstruct.Add(OwningActor); } } } // If this original object came from a blueprint and it was in the selected debug set, change the debugging to the new object. if ((CorrespondingBlueprint) && (OldBlueprintDebugObject) && (NewUObject)) { CorrespondingBlueprint->SetObjectBeingDebugged(NewUObject); } if (bLogConversions) { UE_LOG(LogBlueprint, Log, TEXT("Converted instance '%s' to '%s'"), *OldObject->GetPathName(), *NewUObject->GetPathName()); } } } GEditor->OnObjectsReplaced().Remove(OnObjectsReplacedHandle); // Now replace any pointers to the old archetypes/instances with pointers to the new one TArray<UObject*> SourceObjects; TArray<UObject*> DstObjects; OldToNewInstanceMap.GenerateKeyArray(SourceObjects); OldToNewInstanceMap.GenerateValueArray(DstObjects); // Also look for references in new spawned objects. SourceObjects.Append(DstObjects); FReplaceReferenceHelper::IncludeCDO(OldClass, NewClass, OldToNewInstanceMap, SourceObjects, OriginalCDO); FReplaceReferenceHelper::FindAndReplaceReferences(SourceObjects, ObjectsThatShouldUseOldStuff, ObjectsToReplace, OldToNewInstanceMap, ReinstancedObjectsWeakReferenceMap); { BP_SCOPED_COMPILER_EVENT_STAT(EKismetReinstancerStats_ReplacementConstruction); // the process of setting up new replacement actors is split into two // steps (this here, is the second)... // // the "finalization" here runs the replacement actor's construction- // script and is left until late to account for a scenario where the // construction-script attempts to modify another instance of the // same class... if this were to happen above, in the ObjectsToReplace // loop, then accessing that other instance would cause an assert in // UProperty::ContainerPtrToValuePtrInternal() (which appropriatly // complains that the other instance's type doesn't match because it // hasn't been replaced yet... that's why we wait until after // FArchiveReplaceObjectRef to run construction-scripts). for (FActorReplacementHelper& ReplacementActor : ReplacementActors) { ReplacementActor.Finalize(ObjectRemappingHelper.ReplacedObjects); } } SelectedActors->EndBatchSelectOperation(); if (bSelectionChanged) { GEditor->NoteSelectionChange(); } if (GEditor) { // Refresh any editors for objects that we've updated components for for (auto BlueprintAsset : PotentialEditorsForRefreshing) { FBlueprintEditor* BlueprintEditor = static_cast<FBlueprintEditor*>(FAssetEditorManager::Get().FindEditorForAsset(BlueprintAsset, /*bFocusIfOpen =*/false)); if (BlueprintEditor) { BlueprintEditor->RefreshEditors(); } } } // in the case where we're replacing component instances, we need to make // sure to re-run their owner's construction scripts for (AActor* ActorInstance : OwnersToReconstruct) { ActorInstance->RerunConstructionScripts(); } }
static void FindAndReplaceReferences(const TArray<UObject*>& SourceObjects, TSet<UObject*>* ObjectsThatShouldUseOldStuff, const TArray<UObject*>& ObjectsToReplace, const TMap<UObject*, UObject*>& OldToNewInstanceMap, const TMap<FStringAssetReference, UObject*>& ReinstancedObjectsWeakReferenceMap) { // Find everything that references these objects TSet<UObject *> Targets; { BP_SCOPED_COMPILER_EVENT_STAT(EKismetReinstancerStats_FindReferencers); TFindObjectReferencers<UObject> Referencers(SourceObjects, NULL, false); for (TFindObjectReferencers<UObject>::TIterator It(Referencers); It; ++It) { UObject* Referencer = It.Value(); if (!ObjectsThatShouldUseOldStuff || !ObjectsThatShouldUseOldStuff->Contains(Referencer)) { Targets.Add(Referencer); } } } { BP_SCOPED_COMPILER_EVENT_STAT(EKismetReinstancerStats_ReplaceReferences); for (TSet<UObject *>::TIterator It(Targets); It; ++It) { UObject* Obj = *It; if (!ObjectsToReplace.Contains(Obj)) // Don't bother trying to fix old objects, this would break them { // The class for finding and replacing weak references. // We can't relay on "standard" weak references replacement as // it depends on FStringAssetReference::ResolveObject, which // tries to find the object with the stored path. It is // impossible, cause above we deleted old actors (after // spawning new ones), so during objects traverse we have to // find FStringAssetReferences with the raw given path taken // before deletion of old actors and fix them. class ReferenceReplace : public FArchiveReplaceObjectRef < UObject > { public: ReferenceReplace(UObject* InSearchObject, const TMap<UObject*, UObject*>& InReplacementMap, TMap<FStringAssetReference, UObject*> InWeakReferencesMap) : FArchiveReplaceObjectRef<UObject>(InSearchObject, InReplacementMap, false, false, false, true), WeakReferencesMap(InWeakReferencesMap) { SerializeSearchObject(); } FArchive& operator<<(FStringAssetReference& Ref) override { const UObject*const* PtrToObjPtr = WeakReferencesMap.Find(Ref); if (PtrToObjPtr != nullptr) { Ref = *PtrToObjPtr; } return *this; } FArchive& operator<<(FAssetPtr& Ref) override { return operator<<(Ref.GetUniqueID()); } private: const TMap<FStringAssetReference, UObject*>& WeakReferencesMap; }; ReferenceReplace ReplaceAr(Obj, OldToNewInstanceMap, ReinstancedObjectsWeakReferenceMap); } } } }
void FKismet2CompilerModule::CompileStructure(class UUserDefinedStruct* Struct, FCompilerResultsLog& Results) { Results.SetSourceName(Struct->GetName()); BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_CompileTime); FUserDefinedStructureCompilerUtils::CompileStruct(Struct, Results, true); }
// Compiles a blueprint. void FKismet2CompilerModule::CompileBlueprint(class UBlueprint* Blueprint, const FKismetCompilerOptions& CompileOptions, FCompilerResultsLog& Results, FBlueprintCompileReinstancer* ParentReinstancer, TArray<UObject*>* ObjLoaded) { SCOPE_SECONDS_COUNTER(GBlueprintCompileTime); BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_CompileTime); Results.SetSourceName(Blueprint->GetName()); const bool bIsBrandNewBP = (Blueprint->SkeletonGeneratedClass == NULL) && (Blueprint->GeneratedClass == NULL) && (Blueprint->ParentClass != NULL) && !CompileOptions.bIsDuplicationInstigated; for ( IBlueprintCompiler* Compiler : Compilers ) { Compiler->PreCompile(Blueprint); } if (CompileOptions.CompileType != EKismetCompileType::Cpp) { BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_CompileSkeletonClass); FBlueprintCompileReinstancer SkeletonReinstancer(Blueprint->SkeletonGeneratedClass); FCompilerResultsLog SkeletonResults; SkeletonResults.bSilentMode = true; FKismetCompilerOptions SkeletonCompileOptions; SkeletonCompileOptions.CompileType = EKismetCompileType::SkeletonOnly; CompileBlueprintInner(Blueprint, SkeletonCompileOptions, SkeletonResults, ObjLoaded); } // If this was a full compile, take appropriate actions depending on the success of failure of the compile if( CompileOptions.IsGeneratedClassCompileType() ) { BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_CompileGeneratedClass); // Perform the full compile CompileBlueprintInner(Blueprint, CompileOptions, Results, ObjLoaded); if (Results.NumErrors == 0) { // Blueprint is error free. Go ahead and fix up debug info Blueprint->Status = (0 == Results.NumWarnings) ? BS_UpToDate : BS_UpToDateWithWarnings; Blueprint->BlueprintSystemVersion = UBlueprint::GetCurrentBlueprintSystemVersion(); // Reapply breakpoints to the bytecode of the new class for (int32 Index = 0; Index < Blueprint->Breakpoints.Num(); ++Index) { UBreakpoint* Breakpoint = Blueprint->Breakpoints[Index]; FKismetDebugUtilities::ReapplyBreakpoint(Breakpoint); } } else { // Should never get errors from a brand new blueprint! ensure(!bIsBrandNewBP || (Results.NumErrors == 0)); // There were errors. Compile the generated class to have function stubs Blueprint->Status = BS_Error; static const FBoolConfigValueHelper ReinstanceOnlyWhenNecessary(TEXT("Kismet"), TEXT("bReinstanceOnlyWhenNecessary"), GEngineIni); // Reinstance objects here, so we can preserve their memory layouts to reinstance them again if( ParentReinstancer != NULL ) { ParentReinstancer->UpdateBytecodeReferences(); if(!Blueprint->bIsRegeneratingOnLoad) { ParentReinstancer->ReinstanceObjects(!ReinstanceOnlyWhenNecessary); } } FBlueprintCompileReinstancer StubReinstancer(Blueprint->GeneratedClass); // Toss the half-baked class and generate a stubbed out skeleton class that can be used FCompilerResultsLog StubResults; StubResults.bSilentMode = true; FKismetCompilerOptions StubCompileOptions(CompileOptions); StubCompileOptions.CompileType = EKismetCompileType::StubAfterFailure; CompileBlueprintInner(Blueprint, StubCompileOptions, StubResults, ObjLoaded); StubReinstancer.UpdateBytecodeReferences(); if( !Blueprint->bIsRegeneratingOnLoad ) { StubReinstancer.ReinstanceObjects(!ReinstanceOnlyWhenNecessary); } } } for ( IBlueprintCompiler* Compiler : Compilers ) { Compiler->PostCompile(Blueprint); } UPackage* Package = Blueprint->GetOutermost(); if( Package ) { UMetaData* MetaData = Package->GetMetaData(); MetaData->RemoveMetaDataOutsidePackage(); } }