SerializeHelpers.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "EditorCommon.h"
  9. #include <AzCore/Serialization/Utils.h>
  10. #include <AzCore/Component/ComponentApplicationBus.h>
  11. #include <AzCore/Asset/AssetManager.h>
  12. #include <AzCore/std/parallel/thread.h>
  13. #include <QMessageBox>
  14. #include <QApplication>
  15. namespace SerializeHelpers
  16. {
  17. bool s_initializedReflection = false;
  18. //! Simple helper class for serializing a vector of entities, their child entities
  19. //! and their slice instance information. This is only serialized for the undo system
  20. //! or the clipboard so it does not require version conversion.
  21. //! m_entities is the set of entities that were chosen to be serialized (e.g. by a copy
  22. //! command), m_childEntities are all the descendants of the entities in m_entities.
  23. class SerializedElementContainer
  24. {
  25. public:
  26. virtual ~SerializedElementContainer() { }
  27. AZ_CLASS_ALLOCATOR(SerializedElementContainer, AZ::SystemAllocator);
  28. AZ_RTTI(SerializedElementContainer, "{4A12708F-7EC5-4F56-827A-6E67C3C49B3D}");
  29. AZStd::vector<AZ::Entity*> m_entities;
  30. AZStd::vector<AZ::Entity*> m_childEntities;
  31. EntityRestoreVec m_entityRestoreInfos;
  32. EntityRestoreVec m_childEntityRestoreInfos;
  33. };
  34. namespace Internal
  35. {
  36. void DetachEntitiesIfFullSliceInstanceNotBeingCopied(SerializedElementContainer& entitiesToSerialize)
  37. {
  38. // We simplify this a bit in the same was as SandboxIntegrationManager::CloneSelection and instead say that,
  39. // unless every entity in the slice instance is being copied we do not preserve the connection to the slice.
  40. // make a set of all the entities in entitiesToSerialize
  41. AZStd::unordered_set<AZ::EntityId> allEntitiesBeingCopied;
  42. for (auto entity : entitiesToSerialize.m_entities)
  43. {
  44. allEntitiesBeingCopied.insert(entity->GetId());
  45. }
  46. for (auto entity : entitiesToSerialize.m_childEntities)
  47. {
  48. allEntitiesBeingCopied.insert(entity->GetId());
  49. }
  50. // Create a local function to avoid duplicating code because we have two sets of lists to process
  51. auto CheckEntities = [allEntitiesBeingCopied](AZStd::vector<AZ::Entity*>& entities, EntityRestoreVec& entityRestoreInfos)
  52. {
  53. for (int i = 0; i < entities.size(); ++i)
  54. {
  55. AZ::Entity* entity = entities[i];
  56. AZ::SliceComponent::SliceInstanceAddress sliceAddress;
  57. AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, entity->GetId(),
  58. &AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
  59. if (sliceAddress.IsValid())
  60. {
  61. const AZ::SliceComponent::EntityList& entitiesInSlice = sliceAddress.GetInstance()->GetInstantiated()->m_entities;
  62. for (AZ::Entity* entityInSlice : entitiesInSlice)
  63. {
  64. if (allEntitiesBeingCopied.end() == allEntitiesBeingCopied.find(entityInSlice->GetId()))
  65. {
  66. // at least one of the entities in the slice instance is not in the set being copied so
  67. // remove this entities connection to the slice
  68. entityRestoreInfos[i].m_assetId.SetInvalid();
  69. break;
  70. }
  71. }
  72. }
  73. }
  74. };
  75. CheckEntities(entitiesToSerialize.m_entities, entitiesToSerialize.m_entityRestoreInfos);
  76. CheckEntities(entitiesToSerialize.m_childEntities, entitiesToSerialize.m_childEntityRestoreInfos);
  77. }
  78. }
  79. ////////////////////////////////////////////////////////////////////////////////////////////////////
  80. void InitializeReflection()
  81. {
  82. // Reflect the SerializedElementContainer on first use.
  83. if (!s_initializedReflection)
  84. {
  85. AZ::SerializeContext* context = nullptr;
  86. AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  87. AZ_Assert(context, "No serialize context");
  88. context->Class<SerializedElementContainer>()
  89. ->Version(1)
  90. ->Field("Entities", &SerializedElementContainer::m_entities)
  91. ->Field("ChildEntities", &SerializedElementContainer::m_childEntities)
  92. ->Field("RestoreInfos", &SerializedElementContainer::m_entityRestoreInfos)
  93. ->Field("ChildRestoreInfos", &SerializedElementContainer::m_childEntityRestoreInfos);
  94. s_initializedReflection = true;
  95. }
  96. }
  97. ////////////////////////////////////////////////////////////////////////////////////////////////////
  98. void RestoreSerializedElements(
  99. AZ::EntityId canvasEntityId,
  100. AZ::Entity* parent,
  101. AZ::Entity* insertBefore,
  102. UiEditorEntityContext* entityContext,
  103. const AZStd::string& xml,
  104. bool isCopyOperation,
  105. LyShine::EntityArray* cumulativeListOfCreatedEntities)
  106. {
  107. LyShine::EntityArray listOfNewlyCreatedTopLevelElements;
  108. LyShine::EntityArray listOfAllCreatedEntities;
  109. EntityRestoreVec entityRestoreInfos;
  110. LoadElementsFromXmlString(
  111. canvasEntityId,
  112. xml.c_str(),
  113. isCopyOperation,
  114. parent,
  115. insertBefore,
  116. listOfNewlyCreatedTopLevelElements,
  117. listOfAllCreatedEntities,
  118. entityRestoreInfos);
  119. if (listOfNewlyCreatedTopLevelElements.empty())
  120. {
  121. // This happens when the serialization version numbers DON'T match.
  122. QMessageBox(QMessageBox::Critical,
  123. "Error",
  124. QString("Failed to restore elements. The clipboard serialization format is incompatible."),
  125. QMessageBox::Ok, QApplication::activeWindow()).exec();
  126. // Nothing more to do.
  127. return;
  128. }
  129. // This is for error handling only. In the case of an error RestoreSliceEntity will delete the
  130. // entity. We need to know when this has happened. So we record all the entityIds here and check
  131. // them afterwards.
  132. AzToolsFramework::EntityIdList idsOfNewlyCreatedTopLevelElements;
  133. for (auto entity : listOfNewlyCreatedTopLevelElements)
  134. {
  135. idsOfNewlyCreatedTopLevelElements.push_back(entity->GetId());
  136. }
  137. // Now we need to restore the slice info for all the created elements
  138. // In the case of a copy operation we need to generate new sliceInstanceIds. We use a map so that
  139. // all entities copied from the same slice instance will end up in the same new slice instance.
  140. AZStd::unordered_map<AZ::SliceComponent::SliceInstanceId, AZ::SliceComponent::SliceInstanceId> sliceInstanceMap;
  141. for (int i=0; i < listOfAllCreatedEntities.size(); ++i)
  142. {
  143. AZ::Entity* entity = listOfAllCreatedEntities[i];
  144. AZ::SliceComponent::EntityRestoreInfo& sliceRestoreInfo = entityRestoreInfos[i];
  145. if (sliceRestoreInfo)
  146. {
  147. if (isCopyOperation)
  148. {
  149. // if a copy we can't use the instanceId of the instance that was copied from so generate
  150. // a new one - but only want one new id per original slice instance - so we use a map to
  151. // keep track of which instance Ids we have created new Ids for.
  152. auto iter = sliceInstanceMap.find(sliceRestoreInfo.m_instanceId);
  153. if (iter == sliceInstanceMap.end())
  154. {
  155. sliceInstanceMap[sliceRestoreInfo.m_instanceId] = AZ::SliceComponent::SliceInstanceId::CreateRandom();
  156. }
  157. sliceRestoreInfo.m_instanceId = sliceInstanceMap[sliceRestoreInfo.m_instanceId];
  158. }
  159. UiEditorEntityContextRequestBus::Event(
  160. entityContext->GetContextId(), &UiEditorEntityContextRequestBus::Events::RestoreSliceEntity, entity, sliceRestoreInfo);
  161. }
  162. else
  163. {
  164. entityContext->AddUiEntity(entity);
  165. }
  166. }
  167. // If we are restoring slice entities and any of the entities are the first to be using that slice
  168. // then we have to wait for that slice to be reloaded. We need to wait because we can't create hierarchy
  169. // items for entities before they are recreated. We have tried trying to solve this by deferring the creation
  170. // of the hierarchy items on a queue but that gets really complicated because this function is called
  171. // in several situations. It also seems problematic to return control to the user - they could add or
  172. // delete more items while we are waiting for the assets to load.
  173. if (AZ::Data::AssetManager::IsReady())
  174. {
  175. bool areRequestsPending = false;
  176. UiEditorEntityContextRequestBus::EventResult(
  177. areRequestsPending, entityContext->GetContextId(), &UiEditorEntityContextRequestBus::Events::HasPendingRequests);
  178. while (areRequestsPending)
  179. {
  180. AZ::Data::AssetManager::Instance().DispatchEvents();
  181. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(50));
  182. UiEditorEntityContextRequestBus::EventResult(
  183. areRequestsPending, entityContext->GetContextId(), &UiEditorEntityContextRequestBus::Events::HasPendingRequests);
  184. }
  185. }
  186. // Because RestoreSliceEntity can delete the entity we have some recovery code here that will
  187. // create a new list of top level entities excluding any that have been removed.
  188. // An error should already have been reported in this case so we don't report it again.
  189. LyShine::EntityArray validatedListOfNewlyCreatedTopLevelElements;
  190. for (auto entityId : idsOfNewlyCreatedTopLevelElements)
  191. {
  192. AZ::Entity* entity = nullptr;
  193. AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
  194. // Only add it to the validated list if the entity still exists
  195. if (entity)
  196. {
  197. validatedListOfNewlyCreatedTopLevelElements.push_back(entity);
  198. }
  199. }
  200. // Fixup the created entities, we do this before adding the top level element to the parent so that
  201. // MakeUniqueChileName works correctly
  202. UiCanvasBus::Event(
  203. canvasEntityId,
  204. &UiCanvasBus::Events::FixupCreatedEntities,
  205. validatedListOfNewlyCreatedTopLevelElements,
  206. isCopyOperation,
  207. parent);
  208. // Now add the top-level created elements as children of the parent
  209. for (auto entity : validatedListOfNewlyCreatedTopLevelElements)
  210. {
  211. // add this new entity as a child of the parent (insertionPoint or root)
  212. UiCanvasBus::Event(canvasEntityId, &UiCanvasBus::Events::AddElement, entity, parent, insertBefore);
  213. }
  214. // if a list of entities was passed then add all the entities that we added
  215. // to the list
  216. if (cumulativeListOfCreatedEntities)
  217. {
  218. cumulativeListOfCreatedEntities->insert(
  219. cumulativeListOfCreatedEntities->end(),
  220. validatedListOfNewlyCreatedTopLevelElements.begin(),
  221. validatedListOfNewlyCreatedTopLevelElements.end());
  222. }
  223. }
  224. ////////////////////////////////////////////////////////////////////////////////////////////////////
  225. AZStd::string SaveElementsToXmlString(const LyShine::EntityArray& elements, AZ::SliceComponent* rootSlice, bool isCopyOperation, AZStd::unordered_set<AZ::Data::AssetId>& referencedSliceAssets)
  226. {
  227. InitializeReflection();
  228. // The easiest way to write multiple elements to a stream is to create class that contains them
  229. // that has an allocator. SerializedElementContainer exists for this purpose.
  230. // It saves/loads two lists. One is a list of top-level elements, the second is a list of all of
  231. // the children of those elements.
  232. SerializedElementContainer entitiesToSerialize;
  233. for (auto element : elements)
  234. {
  235. entitiesToSerialize.m_entities.push_back(element);
  236. // add the slice restore info for this top level element
  237. AZ::SliceComponent::EntityRestoreInfo sliceRestoreInfo;
  238. rootSlice->GetEntityRestoreInfo(element->GetId(), sliceRestoreInfo);
  239. entitiesToSerialize.m_entityRestoreInfos.push_back(sliceRestoreInfo);
  240. LyShine::EntityArray childElements;
  241. UiElementBus::Event(
  242. element->GetId(),
  243. &UiElementBus::Events::FindDescendantElements,
  244. []([[maybe_unused]] const AZ::Entity* entity)
  245. {
  246. return true;
  247. },
  248. childElements);
  249. for (auto child : childElements)
  250. {
  251. entitiesToSerialize.m_childEntities.push_back(child);
  252. // add the slice restore info for this child element
  253. rootSlice->GetEntityRestoreInfo(child->GetId(), sliceRestoreInfo);
  254. entitiesToSerialize.m_childEntityRestoreInfos.push_back(sliceRestoreInfo);
  255. }
  256. }
  257. // if this is a copy operation we could be copying some elements in a slice instance without copying the root element
  258. // of the slice instance. This would cause issues. So we need to detect that situation and change the entity restore infos
  259. // to remove the slice instance association.
  260. if (isCopyOperation)
  261. {
  262. Internal::DetachEntitiesIfFullSliceInstanceNotBeingCopied(entitiesToSerialize);
  263. }
  264. // now record the referenced slice assets
  265. for (auto& sliceRestoreInfo : entitiesToSerialize.m_entityRestoreInfos)
  266. {
  267. if (sliceRestoreInfo)
  268. {
  269. referencedSliceAssets.insert(sliceRestoreInfo.m_assetId);
  270. }
  271. }
  272. for (auto& sliceRestoreInfo : entitiesToSerialize.m_childEntityRestoreInfos)
  273. {
  274. if (sliceRestoreInfo)
  275. {
  276. referencedSliceAssets.insert(sliceRestoreInfo.m_assetId);
  277. }
  278. }
  279. // save the entitiesToSerialize structure to the buffer
  280. AZStd::string charBuffer;
  281. AZ::IO::ByteContainerStream<AZStd::string> charStream(&charBuffer);
  282. [[maybe_unused]] bool success = AZ::Utils::SaveObjectToStream(charStream, AZ::ObjectStream::ST_XML, &entitiesToSerialize);
  283. AZ_Assert(success, "Failed to serialize elements to XML");
  284. return charBuffer;
  285. }
  286. ////////////////////////////////////////////////////////////////////////////////////////////////////
  287. void LoadElementsFromXmlString(
  288. [[maybe_unused]] AZ::EntityId canvasEntityId,
  289. const AZStd::string& string,
  290. bool makeNewIDs,
  291. [[maybe_unused]] AZ::Entity* insertionPoint,
  292. [[maybe_unused]] AZ::Entity* insertBefore,
  293. LyShine::EntityArray& listOfCreatedTopLevelElements,
  294. LyShine::EntityArray& listOfAllCreatedElements,
  295. EntityRestoreVec& entityRestoreInfos)
  296. {
  297. InitializeReflection();
  298. AZ::IO::ByteContainerStream<const AZStd::string> charStream(&string);
  299. SerializedElementContainer* unserializedEntities =
  300. AZ::Utils::LoadObjectFromStream<SerializedElementContainer>(charStream);
  301. // If we want new IDs then generate them and fixup all references within the list of entities
  302. if (makeNewIDs)
  303. {
  304. AZ::SerializeContext* context = nullptr;
  305. AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  306. AZ_Assert(context, "No serialization context found");
  307. AZ::SliceComponent::EntityIdToEntityIdMap entityIdMap;
  308. AZ::IdUtils::Remapper<AZ::EntityId>::GenerateNewIdsAndFixRefs(unserializedEntities, entityIdMap, context);
  309. }
  310. // copy unserializedEntities into the return output list of top-level entities
  311. for (auto newEntity : unserializedEntities->m_entities)
  312. {
  313. listOfCreatedTopLevelElements.push_back(newEntity);
  314. }
  315. // we also return a list of all of the created entities (not just top level ones)
  316. listOfAllCreatedElements.insert(listOfAllCreatedElements.end(),
  317. unserializedEntities->m_entities.begin(), unserializedEntities->m_entities.end());
  318. listOfAllCreatedElements.insert(listOfAllCreatedElements.end(),
  319. unserializedEntities->m_childEntities.begin(), unserializedEntities->m_childEntities.end());
  320. // return a list of the EntityRestoreInfos in the same order
  321. entityRestoreInfos.insert(entityRestoreInfos.end(),
  322. unserializedEntities->m_entityRestoreInfos.begin(), unserializedEntities->m_entityRestoreInfos.end());
  323. entityRestoreInfos.insert(entityRestoreInfos.end(),
  324. unserializedEntities->m_childEntityRestoreInfos.begin(), unserializedEntities->m_childEntityRestoreInfos.end());
  325. }
  326. } // namespace EntityHelpers