void AActor::RerunConstructionScripts() { checkf(!HasAnyFlags(RF_ClassDefaultObject), TEXT("RerunConstructionScripts should never be called on a CDO as it can mutate the transient data on the CDO which then propagates to instances!")); FEditorScriptExecutionGuard ScriptGuard; // don't allow (re)running construction scripts on dying actors bool bAllowReconstruction = !IsPendingKill() && !HasAnyFlags(RF_BeginDestroyed|RF_FinishDestroyed); #if WITH_EDITOR if(bAllowReconstruction && GIsEditor) { // Generate the blueprint hierarchy for this actor TArray<UBlueprint*> ParentBPStack; bAllowReconstruction = UBlueprint::GetBlueprintHierarchyFromClass(GetClass(), ParentBPStack); if(bAllowReconstruction) { for(int i = ParentBPStack.Num() - 1; i > 0 && bAllowReconstruction; --i) { const UBlueprint* ParentBP = ParentBPStack[i]; if(ParentBP && ParentBP->bBeingCompiled) { // don't allow (re)running construction scripts if a parent BP is being compiled bAllowReconstruction = false; } } } } #endif if(bAllowReconstruction) { // Set global flag to let system know we are reconstructing blueprint instances TGuardValue<bool> GuardTemplateNameFlag(GIsReconstructingBlueprintInstances, true); // Temporarily suspend the undo buffer; we don't need to record reconstructed component objects into the current transaction ITransaction* CurrentTransaction = GUndo; GUndo = NULL; // Create cache to store component data across rerunning construction scripts #if WITH_EDITOR FActorTransactionAnnotation* ActorTransactionAnnotation = CurrentTransactionAnnotation.Get(); #endif FComponentInstanceDataCache* InstanceDataCache; FTransform OldTransform = FTransform::Identity; FName SocketName; AActor* Parent = NULL; USceneComponent* ParentComponent = NULL; bool bUseRootComponentProperties = true; // Struct to store info about attached actors struct FAttachedActorInfo { AActor* AttachedActor; FName AttachedToSocket; bool bSetRelativeTransform; FTransform RelativeTransform; }; // Save info about attached actors TArray<FAttachedActorInfo> AttachedActorInfos; #if WITH_EDITOR if (ActorTransactionAnnotation) { InstanceDataCache = &ActorTransactionAnnotation->ComponentInstanceData; if (ActorTransactionAnnotation->bRootComponentDataCached) { OldTransform = ActorTransactionAnnotation->RootComponentData.Transform; Parent = ActorTransactionAnnotation->RootComponentData.AttachedParentInfo.Actor.Get(); if (Parent) { USceneComponent* AttachParent = ActorTransactionAnnotation->RootComponentData.AttachedParentInfo.AttachParent.Get(); ParentComponent = (AttachParent ? AttachParent : FindObjectFast<USceneComponent>(Parent, ActorTransactionAnnotation->RootComponentData.AttachedParentInfo.AttachParentName)); SocketName = ActorTransactionAnnotation->RootComponentData.AttachedParentInfo.SocketName; DetachRootComponentFromParent(); } for (const auto& CachedAttachInfo : ActorTransactionAnnotation->RootComponentData.AttachedToInfo) { AActor* AttachedActor = CachedAttachInfo.Actor.Get(); if (AttachedActor) { FAttachedActorInfo Info; Info.AttachedActor = AttachedActor; Info.AttachedToSocket = CachedAttachInfo.SocketName; Info.bSetRelativeTransform = true; Info.RelativeTransform = CachedAttachInfo.RelativeTransform; AttachedActorInfos.Add(Info); AttachedActor->DetachRootComponentFromParent(); } } bUseRootComponentProperties = false; } } else #endif { InstanceDataCache = new FComponentInstanceDataCache(this); // If there are attached objects detach them and store the socket names TArray<AActor*> AttachedActors; GetAttachedActors(AttachedActors); for (AActor* AttachedActor : AttachedActors) { USceneComponent* EachRoot = AttachedActor->GetRootComponent(); // If the component we are attached to is about to go away... if (EachRoot && EachRoot->AttachParent && EachRoot->AttachParent->IsCreatedByConstructionScript()) { // Save info about actor to reattach FAttachedActorInfo Info; Info.AttachedActor = AttachedActor; Info.AttachedToSocket = EachRoot->AttachSocketName; Info.bSetRelativeTransform = false; AttachedActorInfos.Add(Info); // Now detach it AttachedActor->Modify(); EachRoot->DetachFromParent(true); } } } if (bUseRootComponentProperties && RootComponent != nullptr) { // Do not need to detach if root component is not going away if (RootComponent->AttachParent != NULL && RootComponent->IsCreatedByConstructionScript()) { Parent = RootComponent->AttachParent->GetOwner(); // Root component should never be attached to another component in the same actor! if (Parent == this) { UE_LOG(LogActor, Warning, TEXT("RerunConstructionScripts: RootComponent (%s) attached to another component in this Actor (%s)."), *RootComponent->GetPathName(), *Parent->GetPathName()); Parent = NULL; } ParentComponent = RootComponent->AttachParent; SocketName = RootComponent->AttachSocketName; //detach it to remove any scaling RootComponent->DetachFromParent(true); } OldTransform = RootComponent->ComponentToWorld; OldTransform.SetTranslation(RootComponent->GetComponentLocation()); // take into account any custom location } #if WITH_EDITOR // Save the current construction script-created components by name TMap<const FName, UObject*> DestroyedComponentsByName; TInlineComponentArray<UActorComponent*> PreviouslyAttachedComponents; GetComponents(PreviouslyAttachedComponents); for (auto Component : PreviouslyAttachedComponents) { if (Component) { if (Component->IsCreatedByConstructionScript()) { DestroyedComponentsByName.Add(Component->GetFName(), Component); } else { UActorComponent* OuterComponent = Component->GetTypedOuter<UActorComponent>(); while (OuterComponent) { if (OuterComponent->IsCreatedByConstructionScript()) { DestroyedComponentsByName.Add(Component->GetFName(), Component); break; } OuterComponent = OuterComponent->GetTypedOuter<UActorComponent>(); } } } } #endif // Destroy existing components DestroyConstructedComponents(); // Reset random streams ResetPropertiesForConstruction(); // Exchange net roles before running construction scripts UWorld *OwningWorld = GetWorld(); if (OwningWorld && !OwningWorld->IsServer()) { ExchangeNetRoles(true); } // Run the construction scripts ExecuteConstruction(OldTransform, InstanceDataCache); if(Parent) { USceneComponent* ChildRoot = GetRootComponent(); if (ParentComponent == NULL) { ParentComponent = Parent->GetRootComponent(); } if (ChildRoot != NULL && ParentComponent != NULL) { ChildRoot->AttachTo(ParentComponent, SocketName, EAttachLocation::KeepWorldPosition); } } // If we had attached children reattach them now - unless they are already attached for(FAttachedActorInfo& Info : AttachedActorInfos) { // If this actor is no longer attached to anything, reattach if (!Info.AttachedActor->IsPendingKill() && Info.AttachedActor->GetAttachParentActor() == NULL) { USceneComponent* ChildRoot = Info.AttachedActor->GetRootComponent(); if (ChildRoot && ChildRoot->AttachParent != RootComponent) { ChildRoot->AttachTo(RootComponent, Info.AttachedToSocket, EAttachLocation::KeepWorldPosition); if (Info.bSetRelativeTransform) { ChildRoot->SetRelativeTransform(Info.RelativeTransform); } ChildRoot->UpdateComponentToWorld(); } } } // Restore the undo buffer GUndo = CurrentTransaction; #if WITH_EDITOR // Create the mapping of old->new components and notify the editor of the replacements TMap<UObject*, UObject*> OldToNewComponentMapping; TInlineComponentArray<UActorComponent*> NewComponents; GetComponents(NewComponents); for (auto NewComp : NewComponents) { const FName NewCompName = NewComp->GetFName(); if (DestroyedComponentsByName.Contains(NewCompName)) { OldToNewComponentMapping.Add(DestroyedComponentsByName[NewCompName], NewComp); } } if (GEditor && (OldToNewComponentMapping.Num() > 0)) { GEditor->NotifyToolsOfObjectReplacement(OldToNewComponentMapping); } if (ActorTransactionAnnotation) { CurrentTransactionAnnotation = NULL; } else #endif { delete InstanceDataCache; } } }
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(); } }
//------------------------------------------------------------------------------ void FBlueprintNativeCodeGenUtils::GenerateCppCode(UObject* Obj, TSharedPtr<FString> OutHeaderSource, TSharedPtr<FString> OutCppSource) { auto UDEnum = Cast<UUserDefinedEnum>(Obj); auto UDStruct = Cast<UUserDefinedStruct>(Obj); auto BPGC = Cast<UClass>(Obj); auto InBlueprintObj = BPGC ? Cast<UBlueprint>(BPGC->ClassGeneratedBy) : Cast<UBlueprint>(Obj); OutHeaderSource->Empty(); OutCppSource->Empty(); if (InBlueprintObj) { if (EBlueprintStatus::BS_Error == InBlueprintObj->Status) { UE_LOG(LogBlueprintCodeGen, Error, TEXT("Cannot convert \"%s\". It has errors."), *InBlueprintObj->GetPathName()); return; } check(InBlueprintObj->GetOutermost() != GetTransientPackage()); if (!ensureMsgf(InBlueprintObj->GeneratedClass, TEXT("Invalid generated class for %s"), *InBlueprintObj->GetName())) { return; } check(OutHeaderSource.IsValid()); check(OutCppSource.IsValid()); FDisableGatheringDataOnScope DisableFib; const FString TempPackageName = FString::Printf(TEXT("/Temp/__TEMP_BP__/%s"), *InBlueprintObj->GetName()); UPackage* TempPackage = CreatePackage(nullptr, *TempPackageName); check(TempPackage); ON_SCOPE_EXIT { TempPackage->RemoveFromRoot(); TempPackage->MarkPendingKill(); }; UBlueprint* DuplicateBP = nullptr; { FBlueprintDuplicationScopeFlags BPDuplicationFlags( FBlueprintDuplicationScopeFlags::NoExtraCompilation | FBlueprintDuplicationScopeFlags::TheSameTimelineGuid); DuplicateBP = DuplicateObject<UBlueprint>(InBlueprintObj, TempPackage, *InBlueprintObj->GetName()); } ensure((nullptr != DuplicateBP->GeneratedClass) && (InBlueprintObj->GeneratedClass != DuplicateBP->GeneratedClass)); ON_SCOPE_EXIT { DuplicateBP->RemoveFromRoot(); DuplicateBP->MarkPendingKill(); }; IBlueprintCompilerCppBackendModule& CodeGenBackend = (IBlueprintCompilerCppBackendModule&)IBlueprintCompilerCppBackendModule::Get(); CodeGenBackend.GetOriginalClassMap().Add(*DuplicateBP->GeneratedClass, *InBlueprintObj->GeneratedClass); { TSharedPtr<FBlueprintCompileReinstancer> Reinstancer = FBlueprintCompileReinstancer::Create(DuplicateBP->GeneratedClass); IKismetCompilerInterface& Compiler = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>(KISMET_COMPILER_MODULENAME); TGuardValue<bool> GuardTemplateNameFlag(GCompilingBlueprint, true); FCompilerResultsLog Results; FKismetCompilerOptions CompileOptions; CompileOptions.CompileType = EKismetCompileType::Cpp; CompileOptions.OutCppSourceCode = OutCppSource; CompileOptions.OutHeaderSourceCode = OutHeaderSource; Compiler.CompileBlueprint(DuplicateBP, CompileOptions, Results); Compiler.RemoveBlueprintGeneratedClasses(DuplicateBP); } if (EBlueprintType::BPTYPE_Interface == DuplicateBP->BlueprintType && OutCppSource.IsValid()) { OutCppSource->Empty(); // ugly temp hack } } else if ((UDEnum || UDStruct) && OutHeaderSource.IsValid())