Files
o3de/Code/Framework/AzToolsFramework/AzToolsFramework/Commands/EntityStateCommand.cpp
T
Esteban Papp 1f9b284de2 Merge branch 'development' into cmake/SPEC-7179
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>

# Conflicts:
#	Code/Editor/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin_precompiled.h
#	Code/Editor/Plugins/EditorCommon/EditorCommon_precompiled.h
#	Code/Editor/Plugins/EditorCommon/stdafx.cpp
#	Code/Editor/Plugins/FFMPEGPlugin/FFMPEGPlugin_precompiled.h
#	Code/Editor/Plugins/PerforcePlugin/PerforcePlugin_precompiled.h
#	Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsTool_precompiled.h
#	Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFramework_precompiled.h
#	Code/Tools/AssetProcessor/native/precompiled.h
#	Code/Tools/Standalone/StandaloneTools_precompiled.h
#	Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer_precompiled.h
#	Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessing_precompiled.h
#	Gems/Atom/RHI/DX12/Code/Source/RHI/Atom_RHI_DX12_precompiled.h
#	Gems/Atom/RHI/Metal/Code/Include/Platform/Mac/Atom_RHI_Metal_precompiled_Platform.h
#	Gems/Atom/RHI/Metal/Code/Include/Platform/iOS/Atom_RHI_Metal_precompiled_Platform.h
#	Gems/Atom/RHI/Metal/Code/Source/Atom_RHI_Metal_precompiled.h
#	Gems/Atom/RHI/Metal/Code/atom_rhi_metal_common_files.cmake
#	Gems/Atom/RHI/Null/Code/Source/Atom_RHI_Null_precompiled.h
#	Gems/Atom/RHI/Null/Code/atom_rhi_null_common_files.cmake
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Android/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Linux/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Mac/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Windows/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Source/Atom_RHI_Vulkan_precompiled.h
#	Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp
#	Gems/Atom/RHI/Vulkan/Code/atom_rhi_vulkan_common_files.cmake
#	Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont_precompiled.h
#	Gems/Blast/Code/Source/StdAfx.cpp
#	Gems/Camera/Code/Source/Camera_precompiled.h
#	Gems/EMotionFX/Code/Source/EMotionFX_precompiled.h
#	Gems/FastNoise/Code/Source/FastNoise_precompiled.h
#	Gems/Gestures/Code/Source/Gestures_precompiled.h
#	Gems/GradientSignal/Code/Source/GradientSignal_precompiled.h
#	Gems/GraphCanvas/Code/precompiled.h
#	Gems/ImGui/Code/Source/ImGui_precompiled.h
#	Gems/InAppPurchases/Code/Source/InAppPurchases_precompiled.h
#	Gems/LmbrCentral/Code/Source/LmbrCentral_precompiled.h
#	Gems/LmbrCentral/Code/Tests/ShapeGeometryUtilTest.cpp
#	Gems/LyShine/Code/Editor/UiCanvasEditor_precompiled.h
#	Gems/LyShine/Code/Source/Animation/LyShine_precompiled.h
#	Gems/LyShine/Code/Source/LyShine_precompiled.h
#	Gems/LyShineExamples/Code/Source/LyShineExamples_precompiled.h
#	Gems/Maestro/Code/Source/Cinematics/Maestro_precompiled.h
#	Gems/Maestro/Code/Source/Maestro_precompiled.h
#	Gems/MessagePopup/Code/Source/MessagePopup_precompiled.h
#	Gems/Metastream/Code/Source/Metastream_precompiled.h
#	Gems/Microphone/Code/Source/Microphone_precompiled.h
#	Gems/Multiplayer/Code/Source/Multiplayer_precompiled.h
#	Gems/PhysX/Code/NumericalMethods/Source/NumericalMethods_precompiled.h
#	Gems/PhysX/Code/Source/PhysXUnsupported_precompiled.h
#	Gems/PhysX/Code/Source/PhysX_precompiled.h
#	Gems/PhysX/Code/physx_unsupported_files.cmake
#	Gems/PhysXDebug/Code/Source/PhysXDebugUnsupported_precompiled.h
#	Gems/PhysXDebug/Code/Source/PhysXDebug_precompiled.h
#	Gems/ScriptCanvas/Code/Editor/precompiled.h
#	Gems/ScriptCanvas/Code/Source/precompiled.h
#	Gems/ScriptCanvasDeveloper/Code/Source/precompiled.h
#	Gems/ScriptCanvasPhysics/Code/Source/ScriptCanvasPhysics_precompiled.h
#	Gems/ScriptEvents/Code/Source/precompiled.h
#	Gems/ScriptEvents/Code/Tests/Editor/EditorTests.cpp
#	Gems/ScriptedEntityTweener/Code/Source/ScriptedEntityTweener_precompiled.h
#	Gems/SliceFavorites/Code/Source/SliceFavorites_precompiled.h
#	Gems/StartingPointCamera/Code/Source/StartingPointCamera_precompiled.h
#	Gems/StartingPointInput/Code/Source/StartingPointInput_precompiled.h
#	Gems/StartingPointMovement/Code/Source/StartingPointMovement_precompiled.h
#	Gems/SurfaceData/Code/Source/SurfaceData_precompiled.h
#	Gems/TextureAtlas/Code/Source/TextureAtlas_precompiled.h
#	Gems/TickBusOrderViewer/Code/Source/TickBusOrderViewer_precompiled.h
#	Gems/Twitch/Code/Source/Twitch_precompiled.h
#	Gems/VirtualGamepad/Code/Source/VirtualGamepad_precompiled.h
#	Gems/WhiteBox/Code/Source/WhiteBoxUnsupported_precompiled.h
#	Gems/WhiteBox/Code/Source/WhiteBox_precompiled.h
2021-07-16 15:42:37 -07:00

