Files
o3de/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoCache.cpp
T
Danilo Aimini fda28bb7b2 LYN-1818 | [USE CASE] Reparenting between different prefab instances by drag/drop in the Outliner (#1088)
* Add the last known parent to the prefab undo cache to detect changes in the owning instance.
Still WIP.

* Progress in handling reparenting. Still WIP, need a change in CreateLink that will be addressed in a separate branch and then merged back.

* A few fixes, reparenting now works with entities. Still working on instances.

* Fix assert crashing the Editor because of the arguments being in the wrong order.

* Handle moving the patches when removing and recreating links when reparenting nested instances.

* Rearrange some code to prevent including instance removal in instance update undo node, as it would be redundant and cause errors in some edge cases.

* Reorder instance reparenting to account for correct order of operation during undo/redo

* Fix order of operations to support multiple operations in one edit (reparenting to non-container entities while changing instance)

* Add function to refresh patches on links to allow aliases to be restored correctly on reparenting.

* Removed RefreshEntityPatchOnLink function. Introduced a simpler way of handling porting patches.

* Removing unnecessary code that was left after testing.

* Minor fixes to naming and comments.

* Restore previous error, no longer printing the failed patch.

* Remove unused includes.

* Restore include removed by mistake.

* Simplified patches retrieval by using internal function. Renamed some internal functions and variables to be more accurate.
2021-06-03 14:02:40 -07:00

190 lines
7.5 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/PrefabUndoCache.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h>
namespace AzToolsFramework
{
namespace Prefab
{
void PrefabUndoCache::Initialize()
{
m_instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
AZ_Assert(m_instanceToTemplateInterface, "PrefabUndoCache - Could not retrieve instance of InstanceToTemplateInterface");
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
AZ_Assert(m_instanceEntityMapperInterface, "PrefabUndoCache - Could not retrieve instance of InstanceEntityMapperInterface");
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (prefabSystemEnabled)
{
// By default, ToolsApplication will register the regular PreemptiveCache as the handler of this interface.
// Since the SettingsRegistry isn't active when ToolsApplication is constructed, and Start and StartCommon
// aren't called during tests, we have to resort to unregistering the Preemptive cache here, and registering
// the PrefabCache in its place. Both caches check if they're registered before unregistering on destroy.
auto preemptiveCache = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
if (preemptiveCache)
{
AZ::Interface<UndoSystem::UndoCacheInterface>::Unregister(preemptiveCache);
}
AZ::Interface<UndoSystem::UndoCacheInterface>::Register(this);
}
}
void PrefabUndoCache::Destroy()
{
if (AZ::Interface<UndoSystem::UndoCacheInterface>::Get() == this)
{
AZ::Interface<UndoSystem::UndoCacheInterface>::Unregister(this);
}
}
void PrefabUndoCache::Validate(const AZ::EntityId& entityId)
{
(void)entityId;
#if defined(ENABLE_UNDOCACHE_CONSISTENCY_CHECKS)
if (entityId == AZ::SystemEntityId)
{
return;
}
PrefabDom oldData;
AZ::EntityId oldParentId;
Retrieve(entityId, oldData, oldParentId);
UpdateCache(entityId);
PrefabDom newData;
AZ::EntityId newParentId;
Retrieve(entityId, newData, newParentId);
if (newData != oldData || oldParentId != newParentId)
{
// display a useful message
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
if (!entity)
{
AZ_Warning(
"Undo", false,
"Undo system wasn't informed about the deletion of entity %p - make sure you call DeleteEntity, instead of "
"directly deleting it.\n",
entityId);
return;
}
AZ_Warning(
"Undo", false,
"Undo system has inconsistent data for entity %p (%s)\n Ensure that SetDirty is "
"being called (or WorldEditor::WorldEditorMessages::Bus, AddDirtyEntity...) for modified entities.",
entityId, entity->GetName().c_str());
}
// Clear out newly generated data and
// replace with original data to ensure debug mode has the same data as profile/release
// in the event of the consistency check failing.
m_entitySavedStates[entityId] = {AZStd::move(oldData), oldParentId};
#endif // ENABLE_UNDOCACHE_CONSISTENCY_CHECKS
}
void PrefabUndoCache::UpdateCache(const AZ::EntityId& entityId)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
if (!entity)
{
AZ_Warning(
"Undo", false,
"PrefabUndoCache was told to update the cache for entity of id %llu, but that entity is not available in "
"FindEntity",
static_cast<AZ::u64>(entityId)
);
return;
}
InstanceOptionalReference instanceOptionalReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
if (!instanceOptionalReference.has_value())
{
AZ_Warning(
"Undo", false,
"PrefabUndoCache was told to update the cache for entity of id %p (%s), but that entity does not have an owning instance.",
entityId, entity->GetName().c_str());
return;
}
AZ::EntityId parentId;
AZ::TransformBus::EventResult(parentId, entityId, &AZ::TransformBus::Events::GetParentId);
// Capture it
PrefabDom entityDom;
m_instanceToTemplateInterface->GenerateDomForEntity(entityDom, *entity);
m_entitySavedStates[entityId] = {AZStd::move(entityDom), parentId};
AZLOG("Prefab Undo", "Correctly updated cache for entity of id %llu (%s)", static_cast<AZ::u64>(entityId), entity->GetName().c_str());
return;
}
void PrefabUndoCache::PurgeCache(const AZ::EntityId& entityId)
{
m_entitySavedStates.erase(entityId);
}
bool PrefabUndoCache::Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom, AZ::EntityId& parentId)
{
auto it = m_entitySavedStates.find(entityId);
if (it == m_entitySavedStates.end())
{
return false;
}
outDom = AZStd::move(m_entitySavedStates[entityId].dom);
parentId = m_entitySavedStates[entityId].parentId;
m_entitySavedStates.erase(entityId);
return true;
}
void PrefabUndoCache::Store(const AZ::EntityId& entityId, PrefabDom&& dom, const AZ::EntityId& parentId)
{
m_entitySavedStates[entityId] = {AZStd::move(dom), parentId};
}
void PrefabUndoCache::Clear()
{
m_entitySavedStates.clear();
}
} // namespace Prefab
} // namespace AzToolsFramework