SliceConverter.cpp 61 KB

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