290 lines
13 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EntityStateCommand.h"
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzFramework/Slice/SliceEntityBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/Slice/SliceMetadataEntityContextBus.h>
#include "PreemptiveUndoCache.h"
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
// until we have some growing memory array:
#define STATIC_BUFFERSIZE (1024 * 64)
namespace AzToolsFramework
{
namespace
{
void LoadEntity(void* pClassPtr, const AZ::Uuid& uuidValue, const AZ::SerializeContext* sc, AZ::Entity** destPtr)
{
// entity is loaded.
*destPtr = sc->Cast<AZ::Entity*>(pClassPtr, uuidValue);
AZ_Assert(*destPtr, "Could not cast object to entity!");
}
}
EntityStateCommand::EntityStateCommand(UndoSystem::URCommandID ID, const char* friendlyName)
: UndoSystem::URSequencePoint(friendlyName ? friendlyName : "Entity Change", ID)
{
m_entityID = AZ::EntityId(0);
m_entityContextId = AzFramework::EntityContextId::CreateNull();
m_entityState = AZ::Entity::State::Constructed;
m_isSelected = false;
}
EntityStateCommand::~EntityStateCommand()
{
}
void EntityStateCommand::Capture(AZ::Entity* pSourceEntity, bool captureUndo)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
m_entityID = pSourceEntity->GetId();
EBUS_EVENT_ID_RESULT(m_entityContextId, m_entityID, AzFramework::EntityIdContextQueryBus, GetOwningContextId);
EBUS_EVENT_RESULT(m_isSelected, AzToolsFramework::ToolsApplicationRequests::Bus, IsSelected, m_entityID);
AZ::SliceComponent::EntityRestoreInfo& sliceRestoreInfo = captureUndo ? m_undoSliceRestoreInfo : m_redoSliceRestoreInfo;
sliceRestoreInfo = AZ::SliceComponent::EntityRestoreInfo();
AZ_Assert(pSourceEntity, "Null entity for undo");
AZ_Assert(PreemptiveUndoCache::Get(), "You need a pre-emptive undo cache instance to exist for this to work.");
AZ_Assert((!captureUndo) || (m_undoState.empty()), "You can't capture undo more than once");
AZ::SerializeContext* sc = nullptr;
EBUS_EVENT_RESULT(sc, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(sc, "Serialization context not found!");
m_entityState = pSourceEntity->GetState();
// The entity is loose, so we capture it directly.
if (captureUndo)
{
m_undoState = PreemptiveUndoCache::Get()->Retrieve(m_entityID);
if (m_undoState.empty())
{
PreemptiveUndoCache::Get()->UpdateCache(m_entityID);
m_undoState = PreemptiveUndoCache::Get()->Retrieve(m_entityID);
}
AZ_Assert(!m_undoState.empty(), "Invalid empty size for the undo state of an entity.");
}
else
{
m_redoState.clear();
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8> > ms(&m_redoState);
AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&ms, *sc, AZ::ObjectStream::ST_BINARY);
if (!objStream->WriteClass(pSourceEntity))
{
AZ_Assert(false, "Unable to serialize entity for undo/redo. ObjectStream::WriteClass() returned an error.");
}
if (!objStream->Finalize())
{
AZ_Assert(false, "Unable to serialize entity for undo/redo. ObjectStream::Finalize() returned an error.");
}
}
// If slice-owned, extract the data we need to restore it.
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddr;
AzFramework::SliceEntityRequestBus::EventResult(sliceInstanceAddr, m_entityID,
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
if (sliceInstanceAddr.IsValid())
{
AZ::SliceComponent* rootSlice = nullptr;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
AZ_Assert(rootSlice, "Failed to retrieve editor root slice.");
rootSlice->GetEntityRestoreInfo(m_entityID, sliceRestoreInfo);
}
}
void EntityStateCommand::RestoreEntity(const AZ::u8* buffer, AZStd::size_t bufferSizeBytes, const AZ::SliceComponent::EntityRestoreInfo& sliceRestoreInfo) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
AZ_Assert(buffer, "No data to undo!");
AZ_Assert(bufferSizeBytes, "Undo data is empty.");
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "Serialization context not found!");
AZ::IO::MemoryStream memoryStream(reinterpret_cast<const char*>(buffer), bufferSizeBytes);
// If restoring to a slice, keep a reference to the slice asset so it isn't released when the entity
// is deleted, only to immediately reload upon restoring.
AZ::Data::Asset<AZ::SliceAsset> asset;
if (sliceRestoreInfo)
{
asset = AZ::Data::AssetManager::Instance().FindAsset(sliceRestoreInfo.m_assetId, AZ::Data::AssetLoadBehavior::Default);
}
// We have to delete the entity. If it's currently selected, make sure we re-select after re-creating.
AzToolsFramework::EntityIdList selectedEntities;
EBUS_EVENT_RESULT(selectedEntities, ToolsApplicationRequests::Bus, GetSelectedEntities);
AZ::Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, m_entityID);
EntityStateCommandNotificationBus::Event(m_entityID, &EntityStateCommandNotificationBus::Events::PreRestore);
bool isSliceMetadataEntity = false;
SliceMetadataEntityContextRequestBus::BroadcastResult(isSliceMetadataEntity, &SliceMetadataEntityContextRequestBus::Events::IsSliceMetadataEntity, m_entityID);
AZ::ObjectStream::InplaceLoadRootInfoCB inplaceLoadRootInfoCB;
if (isSliceMetadataEntity)
{
// Instead of deleting the entity, dtor it which will remove and disconnect it entirely
entity->~Entity();
// placement new to default the entity
entity = new(entity) AZ::Entity();
// Set up a callback so we in-place load over this entity instead of creating it new
inplaceLoadRootInfoCB = [entity](void** rootAddress, const AZ::SerializeContext::ClassData** /*classData*/, const AZ::Uuid& /*classId*/, AZ::SerializeContext* /*context*/)
{
*rootAddress = entity;
};
}
else
{
// Normal entities will be deleted and loaded anew
//EBUS_EVENT(AZ::ComponentApplicationBus, DeleteEntity, m_entityID);
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationBus::Events::DeleteEntity, m_entityID);
}
bool success = AZ::ObjectStream::LoadBlocking(&memoryStream, *serializeContext,
AZStd::bind(&LoadEntity, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, &entity),
AZ::ObjectStream::FilterDescriptor(AZ::Data::AssetFilterNoAssetLoading),
inplaceLoadRootInfoCB);
(void)success;
AZ_Assert(success, "Unable to serialize entity for undo/redo");
AZ_Assert(entity, "Unable to create entity");
if (entity)
{
if (sliceRestoreInfo)
{
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::RestoreSliceEntity,
entity, sliceRestoreInfo, AzToolsFramework::SliceEntityRestoreType::Deleted);
}
else
{
if (!m_entityContextId.IsNull() && !isSliceMetadataEntity)
{
EBUS_EVENT_ID(m_entityContextId, AzFramework::EntityContextRequestBus, AddEntity, entity);
}
if (m_entityState == AZ::Entity::State::Init || m_entityState == AZ::Entity::State::Active)
{
if (entity->GetState() == AZ::Entity::State::Constructed)
{
entity->Init();
}
}
if (m_entityState == AZ::Entity::State::Active)
{
if (entity->GetState() == AZ::Entity::State::Init)
{
entity->Activate();
}
}
}
PreemptiveUndoCache::Get()->UpdateCache(m_entityID);
if (m_isSelected)
{
if (AZStd::find(selectedEntities.begin(), selectedEntities.end(), m_entityID) == selectedEntities.end())
{
selectedEntities.push_back(m_entityID);
}
}
// Signal that an Entity was created since ComponentApplicationBus::Events::DeleteEntity causes
// EditorEntityContextNotification::OnEditorEntityDeleted to be fired, we need to have parity
// However, we don't send the notification here if we are restoring a slice, because the RestoreSliceEntity
// action gets queued so the Entity won't be in a good state yet until the restoration is complete.
AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequestBus::Events::GetEditorEntityContextId);
if (editorEntityContextId == m_entityContextId && !sliceRestoreInfo)
{
EditorEntityContextNotificationBus::Broadcast(&EditorEntityContextNotification::OnEditorEntityCreated, entity->GetId());
}
EntityStateCommandNotificationBus::Event(m_entityID, &EntityStateCommandNotificationBus::Events::PostRestore, entity);
}
EBUS_EVENT(ToolsApplicationRequests::Bus, SetSelectedEntities, selectedEntities);
}
void EntityStateCommand::Undo()
{
RestoreEntity(m_undoState.data(), m_undoState.size(), m_undoSliceRestoreInfo);
}
void EntityStateCommand::Redo()
{
RestoreEntity(m_redoState.data(), m_redoState.size(), m_redoSliceRestoreInfo);
}
EntityDeleteCommand::EntityDeleteCommand(UndoSystem::URCommandID ID)
: EntityStateCommand(ID, "Delete Entity")
{
}
void EntityDeleteCommand::Capture(AZ::Entity* pSourceEntity)
{
PreemptiveUndoCache::Get()->UpdateCache(pSourceEntity->GetId());
EntityStateCommand::Capture(pSourceEntity, true);
}
void EntityDeleteCommand::Undo()
{
RestoreEntity(m_undoState.data(), m_undoState.size(), m_undoSliceRestoreInfo);
}
void EntityDeleteCommand::Redo()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
EBUS_EVENT(AZ::ComponentApplicationBus, DeleteEntity, m_entityID);
PreemptiveUndoCache::Get()->PurgeCache(m_entityID);
}
EntityCreateCommand::EntityCreateCommand(UndoSystem::URCommandID ID)
: EntityStateCommand(ID, "Create Entity")
{
}
void EntityCreateCommand::Capture(AZ::Entity* pSourceEntity)
{
EntityStateCommand::Capture(pSourceEntity, false);
m_isSelected = true;
}
void EntityCreateCommand::Undo()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
EBUS_EVENT(AZ::ComponentApplicationBus, DeleteEntity, m_entityID);
PreemptiveUndoCache::Get()->PurgeCache(m_entityID);
}
void EntityCreateCommand::Redo()
{
RestoreEntity(m_redoState.data(), m_redoState.size(), m_redoSliceRestoreInfo);
}
} // namespace AzToolsFramework