Fix duplicated actors after reparenting actor in Prefab

#718
This commit is contained in:
Wojtek Figat
2022-07-26 23:04:55 +02:00
parent 9d26004864
commit 8beb732cb9
3 changed files with 29 additions and 19 deletions

View File

@@ -85,7 +85,6 @@ namespace
class PrefabInstanceData
{
public:
// Don't copy or anything
PrefabInstanceData()
{
@@ -111,11 +110,10 @@ public:
}
public:
/// <summary>
/// The prefab instance root actor.
/// </summary>
Actor* TargetActor;
ScriptingObjectReference<Actor> TargetActor;
/// <summary>
/// The cached order in parent of the target actor. Used to preserve it after prefab changes synchronization.
@@ -133,7 +131,6 @@ public:
Dictionary<Guid, int32> PrefabInstanceIdToDataIndex;
public:
/// <summary>
/// Collects all the valid prefab instances to update on prefab data synchronization.
/// </summary>
@@ -187,6 +184,8 @@ void PrefabInstanceData::CollectPrefabInstances(Array<PrefabInstanceData>& prefa
for (int32 instanceIndex = 0; instanceIndex < instances.Count(); instanceIndex++)
{
const auto instance = instances[instanceIndex];
if ((instance->Flags & ObjectFlags::WasMarkedToDelete) != 0)
continue;
if (instance != defaultInstance && targetActor != instance && !targetActor->HasActorInHierarchy(instance))
usedCount++;
}
@@ -196,14 +195,13 @@ void PrefabInstanceData::CollectPrefabInstances(Array<PrefabInstanceData>& prefa
{
// Skip default instance because it will be recreated, skip input actor because it needs just to be linked
Actor* instance = instances[instanceIndex];
if ((instance->Flags & ObjectFlags::WasMarkedToDelete) != 0)
continue;
if (instance != defaultInstance && targetActor != instance && !targetActor->HasActorInHierarchy(instance))
{
auto& data = prefabInstancesData[dataIndex++];
data.TargetActor = instance;
data.OrderInParent = instance->GetOrderInParent();
// Check if actor has not been deleted (the memory access exception could fire)
ASSERT(data.TargetActor->GetID() == data.TargetActor->GetID());
}
}
}
@@ -270,6 +268,8 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array<PrefabInstanceData>& p
// If prefab object root was changed during changes apply then update the TargetActor to point a valid object
Actor* oldTargetActor = instance.TargetActor;
if (!oldTargetActor || (oldTargetActor->Flags & ObjectFlags::WasMarkedToDelete) != 0)
continue;
Actor* newTargetActor = FindActorWithPrefabObjectId(instance.TargetActor, defaultInstance->GetID());
if (!newTargetActor)
{
@@ -449,6 +449,8 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array<PrefabInstanceData>& p
}
Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, actor, nullptr);
Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, actor, nullptr);
if (!actor->IsDuringPlay() && actor->GetParent())
Level::callActorEvent(Level::ActorEventType::OnActorParentChanged, actor, actor->GetParent());
}
}
@@ -1007,9 +1009,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
{
auto obj = sceneObjects.Value->At(i);
if (obj)
{
obj->Initialize();
}
}
// Update transformations
@@ -1218,8 +1218,19 @@ bool Prefab::SyncChangesInternal()
return true;
}
// Recreate default instance but with synchronization since otherwise it might contain old data (eg. nested prefab hierarchy could be changed)
DeleteDefaultInstance();
ObjectsRemovalService::Flush();
{
ScopeLock lock(Locker);
_isCreatingDefaultInstance = true;
_defaultInstance = PrefabManager::SpawnPrefab(this, nullptr, &ObjectsCache, true);
_isCreatingDefaultInstance = false;
}
// Instantiate prefab instance from prefab (default spawning logic)
// Note: it will get any added or removed objects from the nested prefabs
// TODO: try to optimize by using recreated default instance to ApplyAllInternal (will need special path there if apply is done with default instance to unlink it instead of destroying)
const auto targetActor = PrefabManager::SpawnPrefab(this, nullptr, nullptr, true);
if (targetActor == nullptr)
{

View File

@@ -29,7 +29,6 @@ CriticalSection PrefabManager::PrefabsReferencesLocker;
class PrefabManagerService : public EngineService
{
public:
PrefabManagerService()
: EngineService(TEXT("Prefab Manager"), 110)
{
@@ -180,6 +179,13 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
return nullptr;
}
// Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it)
if (withSynchronization)
{
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
}
// Prepare parent linkage for prefab root actor
root->_parent = parent;
if (parent)
@@ -193,13 +199,6 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
obj->Initialize();
}
// Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it)
if (withSynchronization)
{
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
}
// Delete objects without parent or with invalid linkage to the prefab
for (int32 i = 1; i < sceneObjects->Count(); i++)
{

View File

@@ -418,8 +418,8 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab
const Guid jParentId = JsonTools::GetGuid(jData, "ParentID");
//if (jParentId == actorParentId)
// break;
if (jParentId != actorId)
continue;
//if (jParentId != actorId)
// continue;
const Guid jPrefabObjectId = JsonTools::GetGuid(jData, "PrefabObjectID");
if (jPrefabObjectId != prefabObjectId)
continue;