Skip to content

Instantly share code, notes, and snippets.

@DannyGoodayle
Created February 22, 2024 18:21
Show Gist options
  • Save DannyGoodayle/57f22aef7fca46812064f249eda6524d to your computer and use it in GitHub Desktop.
Save DannyGoodayle/57f22aef7fca46812064f249eda6524d to your computer and use it in GitHub Desktop.
Potential fix for reinstancing issue on Smart Objects
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