Created
February 22, 2024 18:21
-
-
Save DannyGoodayle/57f22aef7fca46812064f249eda6524d to your computer and use it in GitHub Desktop.
Potential fix for reinstancing issue on Smart Objects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static void ReplaceActorHelper(AActor* OldActor, UClass* OldClass, UObject*& NewUObject, UClass* NewClass, TMap<UObject*, UObject*>& OldToNewInstanceMap, const TMap<UClass*, UClass*>& InOldToNewClassMap, TMap<FSoftObjectPath, UObject*>& ReinstancedObjectsWeakReferenceMap, TMap<UObject*, FActorAttachmentData>& ActorAttachmentData, TArray<FActorReplacementHelper>& ReplacementActors, bool bPreserveRootComponent, bool& bSelectionChanged) | |
{ | |
FVector Location = FVector::ZeroVector; | |
FRotator Rotation = FRotator::ZeroRotator; | |
if (USceneComponent* OldRootComponent = OldActor->GetRootComponent()) | |
{ | |
// We need to make sure that the GetComponentTransform() transform is up to date, but we don't want to run any initialization logic | |
// so we silence the update, cache it off, revert the change (so no events are raised), and then directly update the transform | |
// with the value calculated in ConditionalUpdateComponentToWorld: | |
FScopedMovementUpdate SilenceMovement(OldRootComponent); | |
OldRootComponent->ConditionalUpdateComponentToWorld(); | |
FTransform OldComponentToWorld = OldRootComponent->GetComponentTransform(); | |
SilenceMovement.RevertMove(); | |
OldRootComponent->SetComponentToWorld(OldComponentToWorld); | |
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* const* MappedClass = InOldToNewClassMap.Find(OldActor->GetClass()); | |
UClass* SpawnClass = MappedClass ? *MappedClass : NewClass; | |
const EObjectFlags FlagMask = RF_Public | RF_ArchetypeObject | RF_Transactional | RF_Transient | RF_TextExportTransient | RF_InheritableComponentTemplate | RF_Standalone; | |
FActorSpawnParameters SpawnInfo; | |
SpawnInfo.OverrideLevel = ActorLevel; | |
SpawnInfo.Owner = OldActor->GetOwner(); | |
SpawnInfo.Instigator = OldActor->GetInstigator(); | |
SpawnInfo.Template = NewArchetype; | |
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; | |
SpawnInfo.bDeferConstruction = true; | |
SpawnInfo.Name = OldActor->GetFName(); | |
SpawnInfo.ObjectFlags |= OldActor->GetFlags() & FlagMask; | |
if (!OldActor->IsListedInSceneOutliner()) | |
{ | |
SpawnInfo.bHideFromSceneOutliner = true; | |
} | |
// Make sure to reuse the same external package if any | |
SpawnInfo.bCreateActorPackage = false; | |
SpawnInfo.OverridePackage = OldActor->GetExternalPackage(); | |
SpawnInfo.OverrideActorGuid = OldActor->GetActorGuid(); | |
// Don't go through AActor::Rename here because we aren't changing outers (the actor's level) and we also don't want to reset loaders | |
// if the actor is using an external package. We really just want to rename that actor out of the way so we can spawn the new one in | |
// the exact same package, keeping the package name intact. | |
OldActor->UObject::Rename(nullptr, OldActor->GetOuter(), REN_DoNotDirty | REN_DontCreateRedirectors | REN_ForceNoResetLoaders); | |
const bool bPackageNewlyCreated = OldActor->GetExternalPackage() && OldActor->GetExternalPackage()->HasAnyPackageFlags(PKG_NewlyCreated); | |
// OSE BEGIN - Unregister native components BEFORE new actor registers components to resolve SmartObject registration issue | |
OldActor->UnregisterAllComponents(); | |
// OSE END - Unregister native components BEFORE new actor registers components to resolve SmartObject registration issue | |
AActor* NewActor = nullptr; | |
{ | |
FMakeClassSpawnableOnScope TemporarilySpawnable(SpawnClass); | |
NewActor = World->SpawnActor(SpawnClass, &Location, &Rotation, SpawnInfo); | |
} | |
if (OldActor->CurrentTransactionAnnotation.IsValid()) | |
{ | |
NewActor->CurrentTransactionAnnotation = OldActor->CurrentTransactionAnnotation; | |
} | |
check(NewActor != nullptr); | |
// When Spawning an actor that has an external package the package can be PKG_NewlyCreated. | |
// We need to remove this flag if the package didn't have that flag prior to the SpawnActor. | |
// This means we are reinstancing an actor that was already saved on disk. | |
if (UPackage* ExternalPackage = NewActor->GetExternalPackage()) | |
{ | |
if (!bPackageNewlyCreated && ExternalPackage->HasAnyPackageFlags(PKG_NewlyCreated)) | |
{ | |
ExternalPackage->ClearPackageFlags(PKG_NewlyCreated); | |
} | |
} | |
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). | |
FActorAttachmentData& CurrentAttachmentData = ActorAttachmentData.FindChecked(OldActor); | |
ReplacementActors.Add(FActorReplacementHelper(NewActor, OldActor, MoveTemp(CurrentAttachmentData))); | |
ActorAttachmentData.Remove(OldActor); | |
ReinstancedObjectsWeakReferenceMap.Add(OldActor, NewUObject); | |
OldActor->DestroyConstructedComponents(); // don't want to serialize components from the old actor | |
// OSE BEGIN - Unregister native components BEFORE new actor registers components to resolve SmartObject registration issue | |
// Unregister native components so we don't copy any sub-components they generate for themselves (like UCameraComponent does) | |
//OldActor->UnregisterAllComponents(); | |
// OSE END - Unregister native components BEFORE new actor registers components to resolve SmartObject registration issue | |
// Unregister any native components, might have cached state based on properties we are going to overwrite | |
NewActor->UnregisterAllComponents(); | |
UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; | |
Params.bPreserveRootComponent = bPreserveRootComponent; | |
PRAGMA_DISABLE_DEPRECATION_WARNINGS | |
// Leaving this enabled for now for the purposes of the aggressive replacement auditing | |
Params.bAggressiveDefaultSubobjectReplacement = true; | |
PRAGMA_ENABLE_DEPRECATION_WARNINGS | |
Params.bNotifyObjectReplacement = true; | |
UEngine::CopyPropertiesForUnrelatedObjects(OldActor, NewActor, Params); | |
// reset properties/streams | |
NewActor->ResetPropertiesForConstruction(); | |
// Only register the components if the world is already initialized | |
if (World->bIsWorldInitialized) | |
{ | |
// register native components | |
NewActor->RegisterAllComponents(); | |
} | |
// | |
// clean up the old actor (unselect it, remove it from the world, etc.)... | |
if (OldActor->IsSelected()) | |
{ | |
if(GEditor) | |
{ | |
GEditor->SelectActor(OldActor, /*bInSelected =*/false, /*bNotify =*/false); | |
} | |
bSelectionChanged = true; | |
} | |
if (GEditor) | |
{ | |
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>(); | |
if (Layers) | |
{ | |
Layers->DisassociateActorFromLayers(OldActor); | |
} | |
} | |
OldToNewInstanceMap.Add(OldActor, NewActor); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment