1f9b284de2
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
370 lines
17 KiB
C++
370 lines
17 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 "EditorCommon.h"
|
|
|
|
#include <AzCore/Serialization/Utils.h>
|
|
#include <AzCore/Component/ComponentApplicationBus.h>
|
|
#include <AzCore/Asset/AssetManager.h>
|
|
#include <AzCore/std/parallel/thread.h>
|
|
|
|
#include <QMessageBox>
|
|
#include <QApplication>
|
|
|
|
namespace SerializeHelpers
|
|
{
|
|
bool s_initializedReflection = false;
|
|
|
|
//! Simple helper class for serializing a vector of entities, their child entities
|
|
//! and their slice instance information. This is only serialized for the undo system
|
|
//! or the clipboard so it does not require version conversion.
|
|
//! m_entities is the set of entities that were chosen to be serialized (e.g. by a copy
|
|
//! command), m_childEntities are all the descendants of the entities in m_entities.
|
|
class SerializedElementContainer
|
|
{
|
|
public:
|
|
virtual ~SerializedElementContainer() { }
|
|
AZ_CLASS_ALLOCATOR(SerializedElementContainer, AZ::SystemAllocator, 0);
|
|
AZ_RTTI(SerializedElementContainer, "{4A12708F-7EC5-4F56-827A-6E67C3C49B3D}");
|
|
AZStd::vector<AZ::Entity*> m_entities;
|
|
AZStd::vector<AZ::Entity*> m_childEntities;
|
|
EntityRestoreVec m_entityRestoreInfos;
|
|
EntityRestoreVec m_childEntityRestoreInfos;
|
|
};
|
|
|
|
|
|
namespace Internal
|
|
{
|
|
void DetachEntitiesIfFullSliceInstanceNotBeingCopied(SerializedElementContainer& entitiesToSerialize)
|
|
{
|
|
// We simplify this a bit in the same was as SandboxIntegrationManager::CloneSelection and instead say that,
|
|
// unless every entity in the slice instance is being copied we do not preserve the connection to the slice.
|
|
|
|
// make a set of all the entities in entitiesToSerialize
|
|
AZStd::unordered_set<AZ::EntityId> allEntitiesBeingCopied;
|
|
for (auto entity : entitiesToSerialize.m_entities)
|
|
{
|
|
allEntitiesBeingCopied.insert(entity->GetId());
|
|
}
|
|
for (auto entity : entitiesToSerialize.m_childEntities)
|
|
{
|
|
allEntitiesBeingCopied.insert(entity->GetId());
|
|
}
|
|
|
|
// Create a local function to avoid duplicating code because we have two sets of lists to process
|
|
auto CheckEntities = [allEntitiesBeingCopied](AZStd::vector<AZ::Entity*>& entities, EntityRestoreVec& entityRestoreInfos)
|
|
{
|
|
for (int i = 0; i < entities.size(); ++i)
|
|
{
|
|
AZ::Entity* entity = entities[i];
|
|
|
|
AZ::SliceComponent::SliceInstanceAddress sliceAddress;
|
|
AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, entity->GetId(),
|
|
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
|
|
|
|
if (sliceAddress.IsValid())
|
|
{
|
|
const AZ::SliceComponent::EntityList& entitiesInSlice = sliceAddress.GetInstance()->GetInstantiated()->m_entities;
|
|
for (AZ::Entity* entityInSlice : entitiesInSlice)
|
|
{
|
|
if (allEntitiesBeingCopied.end() == allEntitiesBeingCopied.find(entityInSlice->GetId()))
|
|
{
|
|
// at least one of the entities in the slice instance is not in the set being copied so
|
|
// remove this entities connection to the slice
|
|
entityRestoreInfos[i].m_assetId.SetInvalid();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
CheckEntities(entitiesToSerialize.m_entities, entitiesToSerialize.m_entityRestoreInfos);
|
|
CheckEntities(entitiesToSerialize.m_childEntities, entitiesToSerialize.m_childEntityRestoreInfos);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void InitializeReflection()
|
|
{
|
|
// Reflect the SerializedElementContainer on first use.
|
|
if (!s_initializedReflection)
|
|
{
|
|
AZ::SerializeContext* context = nullptr;
|
|
EBUS_EVENT_RESULT(context, AZ::ComponentApplicationBus, GetSerializeContext);
|
|
AZ_Assert(context, "No serialize context");
|
|
|
|
context->Class<SerializedElementContainer>()
|
|
->Version(1)
|
|
->Field("Entities", &SerializedElementContainer::m_entities)
|
|
->Field("ChildEntities", &SerializedElementContainer::m_childEntities)
|
|
->Field("RestoreInfos", &SerializedElementContainer::m_entityRestoreInfos)
|
|
->Field("ChildRestoreInfos", &SerializedElementContainer::m_childEntityRestoreInfos);
|
|
|
|
s_initializedReflection = true;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void RestoreSerializedElements(
|
|
AZ::EntityId canvasEntityId,
|
|
AZ::Entity* parent,
|
|
AZ::Entity* insertBefore,
|
|
UiEditorEntityContext* entityContext,
|
|
const AZStd::string& xml,
|
|
bool isCopyOperation,
|
|
LyShine::EntityArray* cumulativeListOfCreatedEntities)
|
|
{
|
|
LyShine::EntityArray listOfNewlyCreatedTopLevelElements;
|
|
LyShine::EntityArray listOfAllCreatedEntities;
|
|
EntityRestoreVec entityRestoreInfos;
|
|
|
|
LoadElementsFromXmlString(
|
|
canvasEntityId,
|
|
xml.c_str(),
|
|
isCopyOperation,
|
|
parent,
|
|
insertBefore,
|
|
listOfNewlyCreatedTopLevelElements,
|
|
listOfAllCreatedEntities,
|
|
entityRestoreInfos);
|
|
|
|
if (listOfNewlyCreatedTopLevelElements.empty())
|
|
{
|
|
// This happens when the serialization version numbers DON'T match.
|
|
QMessageBox(QMessageBox::Critical,
|
|
"Error",
|
|
QString("Failed to restore elements. The clipboard serialization format is incompatible."),
|
|
QMessageBox::Ok, QApplication::activeWindow()).exec();
|
|
|
|
// Nothing more to do.
|
|
return;
|
|
}
|
|
|
|
// This is for error handling only. In the case of an error RestoreSliceEntity will delete the
|
|
// entity. We need to know when this has happened. So we record all the entityIds here and check
|
|
// them afterwards.
|
|
AzToolsFramework::EntityIdList idsOfNewlyCreatedTopLevelElements;
|
|
for (auto entity : listOfNewlyCreatedTopLevelElements)
|
|
{
|
|
idsOfNewlyCreatedTopLevelElements.push_back(entity->GetId());
|
|
}
|
|
|
|
// Now we need to restore the slice info for all the created elements
|
|
// In the case of a copy operation we need to generate new sliceInstanceIds. We use a map so that
|
|
// all entities copied from the same slice instance will end up in the same new slice instance.
|
|
AZStd::unordered_map<AZ::SliceComponent::SliceInstanceId, AZ::SliceComponent::SliceInstanceId> sliceInstanceMap;
|
|
for (int i=0; i < listOfAllCreatedEntities.size(); ++i)
|
|
{
|
|
AZ::Entity* entity = listOfAllCreatedEntities[i];
|
|
|
|
AZ::SliceComponent::EntityRestoreInfo& sliceRestoreInfo = entityRestoreInfos[i];
|
|
|
|
if (sliceRestoreInfo)
|
|
{
|
|
if (isCopyOperation)
|
|
{
|
|
// if a copy we can't use the instanceId of the instance that was copied from so generate
|
|
// a new one - but only want one new id per original slice instance - so we use a map to
|
|
// keep track of which instance Ids we have created new Ids for.
|
|
auto iter = sliceInstanceMap.find(sliceRestoreInfo.m_instanceId);
|
|
if (iter == sliceInstanceMap.end())
|
|
{
|
|
sliceInstanceMap[sliceRestoreInfo.m_instanceId] = AZ::SliceComponent::SliceInstanceId::CreateRandom();
|
|
}
|
|
|
|
sliceRestoreInfo.m_instanceId = sliceInstanceMap[sliceRestoreInfo.m_instanceId];
|
|
}
|
|
|
|
EBUS_EVENT_ID(entityContext->GetContextId(), UiEditorEntityContextRequestBus, RestoreSliceEntity, entity, sliceRestoreInfo);
|
|
}
|
|
else
|
|
{
|
|
entityContext->AddUiEntity(entity);
|
|
}
|
|
}
|
|
|
|
// If we are restoring slice entities and any of the entities are the first to be using that slice
|
|
// then we have to wait for that slice to be reloaded. We need to wait because we can't create hierarchy
|
|
// items for entities before they are recreated. We have tried trying to solve this by deferring the creation
|
|
// of the hierarchy items on a queue but that gets really complicated because this function is called
|
|
// in several situations. It also seems problematic to return control to the user - they could add or
|
|
// delete more items while we are waiting for the assets to load.
|
|
if (AZ::Data::AssetManager::IsReady())
|
|
{
|
|
bool areRequestsPending = false;
|
|
EBUS_EVENT_ID_RESULT(areRequestsPending, entityContext->GetContextId(), UiEditorEntityContextRequestBus, HasPendingRequests);
|
|
while (areRequestsPending)
|
|
{
|
|
AZ::Data::AssetManager::Instance().DispatchEvents();
|
|
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(50));
|
|
EBUS_EVENT_ID_RESULT(areRequestsPending, entityContext->GetContextId(), UiEditorEntityContextRequestBus, HasPendingRequests);
|
|
}
|
|
}
|
|
|
|
// Because RestoreSliceEntity can delete the entity we have some recovery code here that will
|
|
// create a new list of top level entities excluding any that have been removed.
|
|
// An error should already have been reported in this case so we don't report it again.
|
|
LyShine::EntityArray validatedListOfNewlyCreatedTopLevelElements;
|
|
for (auto entityId : idsOfNewlyCreatedTopLevelElements)
|
|
{
|
|
AZ::Entity* entity = nullptr;
|
|
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId);
|
|
|
|
// Only add it to the validated list if the entity still exists
|
|
if (entity)
|
|
{
|
|
validatedListOfNewlyCreatedTopLevelElements.push_back(entity);
|
|
}
|
|
}
|
|
|
|
// Fixup the created entities, we do this before adding the top level element to the parent so that
|
|
// MakeUniqueChileName works correctly
|
|
EBUS_EVENT_ID(canvasEntityId,
|
|
UiCanvasBus,
|
|
FixupCreatedEntities,
|
|
validatedListOfNewlyCreatedTopLevelElements,
|
|
isCopyOperation,
|
|
parent);
|
|
|
|
// Now add the top-level created elements as children of the parent
|
|
for (auto entity : validatedListOfNewlyCreatedTopLevelElements)
|
|
{
|
|
// add this new entity as a child of the parent (insertionPoint or root)
|
|
EBUS_EVENT_ID(canvasEntityId,
|
|
UiCanvasBus,
|
|
AddElement,
|
|
entity,
|
|
parent,
|
|
insertBefore);
|
|
}
|
|
|
|
// if a list of entities was passed then add all the entities that we added
|
|
// to the list
|
|
if (cumulativeListOfCreatedEntities)
|
|
{
|
|
cumulativeListOfCreatedEntities->push_back(validatedListOfNewlyCreatedTopLevelElements);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZStd::string SaveElementsToXmlString(const LyShine::EntityArray& elements, AZ::SliceComponent* rootSlice, bool isCopyOperation, AZStd::unordered_set<AZ::Data::AssetId>& referencedSliceAssets)
|
|
{
|
|
InitializeReflection();
|
|
|
|
// The easiest way to write multiple elements to a stream is to create class that contains them
|
|
// that has an allocator. SerializedElementContainer exists for this purpose.
|
|
// It saves/loads two lists. One is a list of top-level elements, the second is a list of all of
|
|
// the children of those elements.
|
|
SerializedElementContainer entitiesToSerialize;
|
|
for (auto element : elements)
|
|
{
|
|
entitiesToSerialize.m_entities.push_back(element);
|
|
|
|
// add the slice restore info for this top level element
|
|
AZ::SliceComponent::EntityRestoreInfo sliceRestoreInfo;
|
|
rootSlice->GetEntityRestoreInfo(element->GetId(), sliceRestoreInfo);
|
|
entitiesToSerialize.m_entityRestoreInfos.push_back(sliceRestoreInfo);
|
|
|
|
LyShine::EntityArray childElements;
|
|
EBUS_EVENT_ID(element->GetId(), UiElementBus, FindDescendantElements,
|
|
[]([[maybe_unused]] const AZ::Entity* entity) { return true; },
|
|
childElements);
|
|
|
|
for (auto child : childElements)
|
|
{
|
|
entitiesToSerialize.m_childEntities.push_back(child);
|
|
|
|
// add the slice restore info for this child element
|
|
rootSlice->GetEntityRestoreInfo(child->GetId(), sliceRestoreInfo);
|
|
entitiesToSerialize.m_childEntityRestoreInfos.push_back(sliceRestoreInfo);
|
|
}
|
|
}
|
|
|
|
// if this is a copy operation we could be copying some elements in a slice instance without copying the root element
|
|
// of the slice instance. This would cause issues. So we need to detect that situation and change the entity restore infos
|
|
// to remove the slice instance association.
|
|
if (isCopyOperation)
|
|
{
|
|
Internal::DetachEntitiesIfFullSliceInstanceNotBeingCopied(entitiesToSerialize);
|
|
}
|
|
|
|
// now record the referenced slice assets
|
|
for (auto& sliceRestoreInfo : entitiesToSerialize.m_entityRestoreInfos)
|
|
{
|
|
if (sliceRestoreInfo)
|
|
{
|
|
referencedSliceAssets.insert(sliceRestoreInfo.m_assetId);
|
|
}
|
|
}
|
|
|
|
for (auto& sliceRestoreInfo : entitiesToSerialize.m_childEntityRestoreInfos)
|
|
{
|
|
if (sliceRestoreInfo)
|
|
{
|
|
referencedSliceAssets.insert(sliceRestoreInfo.m_assetId);
|
|
}
|
|
}
|
|
|
|
// save the entitiesToSerialize structure to the buffer
|
|
AZStd::string charBuffer;
|
|
AZ::IO::ByteContainerStream<AZStd::string> charStream(&charBuffer);
|
|
[[maybe_unused]] bool success = AZ::Utils::SaveObjectToStream(charStream, AZ::ObjectStream::ST_XML, &entitiesToSerialize);
|
|
AZ_Assert(success, "Failed to serialize elements to XML");
|
|
|
|
return charBuffer;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void LoadElementsFromXmlString(
|
|
[[maybe_unused]] AZ::EntityId canvasEntityId,
|
|
const AZStd::string& string,
|
|
bool makeNewIDs,
|
|
[[maybe_unused]] AZ::Entity* insertionPoint,
|
|
[[maybe_unused]] AZ::Entity* insertBefore,
|
|
LyShine::EntityArray& listOfCreatedTopLevelElements,
|
|
LyShine::EntityArray& listOfAllCreatedElements,
|
|
EntityRestoreVec& entityRestoreInfos)
|
|
{
|
|
InitializeReflection();
|
|
|
|
AZ::IO::ByteContainerStream<const AZStd::string> charStream(&string);
|
|
SerializedElementContainer* unserializedEntities =
|
|
AZ::Utils::LoadObjectFromStream<SerializedElementContainer>(charStream);
|
|
|
|
// If we want new IDs then generate them and fixup all references within the list of entities
|
|
if (makeNewIDs)
|
|
{
|
|
AZ::SerializeContext* context = nullptr;
|
|
EBUS_EVENT_RESULT(context, AZ::ComponentApplicationBus, GetSerializeContext);
|
|
AZ_Assert(context, "No serialization context found");
|
|
|
|
AZ::SliceComponent::EntityIdToEntityIdMap entityIdMap;
|
|
AZ::IdUtils::Remapper<AZ::EntityId>::GenerateNewIdsAndFixRefs(unserializedEntities, entityIdMap, context);
|
|
}
|
|
|
|
// copy unserializedEntities into the return output list of top-level entities
|
|
for (auto newEntity : unserializedEntities->m_entities)
|
|
{
|
|
listOfCreatedTopLevelElements.push_back(newEntity);
|
|
}
|
|
|
|
// we also return a list of all of the created entities (not just top level ones)
|
|
listOfAllCreatedElements.insert(listOfAllCreatedElements.end(),
|
|
unserializedEntities->m_entities.begin(), unserializedEntities->m_entities.end());
|
|
listOfAllCreatedElements.insert(listOfAllCreatedElements.end(),
|
|
unserializedEntities->m_childEntities.begin(), unserializedEntities->m_childEntities.end());
|
|
|
|
// return a list of the EntityRestoreInfos in the same order
|
|
entityRestoreInfos.insert(entityRestoreInfos.end(),
|
|
unserializedEntities->m_entityRestoreInfos.begin(), unserializedEntities->m_entityRestoreInfos.end());
|
|
entityRestoreInfos.insert(entityRestoreInfos.end(),
|
|
unserializedEntities->m_childEntityRestoreInfos.begin(), unserializedEntities->m_childEntityRestoreInfos.end());
|
|
}
|
|
|
|
} // namespace EntityHelpers
|