SliceConverter.cpp 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  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 <AzCore/Asset/AssetManager.h>
  9. #include <AzCore/Component/Entity.h>
  10. #include <AzCore/Component/EntityUtils.h>
  11. #include <AzCore/Debug/Trace.h>
  12. #include <AzCore/JSON/prettywriter.h>
  13. #include <AzCore/Module/Module.h>
  14. #include <AzCore/Serialization/EditContext.h>
  15. #include <AzCore/Serialization/SerializeContext.h>
  16. #include <AzCore/Serialization/Utils.h>
  17. #include <AzCore/Serialization/Json/JsonSerialization.h>
  18. #include <AzCore/Settings/SettingsRegistryImpl.h>
  19. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  20. #include <AzCore/std/containers/unordered_set.h>
  21. #include <AzCore/Utils/Utils.h>
  22. #include <AzFramework/Archive/IArchive.h>
  23. #include <AzFramework/Asset/AssetSystemBus.h>
  24. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  25. #include <AzToolsFramework/Prefab/PrefabDomUtils.h>
  26. #include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
  27. #include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
  28. #include <AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h>
  29. #include <AzToolsFramework/Entity/EditorEntityContextBus.h>
  30. #include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
  31. #include <AzToolsFramework/ToolsComponents/TransformComponent.h>
  32. #include <Application.h>
  33. #include <SliceConverter.h>
  34. #include <SliceConverterEditorEntityContextComponent.h>
  35. #include <Utilities.h>
  36. // SliceConverter reads in a slice file (saved in an ObjectStream format), instantiates it, creates a prefab out of the data,
  37. // and saves the prefab in a JSON format. This can be used for one-time migrations of slices or slice-based levels to prefabs.
  38. //
  39. // If the slice contains legacy data, it will print out warnings / errors about the data that couldn't be serialized.
  40. // The prefab will be generated without that data.
  41. namespace AZ
  42. {
  43. namespace SerializeContextTools
  44. {
  45. bool SliceConverter::ConvertSliceFiles(Application& application)
  46. {
  47. using namespace AZ::JsonSerializationResult;
  48. const AZ::CommandLine* commandLine = application.GetAzCommandLine();
  49. if (!commandLine)
  50. {
  51. AZ_Error("SerializeContextTools", false, "Command line not available.");
  52. return false;
  53. }
  54. JsonSerializerSettings convertSettings;
  55. convertSettings.m_keepDefaults = commandLine->HasSwitch("keepdefaults");
  56. convertSettings.m_registrationContext = application.GetJsonRegistrationContext();
  57. convertSettings.m_serializeContext = application.GetSerializeContext();
  58. if (!convertSettings.m_serializeContext)
  59. {
  60. AZ_Error("Convert-Slice", false, "No serialize context found.");
  61. return false;
  62. }
  63. if (!convertSettings.m_registrationContext)
  64. {
  65. AZ_Error("Convert-Slice", false, "No json registration context found.");
  66. return false;
  67. }
  68. // Connect to the Asset Processor so that we can get the correct source path to any nested slice references.
  69. if (!ConnectToAssetProcessor())
  70. {
  71. AZ_Error("Convert-Slice", false, " Failed to connect to the Asset Processor.\n");
  72. return false;
  73. }
  74. AZStd::string logggingScratchBuffer;
  75. SetupLogging(logggingScratchBuffer, convertSettings.m_reporting, *commandLine);
  76. bool isDryRun = commandLine->HasSwitch("dryrun");
  77. JsonDeserializerSettings verifySettings;
  78. verifySettings.m_registrationContext = application.GetJsonRegistrationContext();
  79. verifySettings.m_serializeContext = application.GetSerializeContext();
  80. SetupLogging(logggingScratchBuffer, verifySettings.m_reporting, *commandLine);
  81. bool result = true;
  82. rapidjson::StringBuffer scratchBuffer;
  83. // For slice conversion, disable the EditorEntityContextComponent logic that activates entities on creation.
  84. // This prevents a lot of error messages and crashes during conversion due to lack of full environment and subsystem setup.
  85. AzToolsFramework::SliceConverterEditorEntityContextComponent::DisableOnContextEntityLogic();
  86. // Loop through the list of requested files and convert them.
  87. AZStd::vector<AZStd::string> fileList = Utilities::ReadFileListFromCommandLine(application, "files");
  88. for (AZStd::string& filePath : fileList)
  89. {
  90. bool convertResult = ConvertSliceFile(convertSettings.m_serializeContext, filePath, isDryRun);
  91. result = result && convertResult;
  92. // Clear out all registered prefab templates between each top-level file that gets processed.
  93. auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
  94. for (auto templateId : m_createdTemplateIds)
  95. {
  96. // We don't just want to call RemoveAllTemplates() because the root template should remain between file conversions.
  97. prefabSystemComponentInterface->RemoveTemplate(templateId);
  98. }
  99. m_aliasIdMapper.clear();
  100. m_createdTemplateIds.clear();
  101. }
  102. DisconnectFromAssetProcessor();
  103. return result;
  104. }
  105. bool SliceConverter::ConvertSliceFile(AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun)
  106. {
  107. /* To convert a slice file, we read the input file in via ObjectStream, then use the "class ready" callback to convert
  108. * the data in memory to a Prefab.
  109. * If the input file is a level file (.ly), we actually need to load the level slice file ("levelentities.editor_xml") from
  110. * within the level file, which effectively is a zip file of the level slice file and a bunch of legacy level files that won't
  111. * be converted, since the systems that would use them no longer exist.
  112. */
  113. bool result = true;
  114. bool packOpened = false;
  115. auto archiveInterface = AZ::Interface<AZ::IO::IArchive>::Get();
  116. AZ::IO::Path outputPath = slicePath;
  117. outputPath.ReplaceExtension("prefab");
  118. AZ_Printf("Convert-Slice", "------------------------------------------------------------------------------------------\n");
  119. AZ_Printf("Convert-Slice", "Converting '%s' to '%s'\n", slicePath.c_str(), outputPath.c_str());
  120. AZ::IO::Path inputPath = slicePath;
  121. auto fileExtension = inputPath.Extension();
  122. if (fileExtension == ".ly")
  123. {
  124. // Special case: for level files, we need to open the .ly zip file and convert the levelentities.editor_xml file
  125. // inside of it. All the other files can be ignored as they are deprecated legacy system files that are no longer
  126. // loaded with prefab-based levels.
  127. packOpened = archiveInterface->OpenPack(slicePath);
  128. inputPath.ReplaceFilename("levelentities.editor_xml");
  129. AZ_Warning("Convert-Slice", packOpened, " '%s' could not be opened as a pack file.\n", slicePath.c_str());
  130. }
  131. else
  132. {
  133. AZ_Warning(
  134. "Convert-Slice", (fileExtension == ".slice"),
  135. " Warning: Only .ly and .slice files are supported, conversion of '%.*s' may not work.\n",
  136. AZ_STRING_ARG(fileExtension.Native()));
  137. }
  138. auto callback = [this, &outputPath, isDryRun](void* classPtr, const Uuid& classId, SerializeContext* context)
  139. {
  140. if (classId != azrtti_typeid<AZ::Entity>())
  141. {
  142. AZ_Printf("Convert-Slice", " File not converted: Slice root is not an entity.\n");
  143. return false;
  144. }
  145. AZ::Entity* rootEntity = reinterpret_cast<AZ::Entity*>(classPtr);
  146. bool convertResult = ConvertSliceToPrefab(context, outputPath, isDryRun, rootEntity);
  147. // Delete the root entity pointer. Otherwise, it will leak itself along with all of the slice asset references held
  148. // within it.
  149. delete rootEntity;
  150. return convertResult;
  151. };
  152. // Read in the slice file and call the callback on completion to convert the read-in slice to a prefab.
  153. // This will also load dependent slice assets, but no other dependent asset types.
  154. // Since we're not actually initializing any of the entities, we don't need any of the non-slice assets to be loaded.
  155. if (!Utilities::InspectSerializedFile(
  156. inputPath.c_str(), serializeContext, callback,
  157. [](const AZ::Data::AssetFilterInfo& filterInfo)
  158. {
  159. return (filterInfo.m_assetType == azrtti_typeid<AZ::SliceAsset>());
  160. }))
  161. {
  162. AZ_Warning("Convert-Slice", false, "Failed to load '%s'. File may not contain an object stream.", inputPath.c_str());
  163. result = false;
  164. }
  165. if (packOpened)
  166. {
  167. [[maybe_unused]] bool closeResult = archiveInterface->ClosePack(slicePath);
  168. AZ_Warning("Convert-Slice", closeResult, "Failed to close '%s'.", slicePath.c_str());
  169. }
  170. AZ_Printf("Convert-Slice", "Finished converting '%s' to '%s'\n", slicePath.c_str(), outputPath.c_str());
  171. AZ_Printf("Convert-Slice", "------------------------------------------------------------------------------------------\n");
  172. return result;
  173. }
  174. bool SliceConverter::ConvertSliceToPrefab(
  175. AZ::SerializeContext* serializeContext, AZ::IO::PathView outputPath, bool isDryRun, AZ::Entity* rootEntity)
  176. {
  177. auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
  178. // Find the slice from the root entity.
  179. SliceComponent* sliceComponent = AZ::EntityUtils::FindFirstDerivedComponent<SliceComponent>(rootEntity);
  180. if (sliceComponent == nullptr)
  181. {
  182. AZ_Printf("Convert-Slice", " File not converted: Root entity did not contain a slice component.\n");
  183. return false;
  184. }
  185. // Get all of the entities from the slice. We're taking ownership of them, so we also remove them from the slice component
  186. // without deleting them.
  187. constexpr bool deleteEntities = false;
  188. constexpr bool removeEmptyInstances = true;
  189. SliceComponent::EntityList sliceEntities = sliceComponent->GetNewEntities();
  190. sliceComponent->RemoveAllEntities(deleteEntities, removeEmptyInstances);
  191. AZ_Printf("Convert-Slice", " Slice contains %zu entities.\n", sliceEntities.size());
  192. // Create an empty Prefab as the start of our conversion.
  193. // The entities are added in a separate step so that we can give them deterministic entity aliases that match their entity Ids
  194. AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> sourceInstance(
  195. prefabSystemComponentInterface->CreatePrefab({}, {}, outputPath));
  196. // Add entities into our prefab.
  197. // In slice->prefab conversions, there's a chicken-and-egg problem that occurs with entity references, so we're initially
  198. // going to add empty dummy entities with the right IDs and aliases.
  199. // The problem is that we can have entities in this root list that have references to nested slice instance entities that we
  200. // haven't created yet, and we will have nested slice entities that need to reference these entities as parents.
  201. // If we create these entities as fully-formed first, they will fail to serialize correctly when adding each nested instance,
  202. // due to the references not pointing to valid entities yet. And if we *wait* to create these and build the nested instances
  203. // first, they'll fail to serialize correctly due to referencing these as parents.
  204. // So our solution is that we'll initially create these entities as empty placeholders with no references, *then* we'll build
  205. // up the nested instances, *then* we'll finish building these entities out.
  206. // prefabPlaceholderEntities will hold onto pointers to the entities we're building up in the prefab. The prefab will own
  207. // the lifetime of them, but we'll use the references here for convenient access.
  208. AZStd::vector<AZ::Entity*> prefabPlaceholderEntities;
  209. // entityAliases will hold onto the alias we want to use for each of those entities. We'll need to use the same alias when
  210. // we replace the entities at the end.
  211. AZStd::vector<AZStd::string> entityAliases;
  212. for (auto& entity : sliceEntities)
  213. {
  214. auto id = entity->GetId();
  215. prefabPlaceholderEntities.emplace_back(aznew AZ::Entity(id));
  216. entityAliases.emplace_back(AZStd::string::format("Entity_%s", id.ToString().c_str()));
  217. sourceInstance->AddEntity(*(prefabPlaceholderEntities.back()), entityAliases.back());
  218. // Save off a mapping of the original slice entity IDs to the new prefab template entity aliases.
  219. // We'll need this mapping for fixing up all the entity references in this slice as well as any nested instances.
  220. auto result = m_aliasIdMapper.emplace(id, SliceEntityMappingInfo(sourceInstance->GetTemplateId(), entityAliases.back()));
  221. if (!result.second)
  222. {
  223. AZ_Printf("Convert-Slice", " Duplicate entity alias -> entity id entries found, conversion may not be successful.\n");
  224. }
  225. }
  226. // Dispatch events here, because prefab creation might trigger asset loads in rare circumstances.
  227. AZ::Data::AssetManager::Instance().DispatchEvents();
  228. // Keep track of the template Id we created, we're going to remove it at the end of slice file conversion to make sure
  229. // the data doesn't stick around between file conversions.
  230. auto templateId = sourceInstance->GetTemplateId();
  231. if (templateId == AzToolsFramework::Prefab::InvalidTemplateId)
  232. {
  233. AZ_Printf("Convert-Slice", " Path error. Path could be invalid, or the prefab may not be loaded in this level.\n");
  234. return false;
  235. }
  236. m_createdTemplateIds.emplace(templateId);
  237. // Save off the the first version of this prefab template with our empty placeholder entities.
  238. // As it saves off, the entities will all change IDs during serialization / propagation, but the aliases will remain the same.
  239. AzToolsFramework::Prefab::PrefabDom prefabDom;
  240. bool storeResult = AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefabDom);
  241. if (storeResult == false)
  242. {
  243. AZ_Printf("Convert-Slice", " Failed to convert prefab instance data to a PrefabDom.\n");
  244. return false;
  245. }
  246. prefabSystemComponentInterface->UpdatePrefabTemplate(templateId, prefabDom);
  247. AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get()->UpdateTemplateInstancesInQueue();
  248. // Dispatch events here, because prefab serialization might trigger asset loads in rare circumstances.
  249. AZ::Data::AssetManager::Instance().DispatchEvents();
  250. // Save off a mapping of the slice's metadata entity ID as well, even though we never converted the entity itself.
  251. // This will help us better detect entity ID mapping errors for nested slice instances.
  252. AZ::Entity* metadataEntity = sliceComponent->GetMetadataEntity();
  253. constexpr bool isMetadataEntity = true;
  254. m_aliasIdMapper.emplace(metadataEntity->GetId(), SliceEntityMappingInfo(templateId, "MetadataEntity", isMetadataEntity));
  255. // Also save off a mapping of the prefab's container entity ID.
  256. m_aliasIdMapper.emplace(sourceInstance->GetContainerEntityId(), SliceEntityMappingInfo(templateId, "ContainerEntity"));
  257. // If this slice has nested slices, we need to loop through those, convert them to prefabs as well, and
  258. // set up the new nesting relationships correctly.
  259. const SliceComponent::SliceList& sliceList = sliceComponent->GetSlices();
  260. AZ_Printf("Convert-Slice", " Slice contains %zu nested slices.\n", sliceList.size());
  261. if (!sliceList.empty())
  262. {
  263. bool nestedSliceResult = ConvertNestedSlices(sliceComponent, sourceInstance.get(), serializeContext, isDryRun);
  264. if (!nestedSliceResult)
  265. {
  266. return false;
  267. }
  268. }
  269. // *After* converting the nested slices, remove our placeholder entities and replace them with the correct ones.
  270. // The placeholder entity IDs will have changed from what we originally created, so we need to make sure our replacement
  271. // entities have the same IDs and aliases as the placeholders so that any instance references that have already been fixed
  272. // up continue to reference the correct entities here.
  273. for (size_t curEntityIdx = 0; curEntityIdx < sliceEntities.size(); curEntityIdx++)
  274. {
  275. auto& sliceEntity = sliceEntities[curEntityIdx];
  276. auto& prefabEntity = prefabPlaceholderEntities[curEntityIdx];
  277. sliceEntity->SetId(prefabEntity->GetId());
  278. }
  279. // Remove and delete our placeholder entities.
  280. // (By using an empty callback on DetachEntities, the unique_ptr will auto-delete the placeholder entities)
  281. sourceInstance->DetachEntities([](AZStd::unique_ptr<AZ::Entity>){});
  282. prefabPlaceholderEntities.clear();
  283. for (size_t curEntityIdx = 0; curEntityIdx < sliceEntities.size(); curEntityIdx++)
  284. {
  285. UpdateCachedTransform(*(sliceEntities[curEntityIdx]));
  286. sourceInstance->AddEntity(*(sliceEntities[curEntityIdx]), entityAliases[curEntityIdx]);
  287. }
  288. // Fix up the container entity to have the proper components and fix up the slice entities to have the proper hierarchy
  289. // with the container as the top-most parent.
  290. AzToolsFramework::Prefab::EntityOptionalReference container = sourceInstance->GetContainerEntity();
  291. FixPrefabEntities(container->get(), sliceEntities);
  292. // Also save off a mapping of the prefab's container entity ID.
  293. m_aliasIdMapper.emplace(sourceInstance->GetContainerEntityId(), SliceEntityMappingInfo(templateId, "ContainerEntity"));
  294. // Remap all of the entity references that exist in these top-level slice entities.
  295. SliceComponent::InstantiatedContainer instantiatedEntities(false);
  296. instantiatedEntities.m_entities = sliceEntities;
  297. RemapIdReferences(m_aliasIdMapper, sourceInstance.get(), sourceInstance.get(), &instantiatedEntities, serializeContext);
  298. // Finally, store the completed slice->prefab conversion back into the template.
  299. storeResult = AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefabDom);
  300. if (storeResult == false)
  301. {
  302. AZ_Printf("Convert-Slice", " Failed to convert prefab instance data to a PrefabDom.\n");
  303. return false;
  304. }
  305. prefabSystemComponentInterface->UpdatePrefabTemplate(templateId, prefabDom);
  306. AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get()->UpdateTemplateInstancesInQueue();
  307. // Dispatch events here, because prefab serialization might trigger asset loads in rare circumstances.
  308. AZ::Data::AssetManager::Instance().DispatchEvents();
  309. if (isDryRun)
  310. {
  311. PrintPrefab(templateId);
  312. return true;
  313. }
  314. else
  315. {
  316. return SavePrefab(outputPath, templateId);
  317. }
  318. }
  319. void SliceConverter::FixPrefabEntities(AZ::Entity& containerEntity, SliceComponent::EntityList& sliceEntities)
  320. {
  321. // Set up the Prefab container entity to be a proper Editor entity. (This logic is normally triggered
  322. // via an EditorRequests EBus in CreatePrefab, but the subsystem that listens for it isn't present in this tool.)
  323. AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
  324. &AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, containerEntity);
  325. if (containerEntity.FindComponent<AzToolsFramework::Prefab::EditorPrefabComponent>() == nullptr)
  326. {
  327. containerEntity.AddComponent(aznew AzToolsFramework::Prefab::EditorPrefabComponent());
  328. }
  329. // Make all the components on the container entity have deterministic component IDs, so that multiple runs of the tool
  330. // on the same slice will produce the same prefab output. We're going to cheat a bit and just use the component type hash
  331. // as the component ID. This would break if we had multiple components of the same type, but that currently doesn't
  332. // happen for the container entity.
  333. auto containerComponents = containerEntity.GetComponents();
  334. for (auto& component : containerComponents)
  335. {
  336. component->SetId(component->GetUnderlyingComponentType().GetHash());
  337. }
  338. // Reparent any root-level slice entities to the container entity.
  339. for (auto entity : sliceEntities)
  340. {
  341. constexpr bool onlySetIfInvalid = true;
  342. SetParentEntity(*entity, containerEntity.GetId(), onlySetIfInvalid);
  343. }
  344. }
  345. bool SliceConverter::ConvertNestedSlices(
  346. SliceComponent* sliceComponent, AzToolsFramework::Prefab::Instance* sourceInstance,
  347. AZ::SerializeContext* serializeContext, bool isDryRun)
  348. {
  349. /* Given a root slice, find all the nested slices and convert them. */
  350. // Get the list of nested slices that this slice uses.
  351. const SliceComponent::SliceList& sliceList = sliceComponent->GetSlices();
  352. auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
  353. // For each nested slice, convert it.
  354. for (auto& slice : sliceList)
  355. {
  356. // Get the nested slice asset. These should already be preloaded due to loading the root asset.
  357. auto sliceAsset = slice.GetSliceAsset();
  358. AZ_Assert(sliceAsset.IsReady(), "slice asset hasn't been loaded yet!");
  359. // The slice list gives us asset IDs, and we need to get to the source path. So first we get the asset path from the ID,
  360. // then we get the source path from the asset path.
  361. AZStd::string processedAssetPath;
  362. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  363. processedAssetPath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, sliceAsset.GetId());
  364. AZStd::string assetPath;
  365. AzToolsFramework::AssetSystemRequestBus::Broadcast(
  366. &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath,
  367. processedAssetPath, assetPath);
  368. if (assetPath.empty())
  369. {
  370. AZ_Warning("Convert-Slice", false,
  371. " Source path for nested slice '%s' could not be found, slice not converted.", processedAssetPath.c_str());
  372. return false;
  373. }
  374. // Check to see if we've already converted this slice at a higher level of slice nesting, or if this is our first
  375. // occurrence and we need to convert it now.
  376. // First, take our absolute slice path and turn it into a project-relative prefab path.
  377. AZ::IO::Path nestedPrefabPath = assetPath;
  378. nestedPrefabPath.ReplaceExtension("prefab");
  379. auto prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
  380. nestedPrefabPath = prefabLoaderInterface->GenerateRelativePath(nestedPrefabPath);
  381. // Now, see if we already have a template ID in memory for it.
  382. AzToolsFramework::Prefab::TemplateId nestedTemplateId =
  383. prefabSystemComponentInterface->GetTemplateIdFromFilePath(nestedPrefabPath);
  384. // If we don't have a template ID yet, convert the nested slice to a prefab and get the template ID.
  385. if (nestedTemplateId == AzToolsFramework::Prefab::InvalidTemplateId)
  386. {
  387. bool nestedSliceResult = ConvertSliceFile(serializeContext, assetPath, isDryRun);
  388. if (!nestedSliceResult)
  389. {
  390. AZ_Warning("Convert-Slice", nestedSliceResult, " Nested slice '%s' could not be converted.", assetPath.c_str());
  391. return false;
  392. }
  393. nestedTemplateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(nestedPrefabPath);
  394. AZ_Assert(nestedTemplateId != AzToolsFramework::Prefab::InvalidTemplateId,
  395. "Template ID for %s is invalid", nestedPrefabPath.c_str());
  396. }
  397. // Get the nested prefab template.
  398. AzToolsFramework::Prefab::TemplateReference nestedTemplate =
  399. prefabSystemComponentInterface->FindTemplate(nestedTemplateId);
  400. // For each slice instance of the nested slice, convert it to a nested prefab instance instead.
  401. auto instances = slice.GetInstances();
  402. AZ_Printf(
  403. "Convert-Slice", "Attaching %zu instances of nested slice '%s'.\n", instances.size(),
  404. nestedPrefabPath.Native().c_str());
  405. // Before processing any further, save off all the known entity IDs from all the instances and how they map back to
  406. // the base nested prefab that they've come from (i.e. this one). As we proceed up the chain of nesting, this will
  407. // build out a hierarchical list of owning instances for each entity that we can trace upwards to know where to add
  408. // the entity into our nested prefab instance.
  409. // This step needs to occur *before* converting the instances themselves, because while converting instances, they
  410. // might have entity ID references that point to other instances. By having the full instance entity ID map in place
  411. // before conversion, we'll be able to fix them up appropriately.
  412. for (auto& instance : instances)
  413. {
  414. AZStd::string instanceAlias = GetInstanceAlias(instance);
  415. UpdateSliceEntityInstanceMappings(instance.GetEntityIdToBaseMap(), instanceAlias);
  416. }
  417. // Now that we have all the entity ID mappings, convert all the instances.
  418. size_t curInstance = 0;
  419. for (auto& instance : instances)
  420. {
  421. AZ_Printf("Convert-Slice", " Converting instance %zu.\n", curInstance++);
  422. bool instanceConvertResult = ConvertSliceInstance(instance, sliceAsset, nestedTemplate, sourceInstance);
  423. if (!instanceConvertResult)
  424. {
  425. return false;
  426. }
  427. }
  428. AZ_Printf(
  429. "Convert-Slice", "Finished attaching %zu instances of nested slice '%s'.\n", instances.size(),
  430. nestedPrefabPath.Native().c_str());
  431. }
  432. return true;
  433. }
  434. AZStd::string SliceConverter::GetInstanceAlias(const AZ::SliceComponent::SliceInstance& instance)
  435. {
  436. // When creating the new instance, we would like to have deterministic instance aliases. Prefabs that depend on this one
  437. // will have patches that reference the alias, so if we reconvert this slice a second time, we would like it to produce
  438. // the same results. To get a deterministic and unique alias, we rely on the slice instance. The slice instance contains
  439. // a map of slice entity IDs to unique instance entity IDs. We'll just consistently use the first entry in the map as the
  440. // unique instance ID.
  441. AZStd::string instanceAlias;
  442. auto entityIdMap = instance.GetEntityIdMap();
  443. if (!entityIdMap.empty())
  444. {
  445. instanceAlias = AZStd::string::format("Instance_%s", entityIdMap.begin()->second.ToString().c_str());
  446. }
  447. else
  448. {
  449. AZ_Error("Convert-Slice", false, " Couldn't create deterministic instance alias.");
  450. instanceAlias = AZStd::string::format("Instance_%s", AZ::Entity::MakeId().ToString().c_str());
  451. }
  452. return instanceAlias;
  453. }
  454. bool SliceConverter::ConvertSliceInstance(
  455. AZ::SliceComponent::SliceInstance& instance,
  456. AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
  457. AzToolsFramework::Prefab::TemplateReference nestedTemplate,
  458. AzToolsFramework::Prefab::Instance* topLevelInstance)
  459. {
  460. /* To convert a slice instance, it's important to understand the similarities and differences between slices and prefabs.
  461. * Both slices and prefabs have the concept of instances of a nested slice/prefab, where each instance can have its own
  462. * set of changed data (transforms, component values, etc).
  463. * For slices, the changed data comes from applying a DataPatch to an instantiated set of entities from the nested slice.
  464. * From prefabs, the changed data comes from Json patches that are applied to the instantiated set of entities from the
  465. * nested prefab. The prefab instance entities also have different IDs than the slice instance entities, so we'll need
  466. * to remap some of them along the way.
  467. * To get from one to the other, we'll need to do the following:
  468. * - Instantiate the nested slice and nested prefab
  469. * - Patch the nested slice instance and fix up the entity ID references
  470. * - Replace the nested prefab instance entities with the fixed-up slice ones
  471. * - Add the nested instance (and the link patch) to the top-level prefab
  472. */
  473. auto instanceToTemplateInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceToTemplateInterface>::Get();
  474. auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
  475. AZStd::string instanceAlias = GetInstanceAlias(instance);
  476. // Create a new unmodified prefab Instance for the nested slice instance.
  477. auto nestedInstance = AZStd::make_unique<AzToolsFramework::Prefab::Instance>(AZStd::move(instanceAlias));
  478. AzToolsFramework::EntityList newEntities;
  479. if (!AzToolsFramework::Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(
  480. *nestedInstance, newEntities, nestedTemplate->get().GetPrefabDom()))
  481. {
  482. AZ_Error(
  483. "Convert-Slice", false, " Failed to load and instantiate nested Prefab Template '%s'.",
  484. nestedTemplate->get().GetFilePath().c_str());
  485. return false;
  486. }
  487. // Save off a mapping of the new nested Instance's container ID
  488. m_aliasIdMapper.emplace(nestedInstance->GetContainerEntityId(),
  489. SliceEntityMappingInfo(nestedInstance->GetTemplateId(), "ContainerEntity"));
  490. // Get the DOM for the unmodified nested instance. This will be used later below for generating the correct patch
  491. // to the top-level template DOM.
  492. AzToolsFramework::Prefab::PrefabDom unmodifiedNestedInstanceDom;
  493. instanceToTemplateInterface->GenerateInstanceDomBySerializing(unmodifiedNestedInstanceDom, *(nestedInstance.get()));
  494. // Instantiate a new instance of the nested slice
  495. SliceComponent* dependentSlice = sliceAsset.Get()->GetComponent();
  496. [[maybe_unused]] AZ::SliceComponent::InstantiateResult instantiationResult = dependentSlice->Instantiate();
  497. AZ_Assert(instantiationResult == AZ::SliceComponent::InstantiateResult::Success, "Failed to instantiate instance");
  498. // Apply the data patch for this instance of the nested slice. This will provide us with a version of the slice's entities
  499. // with all data overrides applied to them.
  500. DataPatch::FlagsMap sourceDataFlags = dependentSlice->GetDataFlagsForInstances().GetDataFlagsForPatching();
  501. DataPatch::FlagsMap targetDataFlags = instance.GetDataFlags().GetDataFlagsForPatching(&instance.GetEntityIdToBaseMap());
  502. AZ::ObjectStream::FilterDescriptor filterDesc(AZ::Data::AssetFilterNoAssetLoading);
  503. AZ::SliceComponent::InstantiatedContainer sourceObjects(false);
  504. dependentSlice->GetEntities(sourceObjects.m_entities);
  505. dependentSlice->GetAllMetadataEntities(sourceObjects.m_metadataEntities);
  506. const DataPatch& dataPatch = instance.GetDataPatch();
  507. auto instantiated =
  508. dataPatch.Apply(&sourceObjects, dependentSlice->GetSerializeContext(), filterDesc, sourceDataFlags, targetDataFlags);
  509. // Replace all the entities in the instance with the new patched ones. To do this, we'll remove all existing entities
  510. // throughout the entire nested hierarchy, then add the new patched entities back in at the appropriate place in the hierarchy.
  511. // (This is easier than trying to figure out what the patched data changes are - we can let the JSON patch handle it for us)
  512. nestedInstance->RemoveEntitiesInHierarchy(
  513. [](const AZStd::unique_ptr<AZ::Entity>&)
  514. {
  515. return true;
  516. });
  517. AZStd::vector<AZStd::pair<AZ::Entity*, AzToolsFramework::Prefab::Instance*>> addedEntityList;
  518. for (auto& entity : instantiated->m_entities)
  519. {
  520. auto entityEntry = m_aliasIdMapper.find(entity->GetId());
  521. if (entityEntry != m_aliasIdMapper.end())
  522. {
  523. auto& mappingStruct = entityEntry->second;
  524. // Starting with the current nested instance, walk downwards through the nesting hierarchy until we're at the
  525. // correct level for this instanced entity ID, then add it. Because we're adding it with the non-instanced alias,
  526. // it doesn't matter what the slice's instanced entity ID is, and the JSON patch will correctly pick up the changes
  527. // we've made for this instance.
  528. AzToolsFramework::Prefab::Instance* addingInstance = nestedInstance.get();
  529. for (auto it = mappingStruct.m_nestedInstanceAliases.rbegin(); it != mappingStruct.m_nestedInstanceAliases.rend(); it++)
  530. {
  531. auto foundInstance = addingInstance->FindNestedInstance(*it);
  532. if (foundInstance.has_value())
  533. {
  534. addingInstance = &(foundInstance->get());
  535. }
  536. else
  537. {
  538. AZ_Assert(false, "Couldn't find nested instance %s", it->c_str());
  539. }
  540. }
  541. UpdateCachedTransform(*entity);
  542. addingInstance->AddEntity(*entity, mappingStruct.m_entityAlias);
  543. addedEntityList.emplace_back(entity, addingInstance);
  544. }
  545. else
  546. {
  547. AZ_Assert(false, "Failed to find entity alias.");
  548. UpdateCachedTransform(*entity);
  549. nestedInstance->AddEntity(*entity);
  550. addedEntityList.emplace_back(entity, nestedInstance.get());
  551. }
  552. }
  553. for (auto& [entity, addingInstance] : addedEntityList)
  554. {
  555. // Fix up the parent hierarchy:
  556. // - Invalid parents need to get set to the container.
  557. // - Valid parents into the top-level instance mean that the nested slice instance is also child-nested under an entity.
  558. // Prefabs handle this type of nesting differently - we need to set the parent to the container, and the container's
  559. // parent to that other instance.
  560. auto containerEntity = addingInstance->GetContainerEntity();
  561. auto containerEntityId = containerEntity->get().GetId();
  562. AzToolsFramework::Components::TransformComponent* transformComponent =
  563. entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
  564. if (transformComponent)
  565. {
  566. bool onlySetIfInvalid = true;
  567. auto parentId = transformComponent->GetParentId();
  568. if (parentId.IsValid())
  569. {
  570. // Look to see if the parent ID exists in a different instance (i.e. an entity in the nested slice is a
  571. // child of an entity in the containing slice). If this case exists, we need to adjust the parents so that
  572. // the child entity connects to the prefab container, and the *container* is the child of the entity in the
  573. // containing slice. (i.e. go from A->B to A->container->B)
  574. auto parentEntry = m_aliasIdMapper.find(parentId);
  575. if (parentEntry != m_aliasIdMapper.end())
  576. {
  577. auto& parentMappingInfo = parentEntry->second;
  578. if (parentMappingInfo.m_templateId != addingInstance->GetTemplateId())
  579. {
  580. if (topLevelInstance->GetTemplateId() == parentMappingInfo.m_templateId)
  581. {
  582. // This entity has a parent from the topLevelInstance, so get its parent ID.
  583. parentId = topLevelInstance->GetEntityId(parentMappingInfo.m_entityAlias);
  584. }
  585. else
  586. {
  587. AzToolsFramework::Prefab::Instance* parentInstance = addingInstance;
  588. while ((parentInstance->GetParentInstance().has_value()) &&
  589. (parentInstance->GetTemplateId() != parentMappingInfo.m_templateId))
  590. {
  591. parentInstance = &(parentInstance->GetParentInstance()->get());
  592. }
  593. if (parentInstance->GetTemplateId() == parentMappingInfo.m_templateId)
  594. {
  595. parentId = parentInstance->GetEntityId(parentMappingInfo.m_entityAlias);
  596. }
  597. else
  598. {
  599. AZ_Assert(false, "Could not find parent instance");
  600. }
  601. }
  602. // Set the container's parent to this entity's parent, and set this entity's parent to the container
  603. SetParentEntity(containerEntity->get(), parentId, false);
  604. onlySetIfInvalid = false;
  605. }
  606. else
  607. {
  608. // If the parent ID is valid and exists inside the same slice instance (i.e. template IDs are equal)
  609. // then it's just a nested entity hierarchy inside the slice and we don't need to adjust anything.
  610. // "onlySetIfInvalid" will still be true, which means we won't change the parent ID below.
  611. }
  612. }
  613. else
  614. {
  615. // If the parent ID is set to something valid, but we can't find it in our ID mapper, something went wrong.
  616. // We'll assert, but don't change the container entity's parent below.
  617. AZ_Assert(false, "Could not find parent entity id: %s", parentId.ToString().c_str());
  618. }
  619. }
  620. SetParentEntity(*entity, containerEntityId, onlySetIfInvalid);
  621. }
  622. }
  623. // Set the container entity of the nested prefab to have the top-level prefab as the parent if it hasn't already gotten
  624. // another entity as its parent.
  625. {
  626. auto containerEntity = nestedInstance->GetContainerEntity();
  627. constexpr bool onlySetIfInvalid = true;
  628. SetParentEntity(containerEntity->get(), topLevelInstance->GetContainerEntityId(), onlySetIfInvalid);
  629. }
  630. // After doing all of the above, run through entity references in any of the patched entities, and fix up the entity IDs to
  631. // match the new ones in our prefabs.
  632. RemapIdReferences(m_aliasIdMapper, topLevelInstance, nestedInstance.get(), instantiated, dependentSlice->GetSerializeContext());
  633. // Add the nested instance itself to the top-level prefab. To do this, we need to add it to our top-level instance,
  634. // create a patch out of it, and patch the top-level prefab template.
  635. AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomBefore;
  636. instanceToTemplateInterface->GenerateInstanceDomBySerializing(topLevelInstanceDomBefore, *topLevelInstance);
  637. // Use the deterministic instance alias for this new instance
  638. AzToolsFramework::Prefab::Instance& addedInstance = topLevelInstance->AddInstance(AZStd::move(nestedInstance));
  639. AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomAfter;
  640. instanceToTemplateInterface->GenerateInstanceDomBySerializing(topLevelInstanceDomAfter, *topLevelInstance);
  641. AzToolsFramework::Prefab::PrefabDom addedInstancePatch;
  642. instanceToTemplateInterface->GeneratePatch(addedInstancePatch, topLevelInstanceDomBefore, topLevelInstanceDomAfter);
  643. instanceToTemplateInterface->PatchTemplate(addedInstancePatch, topLevelInstance->GetTemplateId());
  644. // Get the DOM for the modified nested instance. Now that the data has been fixed up, and the instance has been added
  645. // to the top-level instance, we've got all the changes we need to generate the correct patch.
  646. AzToolsFramework::Prefab::PrefabDom modifiedNestedInstanceDom;
  647. instanceToTemplateInterface->GenerateInstanceDomBySerializing(modifiedNestedInstanceDom, addedInstance);
  648. AzToolsFramework::Prefab::PrefabDom linkPatch;
  649. instanceToTemplateInterface->GeneratePatch(linkPatch, unmodifiedNestedInstanceDom, modifiedNestedInstanceDom);
  650. prefabSystemComponentInterface->CreateLink(
  651. topLevelInstance->GetTemplateId(), addedInstance.GetTemplateId(), addedInstance.GetInstanceAlias(), linkPatch,
  652. AzToolsFramework::Prefab::InvalidLinkId);
  653. prefabSystemComponentInterface->PropagateTemplateChanges(topLevelInstance->GetTemplateId());
  654. AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get()->UpdateTemplateInstancesInQueue();
  655. return true;
  656. }
  657. void SliceConverter::SetParentEntity(const AZ::Entity& entity, const AZ::EntityId& parentId, bool onlySetIfInvalid)
  658. {
  659. AzToolsFramework::Components::TransformComponent* transformComponent =
  660. entity.FindComponent<AzToolsFramework::Components::TransformComponent>();
  661. if (transformComponent)
  662. {
  663. // Only set the parent if we didn't set the onlySetIfInvalid flag, or if we did and the parent is currently invalid
  664. if (!onlySetIfInvalid || !transformComponent->GetParentId().IsValid())
  665. {
  666. transformComponent->SetParent(parentId);
  667. transformComponent->UpdateCachedWorldTransform();
  668. }
  669. }
  670. }
  671. void SliceConverter::UpdateCachedTransform(const AZ::Entity& entity)
  672. {
  673. AzToolsFramework::Components::TransformComponent* transformComponent =
  674. entity.FindComponent<AzToolsFramework::Components::TransformComponent>();
  675. if (transformComponent)
  676. {
  677. transformComponent->UpdateCachedWorldTransform();
  678. }
  679. }
  680. void SliceConverter::PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId)
  681. {
  682. auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
  683. auto prefabTemplate = prefabSystemComponentInterface->FindTemplate(templateId);
  684. auto& prefabDom = prefabTemplate->get().GetPrefabDom();
  685. const AZ::IO::Path& templatePath = prefabTemplate->get().GetFilePath();
  686. rapidjson::StringBuffer prefabBuffer;
  687. rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(prefabBuffer);
  688. prefabDom.Accept(writer);
  689. AZ_Printf("Convert-Slice", "JSON for %s:\n", templatePath.c_str());
  690. // We use Output() to print out the JSON because AZ_Printf has a 4096-character limit.
  691. AZ::Debug::Trace::Instance().Output("", prefabBuffer.GetString());
  692. AZ::Debug::Trace::Instance().Output("", "\n");
  693. }
  694. bool SliceConverter::SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId)
  695. {
  696. auto prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
  697. AZStd::string out;
  698. if (prefabLoaderInterface->SaveTemplateToString(templateId, out))
  699. {
  700. IO::SystemFile outputFile;
  701. if (!outputFile.Open(
  702. AZStd::string(outputPath.Native()).c_str(),
  703. IO::SystemFile::OpenMode::SF_OPEN_CREATE |
  704. IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
  705. IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
  706. {
  707. AZ_Error("Convert-Slice", false, " Unable to create output file '%.*s'.", AZ_STRING_ARG(outputPath.Native()));
  708. return false;
  709. }
  710. outputFile.Write(out.data(), out.size());
  711. outputFile.Close();
  712. return true;
  713. }
  714. AZ_Printf("Convert-Slice", " Could not save prefab - internal error (Json write operation failure).\n");
  715. return false;
  716. }
  717. bool SliceConverter::ConnectToAssetProcessor()
  718. {
  719. AzFramework::AssetSystem::ConnectionSettings connectionSettings;
  720. AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings);
  721. connectionSettings.m_launchAssetProcessorOnFailedConnection = true;
  722. connectionSettings.m_connectionDirection =
  723. AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
  724. connectionSettings.m_connectionIdentifier = AzFramework::AssetSystem::ConnectionIdentifiers::Editor;
  725. connectionSettings.m_loggingCallback = [](AZStd::string_view logData)
  726. {
  727. AZ_Printf("Convert-Slice", "%.*s\n", AZ_STRING_ARG(logData));
  728. };
  729. bool connectedToAssetProcessor = false;
  730. AzFramework::AssetSystemRequestBus::BroadcastResult(
  731. connectedToAssetProcessor, &AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection,
  732. connectionSettings);
  733. return connectedToAssetProcessor;
  734. }
  735. void SliceConverter::DisconnectFromAssetProcessor()
  736. {
  737. AzFramework::AssetSystemRequestBus::Broadcast(
  738. &AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor);
  739. // Wait for the disconnect to finish.
  740. bool disconnected = false;
  741. AzFramework::AssetSystemRequestBus::BroadcastResult(disconnected,
  742. &AzFramework::AssetSystem::AssetSystemRequests::WaitUntilAssetProcessorDisconnected, AZStd::chrono::seconds(30));
  743. AZ_Error("Convert-Slice", disconnected, "Asset Processor failed to disconnect successfully.");
  744. }
  745. void SliceConverter::UpdateSliceEntityInstanceMappings(
  746. const AZ::SliceComponent::EntityIdToEntityIdMap& sliceEntityIdMap, const AZStd::string& currentInstanceAlias)
  747. {
  748. // For each instanced entity, map its ID all the way back to the original prefab template and entity ID that it came from.
  749. // This counts on being run recursively from the leaf nodes upwards, so we first get B->A,
  750. // then C->B which becomes a C->A entry, then D->C which becomes D->A, etc.
  751. for (auto& [newId, oldId] : sliceEntityIdMap)
  752. {
  753. // Try to find the conversion chain from the old ID. if it's there, copy it and use it for the new ID, plus add this
  754. // instance's name to the end of the chain. If it's not there, skip it, since it's probably the slice metadata entity,
  755. // which we didn't convert.
  756. auto parentEntry = m_aliasIdMapper.find(oldId);
  757. if (parentEntry != m_aliasIdMapper.end())
  758. {
  759. // Only add this instance's name if we don't already have an entry for the new ID.
  760. if (m_aliasIdMapper.find(newId) == m_aliasIdMapper.end())
  761. {
  762. auto newMappingEntry = m_aliasIdMapper.emplace(newId, parentEntry->second).first;
  763. newMappingEntry->second.m_nestedInstanceAliases.emplace_back(currentInstanceAlias);
  764. }
  765. else
  766. {
  767. // If we already had an entry for the new ID, it might be because the old and new ID are the same. This happens
  768. // when nesting multiple prefabs directly underneath each other without a nesting entity in-between.
  769. // If the IDs are different, it's an unexpected error condition.
  770. AZ_Assert(oldId == newId, "The same entity instance ID has unexpectedly appeared twice in the same nested prefab.");
  771. }
  772. }
  773. else
  774. {
  775. AZ_Warning("Convert-Slice", false, " Couldn't find an entity ID conversion for %s.", oldId.ToString().c_str());
  776. }
  777. }
  778. }
  779. void SliceConverter::RemapIdReferences(
  780. const AZStd::unordered_map<AZ::EntityId, SliceEntityMappingInfo>& idMapper,
  781. AzToolsFramework::Prefab::Instance* topLevelInstance,
  782. AzToolsFramework::Prefab::Instance* nestedInstance,
  783. SliceComponent::InstantiatedContainer* instantiatedEntities,
  784. SerializeContext* context)
  785. {
  786. // Given a set of instantiated entities, run through all of them, look for entity references, and replace the entity IDs with
  787. // new ones that match up with our prefabs.
  788. IdUtils::Remapper<EntityId>::ReplaceIdsAndIdRefs(
  789. instantiatedEntities,
  790. [idMapper, &topLevelInstance, &nestedInstance](
  791. const EntityId& sourceId, bool isEntityId, [[maybe_unused]] const AZStd::function<EntityId()>& idGenerator) -> EntityId
  792. {
  793. EntityId newId = sourceId;
  794. // Only convert valid entity references. Actual entity IDs have already been taken care of elsewhere, so ignore them.
  795. if (!isEntityId && sourceId.IsValid())
  796. {
  797. auto entityEntry = idMapper.find(sourceId);
  798. // The id mapping table should include all of our known slice entities, slice metadata entities, and prefab
  799. // container entities. If we can't find the entity reference, it should either be because it's actually invalid
  800. // in the source data or because it's a transform parent id that we've already remapped prior to this point.
  801. // Either way, just keep it as-is and return it.
  802. if (entityEntry == idMapper.end())
  803. {
  804. return sourceId;
  805. }
  806. // We've got a slice->prefab mapping entry, so now we need to use it.
  807. auto& mappingStruct = entityEntry->second;
  808. if (mappingStruct.m_nestedInstanceAliases.empty())
  809. {
  810. // If we don't have a chain of nested instance aliases, then this entity reference is either within the
  811. // current nested instance or it's pointing to an entity in the top-level instance. We'll try them both
  812. // to look for a match.
  813. EntityId prefabId = nestedInstance->GetEntityId(mappingStruct.m_entityAlias);
  814. if (!prefabId.IsValid())
  815. {
  816. prefabId = topLevelInstance->GetEntityId(mappingStruct.m_entityAlias);
  817. }
  818. if (prefabId.IsValid())
  819. {
  820. newId = prefabId;
  821. }
  822. else
  823. {
  824. AZ_Error("Convert-Slice", false, " Couldn't find source ID %s", sourceId.ToString().c_str());
  825. newId = sourceId;
  826. }
  827. }
  828. else
  829. {
  830. // We *do* have a chain of nested instance aliases. This chain could either be relative to the nested instance
  831. // or the top-level instance. We can tell which one it is by which one can find the first nested instance
  832. // alias.
  833. AzToolsFramework::Prefab::Instance* entityInstance = nestedInstance;
  834. auto it = mappingStruct.m_nestedInstanceAliases.rbegin();
  835. if (!entityInstance->FindNestedInstance(*it).has_value())
  836. {
  837. entityInstance = topLevelInstance;
  838. }
  839. // Now that we've got a starting point, iterate through the chain of nested instance aliases to find the
  840. // correct instance to get the entity ID for. We have to go from slice IDs -> entity aliases -> entity IDs
  841. // because prefab instance creation can change some of our entity IDs along the way.
  842. for (; it != mappingStruct.m_nestedInstanceAliases.rend(); it++)
  843. {
  844. auto foundInstance = entityInstance->FindNestedInstance(*it);
  845. if (foundInstance.has_value())
  846. {
  847. entityInstance = &(foundInstance->get());
  848. }
  849. else
  850. {
  851. AZ_Assert(false, "Couldn't find nested instance %s", it->c_str());
  852. }
  853. }
  854. EntityId prefabId = entityInstance->GetEntityId(mappingStruct.m_entityAlias);
  855. if (prefabId.IsValid())
  856. {
  857. newId = prefabId;
  858. }
  859. }
  860. }
  861. return newId;
  862. },
  863. context);
  864. }
  865. } // namespace SerializeContextTools
  866. } // namespace AZ