Slices.cpp 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410
  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/base.h>
  9. #include <AzCore/Component/ComponentApplication.h>
  10. #include <AzCore/Component/Entity.h>
  11. #include <AzCore/Component/Component.h>
  12. #include <AzCore/Memory/AllocationRecords.h>
  13. #include <AzCore/Serialization/SerializeContext.h>
  14. #include <AzCore/Slice/SliceComponent.h>
  15. #include <AzCore/Slice/SliceAsset.h>
  16. #include <AzCore/Slice/SliceAssetHandler.h>
  17. #include <AzCore/Script/ScriptSystemComponent.h>
  18. #include <AzCore/Script/ScriptAsset.h>
  19. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  20. #include <AzCore/std/chrono/chrono.h>
  21. #include <AzCore/RTTI/TypeInfo.h>
  22. #include <AzCore/UnitTest/TestTypes.h>
  23. #include <AzCore/UserSettings/UserSettingsComponent.h>
  24. #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  25. #include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
  26. #include <AzToolsFramework/Slice/SliceCompilation.h>
  27. #include <AzToolsFramework/ToolsComponents/TransformComponent.h>
  28. #include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponent.h>
  29. #include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponentBus.h>
  30. #include <AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx>
  31. #include "EntityTestbed.h"
  32. #include <AzCore/Serialization/Utils.h>
  33. #include <AzCore/IO/FileIO.h>
  34. namespace UnitTest
  35. {
  36. using namespace AZ;
  37. class SliceInteractiveWorkflowTest
  38. : public EntityTestbed
  39. , AZ::Data::AssetBus::MultiHandler
  40. {
  41. public:
  42. class TestComponent1
  43. : public AZ::Component
  44. {
  45. public:
  46. AZ_COMPONENT(TestComponent1, "{54BA51C3-41BD-4BB6-B1ED-7F6CEFAC2F9F}");
  47. void Init() override
  48. {
  49. }
  50. void Activate() override {}
  51. void Deactivate() override {}
  52. static void Reflect(ReflectContext* context)
  53. {
  54. auto* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  55. if (serialize)
  56. {
  57. serialize->Class<TestComponent1, AZ::Component>()
  58. ->Version(1)
  59. ->Field("SomeFlag", &TestComponent1::m_someFlag)
  60. ;
  61. AZ::EditContext* ec = serialize->GetEditContext();
  62. if (ec)
  63. {
  64. ec->Class<TestComponent1>("Another component", "A component.")
  65. ->DataElement("CheckBox", &TestComponent1::m_someFlag, "SomeFlag", "");
  66. }
  67. }
  68. }
  69. bool m_someFlag = false;
  70. };
  71. class TestComponent
  72. : public AZ::Component
  73. {
  74. public:
  75. AZ_COMPONENT(TestComponent, "{F146074C-152E-483C-AD33-6D1945B4261A}");
  76. void Init() override
  77. {
  78. m_rootElement = aznew Entity("Blah");
  79. m_rootElement->CreateComponent<TestComponent1>();
  80. }
  81. void Activate() override {}
  82. void Deactivate() override {}
  83. static void Reflect(ReflectContext* context)
  84. {
  85. auto* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  86. if (serialize)
  87. {
  88. serialize->Class<TestComponent, AZ::Component>()
  89. ->Version(1)
  90. ->Field("RootElement", &TestComponent::m_rootElement)
  91. ->Field("LastElement", &TestComponent::m_lastElementId)
  92. ->Field("DrawOrder", &TestComponent::m_drawOrder)
  93. ->Field("IsPixelAligned", &TestComponent::m_isPixelAligned)
  94. ;
  95. AZ::EditContext* ec = serialize->GetEditContext();
  96. if (ec)
  97. {
  98. ec->Class<TestComponent>("Ui Canvas", "A component.")
  99. ->DataElement("CheckBox", &TestComponent::m_isPixelAligned, "IsPixelAligned", "Is pixel aligned.");
  100. }
  101. }
  102. }
  103. AZ::Entity* m_rootElement = nullptr;
  104. unsigned int m_lastElementId = 0;
  105. int m_drawOrder = 0;
  106. bool m_isPixelAligned = false;
  107. };
  108. AZ::Data::AssetId m_instantiatingSliceAsset;
  109. AZStd::atomic_int m_stressLoadPending;
  110. AZStd::vector<AZ::Data::Asset<AZ::SliceAsset> > m_stressTestSliceAssets;
  111. enum
  112. {
  113. Stress_Descendents = 3,
  114. Stress_Generations = 5,
  115. };
  116. SliceInteractiveWorkflowTest()
  117. {
  118. }
  119. ~SliceInteractiveWorkflowTest() override
  120. {
  121. EntityTestbed::Destroy();
  122. }
  123. void OnSetup() override
  124. {
  125. auto* catalogBus = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
  126. if (catalogBus)
  127. {
  128. // Register asset types the asset DB should query our catalog for.
  129. catalogBus->AddAssetType(AZ::AzTypeInfo<AZ::SliceAsset>::Uuid());
  130. catalogBus->AddAssetType(AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid());
  131. // Build the catalog (scan).
  132. catalogBus->AddExtension(".xml");
  133. catalogBus->AddExtension(".lua");
  134. }
  135. }
  136. void OnReflect(AZ::SerializeContext& context, AZ::Entity& systemEntity) override
  137. {
  138. (void)context;
  139. (void)systemEntity;
  140. TestComponent::Reflect(&context);
  141. TestComponent1::Reflect(&context);
  142. }
  143. void OnAddButtons(QHBoxLayout& layout) override
  144. {
  145. QPushButton* sliceSelected = new QPushButton(QString("New Slice"));
  146. QPushButton* sliceInherit = new QPushButton(QString("Inherit Slice"));
  147. QPushButton* sliceInstance = new QPushButton(QString("Instantiate Slice"));
  148. QPushButton* saveRoot = new QPushButton(QString("Save Root"));
  149. QPushButton* stressGen = new QPushButton(QString("Stress Gen"));
  150. QPushButton* stressLoad = new QPushButton(QString("Stress Load"));
  151. QPushButton* stressInst = new QPushButton(QString("Stress Inst"));
  152. QPushButton* stressAll = new QPushButton(QString("Stress All"));
  153. stressInst->setEnabled(false);
  154. layout.addWidget(sliceSelected);
  155. layout.addWidget(sliceInherit);
  156. layout.addWidget(sliceInstance);
  157. layout.addWidget(saveRoot);
  158. layout.addWidget(stressGen);
  159. layout.addWidget(stressLoad);
  160. layout.addWidget(stressInst);
  161. layout.addWidget(stressAll);
  162. m_qtApplication->connect(sliceSelected, &QPushButton::pressed, [ this ]() { this->CreateSlice(false); });
  163. m_qtApplication->connect(sliceInherit, &QPushButton::pressed, [this](){this->CreateSlice(true); });
  164. m_qtApplication->connect(sliceInstance, &QPushButton::pressed, [ this ]() { this->InstantiateSlice(); });
  165. m_qtApplication->connect(saveRoot, &QPushButton::pressed, [ this ]() { this->SaveRoot(); });
  166. m_qtApplication->connect(stressGen, &QPushButton::pressed, [ this ]() { this->StressGen(); });
  167. m_qtApplication->connect(stressLoad, &QPushButton::pressed, [ this, stressInst ]()
  168. {
  169. if (this->StressLoad())
  170. {
  171. stressInst->setEnabled(true);
  172. }
  173. });
  174. m_qtApplication->connect(stressInst, &QPushButton::pressed, [ this ]() { this->StressInst(); });
  175. m_qtApplication->connect(stressAll, &QPushButton::pressed,
  176. [ this ]()
  177. {
  178. this->StressGen();
  179. this->StressLoad();
  180. this->StressInst();
  181. }
  182. );
  183. }
  184. void OnEntityAdded(AZ::Entity& entity) override
  185. {
  186. (void)entity;
  187. entity.CreateComponent<TestComponent>();
  188. }
  189. void StressGenDrill(const AZ::Data::Asset<AZ::SliceAsset>& parent, size_t& nextSliceIndex, size_t generation, size_t& slicesCreated)
  190. {
  191. AZ::Data::Asset<AZ::SliceAsset> descendents[Stress_Descendents];
  192. for (size_t i = 0; i < Stress_Descendents; ++i)
  193. {
  194. AZ::Entity* entity = new AZ::Entity;
  195. auto* slice = entity->CreateComponent<AZ::SliceComponent>();
  196. {
  197. slice->AddSlice(parent);
  198. AZ::SliceComponent::EntityList entities;
  199. slice->GetEntities(entities);
  200. entities[0]->SetName(AZStd::string::format("Gen%zu_Descendent%zu_%zu", generation, i, nextSliceIndex));
  201. entities[1]->SetName(AZStd::string::format("Gen%zu_Descendent%zu_%zu", generation, i, nextSliceIndex + 1));
  202. //entities[0]->FindComponent<TestComponent>()->m_floatValue = float(nextSliceIndex) + 0.1f;
  203. //entities[0]->FindComponent<TestComponent>()->m_intValue = int(generation);
  204. //entities[1]->FindComponent<TestComponent>()->m_floatValue = float(nextSliceIndex) + 0.2f;
  205. }
  206. char assetFile[512];
  207. azsnprintf(assetFile, AZ_ARRAY_SIZE(assetFile), "GeneratedSlices/Gen%zu_Descendent%zu_%zu.xml", generation, i, nextSliceIndex++);
  208. AZ::Data::AssetId assetId;
  209. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  210. assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, assetFile, azrtti_typeid<AZ::SliceAsset>(), true);
  211. AZ::Utils::SaveObjectToFile(assetFile, AZ::DataStream::ST_XML, entity);
  212. slicesCreated += 1;
  213. descendents[i].Create(assetId, false);
  214. descendents[i].Get()->SetData(entity, slice, false);
  215. }
  216. // Drill down on next generation of inheritence.
  217. if (generation + 1 < Stress_Generations)
  218. {
  219. for (size_t i = 0; i < Stress_Descendents; ++i)
  220. {
  221. StressGenDrill(descendents[i], nextSliceIndex, generation + 1, slicesCreated);
  222. }
  223. }
  224. }
  225. void StressGen()
  226. {
  227. ResetRoot();
  228. // Build a base slice containing two entities.
  229. AZ::Entity* e1 = new AZ::Entity();
  230. e1->SetName("Gen0_Left");
  231. //auto* c1 = e1->CreateComponent<TestComponent>();
  232. //c1->m_floatValue = 0.1f;
  233. AZ::Entity* e2 = new AZ::Entity();
  234. e2->SetName("Gen0_Right");
  235. //auto* c2 = e2->CreateComponent<TestComponent>();
  236. //c2->m_floatValue = 0.2f;
  237. AZ::Entity* root = new AZ::Entity();
  238. auto* slice = root->CreateComponent<AZ::SliceComponent>();
  239. slice->AddEntity(e1);
  240. slice->AddEntity(e2);
  241. AZ::Utils::SaveObjectToFile("GeneratedSlices/Gen0.xml", AZ::DataStream::ST_XML, root);
  242. // Build a deep binary tree, where we create two branches of each slice, each with a different
  243. // override from the parent.
  244. AZ::Data::AssetId assetId;
  245. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
  246. "GeneratedSlices/Gen0.xml", azrtti_typeid<AZ::SliceAsset>(), true);
  247. AZ::Data::Asset<AZ::SliceAsset> baseSliceAsset;
  248. baseSliceAsset.Create(assetId, false);
  249. baseSliceAsset.Get()->SetData(root, slice);
  250. // Generate tree to Stress_Generations # of generations.
  251. size_t nextSliceIndex = 1;
  252. size_t slicesCreated = 1;
  253. (void)nextSliceIndex;
  254. (void)slicesCreated;
  255. StressGenDrill(baseSliceAsset, nextSliceIndex, 1, slicesCreated);
  256. AZ_TracePrintf("Debug", "Done generating %u assets\n", slicesCreated);
  257. }
  258. void StressLoadDrill(size_t& nextSliceIndex, size_t generation, AZStd::atomic_int& pending, size_t& assetsLoaded)
  259. {
  260. for (size_t i = 0; i < Stress_Descendents; ++i)
  261. {
  262. char assetFile[512];
  263. azsnprintf(assetFile, AZ_ARRAY_SIZE(assetFile), "GeneratedSlices/Gen%zu_Descendent%zu_%zu.xml", generation, i, nextSliceIndex++);
  264. AZ::Data::AssetId assetId;
  265. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
  266. assetFile, azrtti_typeid<AZ::SliceAsset>(), true);
  267. if (assetId.IsValid())
  268. {
  269. ++pending;
  270. AZ::Data::AssetBus::MultiHandler::BusConnect(assetId);
  271. AZ::Data::Asset<AZ::SliceAsset> asset;
  272. if (!asset.Create(assetId, true))
  273. {
  274. AZ_Error("Debug", false, "Asset %s could not be created.", assetFile);
  275. --pending;
  276. }
  277. ++assetsLoaded;
  278. }
  279. else
  280. {
  281. AZ_Error("Debug", false, "Asset %s could not be found.", assetFile);
  282. }
  283. }
  284. if (generation + 1 < Stress_Generations)
  285. {
  286. for (size_t i = 0; i < Stress_Descendents; ++i)
  287. {
  288. StressLoadDrill(nextSliceIndex, generation + 1, pending, assetsLoaded);
  289. }
  290. }
  291. }
  292. void StressInstDrill(const AZ::Data::Asset<AZ::SliceAsset>& asset, size_t& nextSliceIndex, size_t generation, size_t& slicesInstantiated)
  293. {
  294. // Recurse...
  295. if (generation < Stress_Generations)
  296. {
  297. for (size_t i = 0; i < Stress_Descendents; ++i)
  298. {
  299. char assetFile[512];
  300. azsnprintf(assetFile, AZ_ARRAY_SIZE(assetFile), "GeneratedSlices/Gen%zu_Descendent%zu_%zu.xml", generation, i, nextSliceIndex++);
  301. AZ_Error("Debug", asset.IsReady(), "Asset %s not ready?", assetFile);
  302. StressInstDrill(asset, nextSliceIndex, generation + 1, slicesInstantiated);
  303. }
  304. }
  305. if (asset.IsReady())
  306. {
  307. AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
  308. &AzToolsFramework::SliceEditorEntityOwnershipServiceRequests::InstantiateEditorSlice,
  309. asset, AZ::Transform::CreateIdentity());
  310. ++slicesInstantiated;
  311. }
  312. }
  313. bool StressLoad()
  314. {
  315. m_instantiatingSliceAsset.SetInvalid();
  316. m_stressTestSliceAssets.clear();
  317. m_stressLoadPending = 0;
  318. ResetRoot();
  319. // Preload all slice assets.
  320. AZ::Data::AssetId rootAssetId;
  321. AZ::Data::AssetCatalogRequestBus::BroadcastResult(rootAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
  322. "GeneratedSlices/Gen0.xml", azrtti_typeid<AZ::SliceAsset>(), true);
  323. if (rootAssetId.IsValid())
  324. {
  325. AZ::Data::AssetBus::MultiHandler::BusConnect(rootAssetId);
  326. ++m_stressLoadPending;
  327. AZ::Data::Asset<AZ::SliceAsset> baseSliceAsset;
  328. if (!baseSliceAsset.Create(rootAssetId, true))
  329. {
  330. return false;
  331. }
  332. const AZStd::chrono::steady_clock::time_point startTime = AZStd::chrono::steady_clock::now();
  333. size_t nextIndex = 1;
  334. size_t assetsLoaded = 1;
  335. StressLoadDrill(nextIndex, 1, m_stressLoadPending, assetsLoaded);
  336. while (m_stressLoadPending > 0)
  337. {
  338. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
  339. AZ::TickBus::Broadcast(&AZ::TickBus::Events::OnTick, 0.3f, AZ::ScriptTimePoint());
  340. }
  341. const AZStd::chrono::steady_clock::time_point assetLoadFinishTime = AZStd::chrono::steady_clock::now();
  342. AZ_Printf("StressTest", "All Assets Loaded: %u assets, took %.2f ms\n", assetsLoaded,
  343. float(AZStd::chrono::duration_cast<AZStd::chrono::microseconds>(assetLoadFinishTime - startTime).count()) * 0.001f);
  344. return true;
  345. }
  346. return false;
  347. }
  348. bool StressInst()
  349. {
  350. ResetRoot();
  351. // Instantiate from the bottom generation up.
  352. {
  353. AZ::Data::AssetId assetId;
  354. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
  355. "GeneratedSlices/Gen0.xml", azrtti_typeid<AZ::SliceAsset>(), true);
  356. AZ::Data::Asset<AZ::SliceAsset> baseSliceAsset;
  357. baseSliceAsset.Create(assetId, false);
  358. if (baseSliceAsset.IsReady())
  359. {
  360. size_t nextIndex = 1;
  361. size_t slices = 0;
  362. size_t liveAllocs = 0;
  363. [[maybe_unused]] size_t totalAllocs = 0;
  364. auto cb = [&liveAllocs](void*, const AZ::Debug::AllocationInfo&, unsigned char)
  365. {
  366. ++liveAllocs;
  367. return true;
  368. };
  369. AZ::AllocatorInstance<AZ::SystemAllocator>::Get().GetRecords()->EnumerateAllocations(cb);
  370. totalAllocs = AZ::AllocatorInstance<AZ::SystemAllocator>::Get().GetRecords()->RequestedAllocs();
  371. AZ_TracePrintf("StressTest", "Allocs Before Inst: %u live, %u total\n", liveAllocs, totalAllocs);
  372. const AZStd::chrono::steady_clock::time_point startTime = AZStd::chrono::steady_clock::now();
  373. StressInstDrill(baseSliceAsset, nextIndex, 1, slices);
  374. const AZStd::chrono::steady_clock::time_point instantiateFinishTime = AZStd::chrono::steady_clock::now();
  375. liveAllocs = 0;
  376. totalAllocs = 0;
  377. AZ::AllocatorInstance<AZ::SystemAllocator>::Get().GetRecords()->EnumerateAllocations(cb);
  378. totalAllocs = AZ::AllocatorInstance<AZ::SystemAllocator>::Get().GetRecords()->RequestedAllocs();
  379. AZ_TracePrintf("StressTest", "Allocs AfterInst: %u live, %u total\n", liveAllocs, totalAllocs);
  380. // 1023 slices, 2046 entities
  381. // Before -> After = Delta
  382. // (Live)|(Total) -> (Live)|(Total) = (Live)|(Total)
  383. // 28626 | 171792 -> 53157 | 533638 = 24531 | 361846
  384. // 38842 | 533654 -> 53157 | 716707 = 14315 | 183053
  385. // 38842 | 716723 -> 53157 | 899776 = 14315 | 183053
  386. AZ::SliceComponent* rootSlice;
  387. AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
  388. rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
  389. AZ::SliceComponent::EntityList entities;
  390. entities.reserve(128);
  391. rootSlice->GetEntities(entities);
  392. AZ_Printf("StressTest", "All Assets Instantiated: %u slices, %u entities, took %.2f ms\n", slices, entities.size(),
  393. float(AZStd::chrono::duration_cast<AZStd::chrono::microseconds>(instantiateFinishTime - startTime).count()) * 0.001f);
  394. return true;
  395. }
  396. }
  397. return false;
  398. }
  399. void CreateSlice(bool inherit)
  400. {
  401. (void)inherit;
  402. static AZ::u32 sliceCounter = 1;
  403. AzToolsFramework::EntityIdList selected;
  404. AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
  405. selected, &AzToolsFramework::ToolsApplicationRequests::Bus::Events::GetSelectedEntities);
  406. AZ::SliceComponent* rootSlice = nullptr;
  407. AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
  408. rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
  409. AZ_Assert(rootSlice, "Failed to get root slice.");
  410. if (!selected.empty())
  411. {
  412. AZ::Entity newEntity(AZStd::string::format("Slice%u", sliceCounter).c_str());
  413. AZ::SliceComponent* newSlice = newEntity.CreateComponent<AZ::SliceComponent>();
  414. AZStd::vector<AZ::Entity*> reclaimFromSlice;
  415. AZStd::vector<AZ::SliceComponent::SliceInstanceAddress> sliceInstances;
  416. // Add all selected entities.
  417. for (AZ::EntityId id : selected)
  418. {
  419. AZ::Entity* entity = nullptr;
  420. AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, id);
  421. if (entity)
  422. {
  423. AZ::SliceComponent::SliceInstanceAddress sliceAddress = rootSlice->FindSlice(entity);
  424. if (sliceAddress.IsValid())
  425. {
  426. // This entity already belongs to a slice instance, so inherit that instance (the whole thing for now).
  427. if (sliceInstances.end() == AZStd::find(sliceInstances.begin(), sliceInstances.end(), sliceAddress))
  428. {
  429. sliceInstances.push_back(sliceAddress);
  430. }
  431. }
  432. else
  433. {
  434. // Otherwise add loose.
  435. newSlice->AddEntity(entity);
  436. reclaimFromSlice.push_back(entity);
  437. }
  438. }
  439. }
  440. for (AZ::SliceComponent::SliceInstanceAddress& info : sliceInstances)
  441. {
  442. info = newSlice->AddSliceInstance(info.GetReference(), info.GetInstance());
  443. }
  444. const QString saveAs = QFileDialog::getSaveFileName(nullptr,
  445. QString("Save As..."), QString("."), QString("Xml Files (*.xml)"));
  446. if (!saveAs.isEmpty())
  447. {
  448. AZ::Utils::SaveObjectToFile(saveAs.toUtf8().constData(), AZ::DataStream::ST_XML, &newEntity);
  449. }
  450. // Reclaim entities.
  451. for (AZ::Entity* entity : reclaimFromSlice)
  452. {
  453. newSlice->RemoveEntity(entity, false);
  454. }
  455. // Reclaim slices.
  456. for (AZ::SliceComponent::SliceInstanceAddress& info : sliceInstances)
  457. {
  458. rootSlice->AddSliceInstance(info.GetReference(), info.GetInstance());
  459. }
  460. ++sliceCounter;
  461. }
  462. }
  463. void InstantiateSlice()
  464. {
  465. const QString loadFrom = QFileDialog::getOpenFileName(nullptr,
  466. QString("Select Slice..."), QString("."), QString("Xml Files (*.xml)"));
  467. if (!loadFrom.isEmpty())
  468. {
  469. AZ::Data::AssetId assetId;
  470. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
  471. loadFrom.toUtf8().constData(), azrtti_typeid<AZ::SliceAsset>(), true);
  472. AZ::Data::Asset<AZ::SliceAsset> baseSliceAsset;
  473. baseSliceAsset.Create(assetId, true);
  474. m_instantiatingSliceAsset = baseSliceAsset.GetId();
  475. AZ::Data::AssetBus::MultiHandler::BusConnect(assetId);
  476. }
  477. }
  478. void OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset) override
  479. {
  480. AZ::Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId());
  481. if (asset.GetId() == m_instantiatingSliceAsset)
  482. {
  483. }
  484. else
  485. {
  486. --m_stressLoadPending;
  487. }
  488. }
  489. void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override
  490. {
  491. AZ::Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId());
  492. if (asset.GetId() == m_instantiatingSliceAsset)
  493. {
  494. if (asset.Get() == nullptr)
  495. {
  496. return;
  497. }
  498. m_instantiatingSliceAsset.SetInvalid();
  499. // Just add the slice to the level slice.
  500. AZ::Data::Asset<AZ::SliceAsset> sliceAsset = asset;
  501. AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
  502. &AzToolsFramework::SliceEditorEntityOwnershipServiceRequests::InstantiateEditorSlice,
  503. asset, AZ::Transform::CreateIdentity());
  504. // Init everything in the slice.
  505. AZ::SliceComponent* rootSlice = nullptr;
  506. AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
  507. rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
  508. AZ_Assert(rootSlice, "Failed to get root slice.");
  509. AZ::SliceComponent::EntityList entities;
  510. rootSlice->GetEntities(entities);
  511. for (AZ::Entity* entity : entities)
  512. {
  513. if (entity->GetState() == AZ::Entity::State::Constructed)
  514. {
  515. entity->Init();
  516. }
  517. }
  518. m_entityCounter += AZ::u32(entities.size());
  519. }
  520. else
  521. {
  522. m_stressTestSliceAssets.push_back(asset);
  523. --m_stressLoadPending;
  524. }
  525. }
  526. void run()
  527. {
  528. int argc = 0;
  529. char* argv = nullptr;
  530. Run(argc, &argv);
  531. }
  532. }; // class SliceInteractiveWorkflowTest
  533. TEST_F(SliceInteractiveWorkflowTest, DISABLED_Test)
  534. {
  535. run();
  536. }
  537. class MinimalEntityWorkflowTester
  538. : public EntityTestbed
  539. {
  540. public:
  541. void run()
  542. {
  543. int argc = 0;
  544. char* argv = nullptr;
  545. Run(argc, &argv);
  546. }
  547. void OnEntityAdded(AZ::Entity& entity) override
  548. {
  549. // Add your components.
  550. entity.CreateComponent<AzToolsFramework::Components::TransformComponent>();
  551. }
  552. };
  553. TEST_F(MinimalEntityWorkflowTester, DISABLED_Test)
  554. {
  555. run();
  556. }
  557. class SortTransformParentsBeforeChildrenTest
  558. : public LeakDetectionFixture
  559. {
  560. protected:
  561. AZStd::vector<AZ::Entity*> m_unsorted;
  562. AZStd::vector<AZ::Entity*> m_sorted;
  563. void TearDown() override
  564. {
  565. for (AZ::Entity* entity : m_unsorted)
  566. {
  567. delete entity;
  568. }
  569. m_unsorted.clear();
  570. m_sorted.clear();
  571. }
  572. // Entity IDs to use in tests
  573. AZ::EntityId E1 = AZ::EntityId(1);
  574. AZ::EntityId E2 = AZ::EntityId(2);
  575. AZ::EntityId E3 = AZ::EntityId(3);
  576. AZ::EntityId E4 = AZ::EntityId(4);
  577. AZ::EntityId E5 = AZ::EntityId(5);
  578. AZ::EntityId E6 = AZ::EntityId(6);
  579. AZ::EntityId MissingNo = AZ::EntityId(999);
  580. // add entity to m_unsorted
  581. void AddEntity(AZ::EntityId id, AZ::EntityId parentId = AZ::EntityId())
  582. {
  583. m_unsorted.push_back(aznew AZ::Entity(id));
  584. m_unsorted.back()->CreateComponent<AzFramework::TransformComponent>()->SetParent(parentId);
  585. }
  586. void SortAndSanityCheck()
  587. {
  588. m_sorted = m_unsorted;
  589. AzToolsFramework::SortTransformParentsBeforeChildren(m_sorted);
  590. // sanity check that all entries are still there
  591. EXPECT_TRUE(DoSameEntriesExistAfterSort());
  592. }
  593. bool DoSameEntriesExistAfterSort()
  594. {
  595. if (m_sorted.size() != m_unsorted.size())
  596. {
  597. return false;
  598. }
  599. for (AZ::Entity* entity : m_unsorted)
  600. {
  601. // compare counts in case multiple entries are identical (ex: 2 nullptrs)
  602. size_t unsortedCount = Count(entity, m_unsorted);
  603. size_t sortedCount = Count(entity, m_sorted);
  604. if (sortedCount < 1 || sortedCount != unsortedCount)
  605. {
  606. return false;
  607. }
  608. }
  609. return true;
  610. }
  611. int Count(const AZ::Entity* value, const AZStd::vector<AZ::Entity*>& entityList)
  612. {
  613. int count = 0;
  614. for (const AZ::Entity* entity : entityList)
  615. {
  616. if (entity == value)
  617. {
  618. ++count;
  619. }
  620. }
  621. return count;
  622. }
  623. bool IsChildAfterParent(AZ::EntityId childId, AZ::EntityId parentId)
  624. {
  625. int parentIndex = -1;
  626. int childIndex = -1;
  627. for (int i = 0; i < m_sorted.size(); ++i)
  628. {
  629. if (m_sorted[i] && (m_sorted[i]->GetId() == parentId) && (parentIndex == -1))
  630. {
  631. parentIndex = i;
  632. }
  633. if (m_sorted[i] && (m_sorted[i]->GetId() == childId) && (childIndex == -1))
  634. {
  635. childIndex = i;
  636. }
  637. }
  638. EXPECT_NE(childIndex, -1);
  639. EXPECT_NE(parentIndex, -1);
  640. return childIndex > parentIndex;
  641. }
  642. };
  643. TEST_F(SortTransformParentsBeforeChildrenTest, 0Entities_IsOk)
  644. {
  645. SortAndSanityCheck();
  646. }
  647. TEST_F(SortTransformParentsBeforeChildrenTest, 1Entity_IsOk)
  648. {
  649. AddEntity(E1);
  650. SortAndSanityCheck();
  651. }
  652. TEST_F(SortTransformParentsBeforeChildrenTest, ParentAndChild_SortsCorrectly)
  653. {
  654. AddEntity(E2, E1);
  655. AddEntity(E1);
  656. SortAndSanityCheck();
  657. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  658. }
  659. TEST_F(SortTransformParentsBeforeChildrenTest, 6EntitiesWith2Roots_SortsCorrectly)
  660. {
  661. // Hierarchy looks like:
  662. // 1
  663. // + 2
  664. // + 3
  665. // 4
  666. // + 5
  667. // + 6
  668. // The entities are added in "randomish" order on purpose
  669. AddEntity(E3, E2);
  670. AddEntity(E1);
  671. AddEntity(E6, E4);
  672. AddEntity(E5, E4);
  673. AddEntity(E2, E1);
  674. AddEntity(E4);
  675. SortAndSanityCheck();
  676. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  677. EXPECT_TRUE(IsChildAfterParent(E3, E2));
  678. EXPECT_TRUE(IsChildAfterParent(E5, E4));
  679. EXPECT_TRUE(IsChildAfterParent(E6, E4));
  680. }
  681. TEST_F(SortTransformParentsBeforeChildrenTest, ParentNotFound_ChildTreatedAsRoot)
  682. {
  683. AddEntity(E1);
  684. AddEntity(E2, E1);
  685. AddEntity(E3, MissingNo); // E3's parent not found
  686. AddEntity(E4, E3);
  687. SortAndSanityCheck();
  688. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  689. EXPECT_TRUE(IsChildAfterParent(E4, E2));
  690. }
  691. TEST_F(SortTransformParentsBeforeChildrenTest, NullptrEntry_IsToleratedButNotSorted)
  692. {
  693. AddEntity(E2, E1);
  694. m_unsorted.push_back(nullptr);
  695. AddEntity(E1);
  696. SortAndSanityCheck();
  697. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  698. }
  699. TEST_F(SortTransformParentsBeforeChildrenTest, DuplicateEntityId_IsToleratedButNotSorted)
  700. {
  701. AddEntity(E2, E1);
  702. AddEntity(E1);
  703. AddEntity(E1); // duplicate EntityId
  704. SortAndSanityCheck();
  705. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  706. }
  707. TEST_F(SortTransformParentsBeforeChildrenTest, DuplicateEntityPtr_IsToleratedButNotSorted)
  708. {
  709. AddEntity(E2, E1);
  710. AddEntity(E1);
  711. m_unsorted.push_back(m_unsorted.back()); // duplicate Entity pointer
  712. SortAndSanityCheck();
  713. m_unsorted.pop_back(); // remove duplicate ptr so we don't double-delete during teardown
  714. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  715. }
  716. TEST_F(SortTransformParentsBeforeChildrenTest, LoopingHierarchy_PicksAnyParentAsRoot)
  717. {
  718. // loop: E1 -> E2 -> E3 -> E1 -> ...
  719. AddEntity(E2, E1);
  720. AddEntity(E3, E2);
  721. AddEntity(E1, E3);
  722. SortAndSanityCheck();
  723. AZ::EntityId first = m_sorted.front()->GetId();
  724. if (first == E1)
  725. {
  726. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  727. EXPECT_TRUE(IsChildAfterParent(E3, E2));
  728. }
  729. else if (first == E2)
  730. {
  731. EXPECT_TRUE(IsChildAfterParent(E3, E2));
  732. EXPECT_TRUE(IsChildAfterParent(E1, E3));
  733. }
  734. else // if (first == E3)
  735. {
  736. EXPECT_TRUE(IsChildAfterParent(E1, E3));
  737. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  738. }
  739. }
  740. TEST_F(SortTransformParentsBeforeChildrenTest, EntityLackingTransformComponent_IsTreatedLikeItHasNoParent)
  741. {
  742. AddEntity(E2, E1);
  743. m_unsorted.push_back(aznew AZ::Entity(E1)); // E1 has no components
  744. SortAndSanityCheck();
  745. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  746. }
  747. TEST_F(SortTransformParentsBeforeChildrenTest, EntityParentedToSelf_IsTreatedLikeItHasNoParent)
  748. {
  749. AddEntity(E2, E1);
  750. AddEntity(E1, E1); // parented to self
  751. SortAndSanityCheck();
  752. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  753. }
  754. TEST_F(SortTransformParentsBeforeChildrenTest, EntityWithInvalidId_IsToleratedButNotSorted)
  755. {
  756. AddEntity(E2, E1);
  757. AddEntity(E1);
  758. AddEntity(AZ::EntityId()); // entity using invalid ID as its own ID
  759. SortAndSanityCheck();
  760. EXPECT_TRUE(IsChildAfterParent(E2, E1));
  761. }
  762. class TestExportRuntimeComponent
  763. : public AZ::Component
  764. {
  765. public:
  766. AZ_COMPONENT(TestExportRuntimeComponent, "{C984534F-C907-4968-B9D3-AF2A99CBD678}", AZ::Component);
  767. TestExportRuntimeComponent() {}
  768. TestExportRuntimeComponent(bool returnPointerToSelf, bool exportHandled) :
  769. m_returnPointerToSelf(returnPointerToSelf),
  770. m_exportHandled(exportHandled)
  771. {}
  772. void Activate() override {}
  773. void Deactivate() override {}
  774. static void Reflect(AZ::ReflectContext* context)
  775. {
  776. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  777. {
  778. serializeContext->Class<TestExportRuntimeComponent, AZ::Component>()
  779. ;
  780. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  781. {
  782. editContext->Class<TestExportRuntimeComponent>(
  783. "Test Export Runtime Component", "Validate different options for exporting runtime components")
  784. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  785. ->Attribute(AZ::Edit::Attributes::RuntimeExportCallback, &TestExportRuntimeComponent::ExportComponent)
  786. ;
  787. }
  788. }
  789. }
  790. AZ::ExportedComponent ExportComponent(AZ::Component* thisComponent, const AZ::PlatformTagSet& /*platformTags*/)
  791. {
  792. return AZ::ExportedComponent(m_returnPointerToSelf ? thisComponent : nullptr, false, m_exportHandled);
  793. }
  794. bool m_returnPointerToSelf = false;
  795. bool m_exportHandled = false;
  796. };
  797. class TestExportOtherRuntimeComponent
  798. : public AZ::Component
  799. {
  800. public:
  801. AZ_COMPONENT(TestExportOtherRuntimeComponent, "{7EEDCE0A-2D5F-4017-A20B-9224E52D75B8}");
  802. void Activate() override {}
  803. void Deactivate() override {}
  804. static void Reflect(ReflectContext* context)
  805. {
  806. auto* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  807. if (serialize)
  808. {
  809. serialize->Class<TestExportOtherRuntimeComponent, AZ::Component>()
  810. ;
  811. }
  812. }
  813. };
  814. class SliceTestExportEditorComponent
  815. : public AzToolsFramework::Components::EditorComponentBase
  816. {
  817. public:
  818. AZ_COMPONENT(SliceTestExportEditorComponent, "{8FA877A2-38E6-49AD-B31E-71B86DC8BB03}", AzToolsFramework::Components::EditorComponentBase);
  819. enum ExportComponentType
  820. {
  821. EXPORT_EDITOR_COMPONENT,
  822. EXPORT_RUNTIME_COMPONENT,
  823. EXPORT_OTHER_RUNTIME_COMPONENT,
  824. EXPORT_NULL_COMPONENT
  825. };
  826. SliceTestExportEditorComponent() {}
  827. SliceTestExportEditorComponent(ExportComponentType exportType, bool exportHandled) :
  828. m_exportType(exportType),
  829. m_exportHandled(exportHandled)
  830. {}
  831. void Activate() override {}
  832. void Deactivate() override {}
  833. static void Reflect(AZ::ReflectContext* context)
  834. {
  835. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  836. {
  837. serializeContext->Class<SliceTestExportEditorComponent, AzToolsFramework::Components::EditorComponentBase>()
  838. ;
  839. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  840. {
  841. editContext->Class<SliceTestExportEditorComponent>(
  842. "Test Export Editor Component", "Validate different options for exporting editor components")
  843. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  844. ->Attribute(AZ::Edit::Attributes::RuntimeExportCallback, &SliceTestExportEditorComponent::ExportComponent)
  845. ;
  846. }
  847. }
  848. }
  849. AZ::ExportedComponent ExportComponent(AZ::Component* thisComponent, const AZ::PlatformTagSet& /*platformTags*/)
  850. {
  851. switch (m_exportType)
  852. {
  853. case EXPORT_EDITOR_COMPONENT:
  854. return AZ::ExportedComponent(thisComponent, false, m_exportHandled);
  855. case EXPORT_RUNTIME_COMPONENT:
  856. return AZ::ExportedComponent(aznew TestExportRuntimeComponent(true, true), true, m_exportHandled);
  857. case EXPORT_OTHER_RUNTIME_COMPONENT:
  858. return AZ::ExportedComponent(aznew TestExportOtherRuntimeComponent(), true, m_exportHandled);
  859. case EXPORT_NULL_COMPONENT:
  860. return AZ::ExportedComponent(nullptr, false, m_exportHandled);
  861. }
  862. return AZ::ExportedComponent();
  863. }
  864. void BuildGameEntity(AZ::Entity* gameEntity) override
  865. {
  866. gameEntity->CreateComponent<TestExportRuntimeComponent>(true, true);
  867. }
  868. ExportComponentType m_exportType = EXPORT_NULL_COMPONENT;
  869. bool m_exportHandled = false;
  870. };
  871. class SliceCompilerTest
  872. : public UnitTest::LeakDetectionFixture
  873. {
  874. protected:
  875. AzToolsFramework::ToolsApplication m_app;
  876. AZ::Data::Asset<AZ::SliceAsset> m_editorSliceAsset;
  877. AZ::SliceComponent* m_editorSliceComponent = nullptr;
  878. AZ::Data::Asset<AZ::SliceAsset> m_compiledSliceAsset;
  879. AZ::SliceComponent* m_compiledSliceComponent = nullptr;
  880. void SetUp() override
  881. {
  882. AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
  883. auto projectPathKey =
  884. AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
  885. AZ::IO::FixedMaxPath enginePath;
  886. registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
  887. registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
  888. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
  889. AZ::ComponentApplication::StartupParameters startupParameters;
  890. startupParameters.m_loadSettingsRegistry = false;
  891. m_app.Start(AzFramework::Application::Descriptor(), startupParameters);
  892. // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
  893. // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
  894. // in the unit tests.
  895. AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
  896. m_app.RegisterComponentDescriptor(TestExportRuntimeComponent::CreateDescriptor());
  897. m_app.RegisterComponentDescriptor(TestExportOtherRuntimeComponent::CreateDescriptor());
  898. m_app.RegisterComponentDescriptor(SliceTestExportEditorComponent::CreateDescriptor());
  899. m_editorSliceAsset = Data::AssetManager::Instance().CreateAsset<SliceAsset>(Data::AssetId(Uuid::CreateRandom()));
  900. AZ::Entity* editorSliceEntity = aznew AZ::Entity();
  901. m_editorSliceComponent = editorSliceEntity->CreateComponent<AZ::SliceComponent>();
  902. m_editorSliceAsset.Get()->SetData(editorSliceEntity, m_editorSliceComponent);
  903. }
  904. void TearDown() override
  905. {
  906. m_compiledSliceComponent = nullptr;
  907. m_compiledSliceAsset.Release();
  908. m_editorSliceComponent = nullptr;
  909. m_editorSliceAsset.Release();
  910. m_app.Stop();
  911. }
  912. // create entity with a given parent in the editor slice
  913. void CreateEditorEntity(AZ::u64 id, const char* name, AZ::u64 parentId = (AZ::u64)AZ::EntityId())
  914. {
  915. AZ::Entity* entity = aznew AZ::Entity(AZ::EntityId(id), name);
  916. AzToolsFramework::Components::TransformComponent* transformComponent = entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
  917. transformComponent->SetParent(AZ::EntityId(parentId));
  918. m_editorSliceComponent->AddEntity(entity);
  919. }
  920. // create entity containing the EditorOnly component in the editor slice
  921. void CreateEditorOnlyEntity(const char* name, bool editorOnly)
  922. {
  923. AZ::Entity* entity = aznew AZ::Entity(name);
  924. entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
  925. entity->CreateComponent<AzToolsFramework::Components::EditorOnlyEntityComponent>();
  926. m_editorSliceComponent->AddEntity(entity);
  927. entity->Init();
  928. EXPECT_EQ(AZ::Entity::State::Init, entity->GetState());
  929. entity->Activate();
  930. EXPECT_EQ(AZ::Entity::State::Active, entity->GetState());
  931. AzToolsFramework::EditorOnlyEntityComponentRequestBus::Event(entity->GetId(), &AzToolsFramework::EditorOnlyEntityComponentRequests::SetIsEditorOnlyEntity, editorOnly);
  932. }
  933. // create entity containing the EditorOnly component in the editor slice
  934. void CreateTestExportRuntimeEntity(const char* name, bool returnPointerToSelf, bool exportHandled)
  935. {
  936. AZ::Entity* entity = aznew AZ::Entity(name);
  937. entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
  938. entity->CreateComponent<TestExportRuntimeComponent>(returnPointerToSelf, exportHandled);
  939. m_editorSliceComponent->AddEntity(entity);
  940. }
  941. // create entity containing the EditorOnly component in the editor slice
  942. void CreateTestExportEditorEntity(const char* name, SliceTestExportEditorComponent::ExportComponentType exportType, bool exportHandled)
  943. {
  944. AZ::Entity* entity = aznew AZ::Entity(name);
  945. entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
  946. entity->CreateComponent<SliceTestExportEditorComponent>(exportType, exportHandled);
  947. m_editorSliceComponent->AddEntity(entity);
  948. }
  949. // compile m_editorSliceAsset -> m_compiledSliceAsset
  950. bool CompileSlice(bool expectSuccess = true)
  951. {
  952. AzToolsFramework::WorldEditorOnlyEntityHandler worldEditorOnlyEntityHandler;
  953. AzToolsFramework::EditorOnlyEntityHandlers handlers =
  954. {
  955. &worldEditorOnlyEntityHandler
  956. };
  957. AzToolsFramework::SliceCompilationResult compileResult = AzToolsFramework::CompileEditorSlice(m_editorSliceAsset, AZ::PlatformTagSet(), *m_app.GetSerializeContext(), handlers);
  958. EXPECT_EQ(compileResult.IsSuccess(), expectSuccess);
  959. if (compileResult.IsSuccess())
  960. {
  961. m_compiledSliceAsset = AZStd::move(compileResult.GetValue());
  962. m_compiledSliceComponent = m_compiledSliceAsset.Get()->GetComponent();
  963. return true;
  964. }
  965. return false;
  966. }
  967. // check order of entities in compiled slice
  968. // reference entities by name, since they have different IDs in compiled slice
  969. bool IsChildAfterParent(const char* childName, const char* parentName)
  970. {
  971. AZStd::vector<AZ::Entity*> entities;
  972. m_compiledSliceComponent->GetEntities(entities);
  973. AZ::Entity* parent = nullptr;
  974. for (AZ::Entity* entity : entities)
  975. {
  976. const AZStd::string& name = entity->GetName();
  977. if (name == parentName)
  978. {
  979. parent = entity;
  980. }
  981. if (name == childName)
  982. {
  983. return parent != nullptr;
  984. }
  985. }
  986. return false;
  987. }
  988. // Locate and return an entity from the compiled slice
  989. AZ::Entity* GetCompiledEntity(const char* entityName)
  990. {
  991. AZStd::vector<AZ::Entity*> entities;
  992. m_compiledSliceComponent->GetEntities(entities);
  993. for (AZ::Entity* entity : entities)
  994. {
  995. const AZStd::string& name = entity->GetName();
  996. if (name == entityName)
  997. {
  998. return entity;
  999. }
  1000. }
  1001. return nullptr;
  1002. }
  1003. };
  1004. TEST_F(SliceCompilerTest, EntitiesInCompiledSlice_SortedParentsBeforeChildren)
  1005. {
  1006. // Hieararchy looks like:
  1007. // A
  1008. // + B
  1009. // + C
  1010. // D
  1011. // + E
  1012. // + F
  1013. CreateEditorEntity(0xB, "B", 0xA);
  1014. CreateEditorEntity(0xE, "E", 0xD);
  1015. CreateEditorEntity(0xD, "D");
  1016. CreateEditorEntity(0xA, "A");
  1017. CreateEditorEntity(0xF, "F", 0xD);
  1018. CreateEditorEntity(0xC, "C", 0xB);
  1019. if (!CompileSlice())
  1020. {
  1021. return;
  1022. }
  1023. EXPECT_TRUE(IsChildAfterParent("B", "A"));
  1024. EXPECT_TRUE(IsChildAfterParent("C", "B"));
  1025. EXPECT_TRUE(IsChildAfterParent("E", "D"));
  1026. EXPECT_TRUE(IsChildAfterParent("F", "D"));
  1027. }
  1028. TEST_F(SliceCompilerTest, EditorOnlyEntity_OnlyRuntimeEntityExported)
  1029. {
  1030. // Create one entity that's flagged as Editor-Only, and one that's enabled for runtime.
  1031. CreateEditorOnlyEntity("EditorOnly", true);
  1032. CreateEditorOnlyEntity("EditorAndRuntime", false);
  1033. if (!CompileSlice())
  1034. {
  1035. return;
  1036. }
  1037. // Expected result: Only the runtime entity exists in the exported slice.
  1038. EXPECT_FALSE(GetCompiledEntity("EditorOnly"));
  1039. EXPECT_TRUE(GetCompiledEntity("EditorAndRuntime"));
  1040. }
  1041. TEST_F(SliceCompilerTest, RuntimeExportCallback_RuntimeComponentExportedSuccessfully)
  1042. {
  1043. // Create a component that has a RuntimeExportCallback and successfully exports itself
  1044. CreateTestExportRuntimeEntity("EntityWithRuntimeComponent", true, true);
  1045. if (!CompileSlice())
  1046. {
  1047. return;
  1048. }
  1049. // Expected result: exported slice contains the component.
  1050. AZ::Entity* entity = GetCompiledEntity("EntityWithRuntimeComponent");
  1051. EXPECT_TRUE(entity);
  1052. EXPECT_TRUE(entity->FindComponent<TestExportRuntimeComponent>());
  1053. }
  1054. TEST_F(SliceCompilerTest, RuntimeExportCallback_RuntimeComponentExportSuppressed)
  1055. {
  1056. // Create a component that has a RuntimeExportCallback and successfully suppresses itself from exporting
  1057. CreateTestExportRuntimeEntity("EntityWithRuntimeComponent", false, true);
  1058. if (!CompileSlice())
  1059. {
  1060. return;
  1061. }
  1062. // Expected result: exported slice does NOT contain the component.
  1063. AZ::Entity* entity = GetCompiledEntity("EntityWithRuntimeComponent");
  1064. EXPECT_TRUE(entity);
  1065. EXPECT_FALSE(entity->FindComponent<TestExportRuntimeComponent>());
  1066. }
  1067. TEST_F(SliceCompilerTest, RuntimeExportCallback_RuntimeComponentExportUnhandled)
  1068. {
  1069. // Create a component that has a RuntimeExportCallback, returns a pointer to itself, but says it wasn't handled.
  1070. CreateTestExportRuntimeEntity("EntityWithRuntimeComponent", true, false);
  1071. if (!CompileSlice())
  1072. {
  1073. return;
  1074. }
  1075. // Expected result: exported slice contains the component, because the default behavior is "clone/add" for
  1076. // runtime components.
  1077. AZ::Entity* entity = GetCompiledEntity("EntityWithRuntimeComponent");
  1078. EXPECT_TRUE(entity);
  1079. EXPECT_TRUE(entity->FindComponent<TestExportRuntimeComponent>());
  1080. }
  1081. TEST_F(SliceCompilerTest, RuntimeExportCallback_RuntimeComponentExportSuppressedAndUnhandled)
  1082. {
  1083. // Create a component that has a RuntimeExportCallback and suppresses itself from exporting, but says it wasn't handled
  1084. CreateTestExportRuntimeEntity("EntityWithRuntimeComponent", false, false);
  1085. if (!CompileSlice())
  1086. {
  1087. return;
  1088. }
  1089. // Expected result: exported slice contains the component, because by saying it wasn't handled, it
  1090. // should fall back on the default behavior of "clone/add" for runtime components.
  1091. AZ::Entity* entity = GetCompiledEntity("EntityWithRuntimeComponent");
  1092. EXPECT_TRUE(entity);
  1093. EXPECT_TRUE(entity->FindComponent<TestExportRuntimeComponent>());
  1094. }
  1095. TEST_F(SliceCompilerTest, RuntimeExportCallback_EditorComponentExportedSuccessfully)
  1096. {
  1097. // Create an editor component that has a RuntimeExportCallback and successfully exports itself
  1098. CreateTestExportEditorEntity("EntityWithEditorComponent", SliceTestExportEditorComponent::ExportComponentType::EXPORT_OTHER_RUNTIME_COMPONENT, true);
  1099. if (!CompileSlice())
  1100. {
  1101. return;
  1102. }
  1103. // Expected result: exported slice contains the OtherRuntime component, exported from RuntimeExportCallback.
  1104. // (A result of Runtime component means BuildGameEntity() ran instead)
  1105. AZ::Entity* entity = GetCompiledEntity("EntityWithEditorComponent");
  1106. EXPECT_TRUE(entity);
  1107. EXPECT_FALSE(entity->FindComponent<SliceTestExportEditorComponent>());
  1108. EXPECT_FALSE(entity->FindComponent<TestExportRuntimeComponent>());
  1109. EXPECT_TRUE(entity->FindComponent<TestExportOtherRuntimeComponent>());
  1110. }
  1111. TEST_F(SliceCompilerTest, RuntimeExportCallback_EditorComponentExportSuppressed)
  1112. {
  1113. // Create an editor component that has a RuntimeExportCallback and successfully suppresses itself from exporting
  1114. CreateTestExportEditorEntity("EntityWithEditorComponent", SliceTestExportEditorComponent::ExportComponentType::EXPORT_NULL_COMPONENT, true);
  1115. if (!CompileSlice())
  1116. {
  1117. return;
  1118. }
  1119. // Expected result: exported slice does NOT contain either component.
  1120. AZ::Entity* entity = GetCompiledEntity("EntityWithEditorComponent");
  1121. EXPECT_TRUE(entity);
  1122. EXPECT_FALSE(entity->FindComponent<SliceTestExportEditorComponent>());
  1123. EXPECT_FALSE(entity->FindComponent<TestExportRuntimeComponent>());
  1124. EXPECT_FALSE(entity->FindComponent<TestExportOtherRuntimeComponent>());
  1125. }
  1126. TEST_F(SliceCompilerTest, RuntimeExportCallback_EditorComponentExportUnhandledFallbackToBuildGameEntity)
  1127. {
  1128. // Create an editor component that has a RuntimeExportCallback, returns a pointer to itself, but says it wasn't handled.
  1129. CreateTestExportEditorEntity("EntityWithEditorComponent", SliceTestExportEditorComponent::ExportComponentType::EXPORT_EDITOR_COMPONENT, false);
  1130. if (!CompileSlice())
  1131. {
  1132. return;
  1133. }
  1134. // Expected result: exported slice contains the runtime component, because the fallback to BuildGameEntity()
  1135. // produced a runtime component.
  1136. AZ::Entity* entity = GetCompiledEntity("EntityWithEditorComponent");
  1137. EXPECT_TRUE(entity);
  1138. EXPECT_FALSE(entity->FindComponent<SliceTestExportEditorComponent>());
  1139. EXPECT_TRUE(entity->FindComponent<TestExportRuntimeComponent>());
  1140. EXPECT_FALSE(entity->FindComponent<TestExportOtherRuntimeComponent>());
  1141. }
  1142. TEST_F(SliceCompilerTest, RuntimeExportCallback_EditorComponentExportSuppressedAndUnhandledFallbackToBuildGameEntity)
  1143. {
  1144. // Create an editor component that has a RuntimeExportCallback and suppresses itself from exporting, but says it wasn't handled
  1145. CreateTestExportEditorEntity("EntityWithEditorComponent", SliceTestExportEditorComponent::ExportComponentType::EXPORT_NULL_COMPONENT, false);
  1146. if (!CompileSlice())
  1147. {
  1148. return;
  1149. }
  1150. // Expected result: exported slice contains the runtime component, because the fallback to BuildGameEntity()
  1151. // produced a runtime component.
  1152. AZ::Entity* entity = GetCompiledEntity("EntityWithEditorComponent");
  1153. EXPECT_TRUE(entity);
  1154. EXPECT_FALSE(entity->FindComponent<SliceTestExportEditorComponent>());
  1155. EXPECT_TRUE(entity->FindComponent<TestExportRuntimeComponent>());
  1156. EXPECT_FALSE(entity->FindComponent<TestExportOtherRuntimeComponent>());
  1157. }
  1158. TEST_F(SliceCompilerTest, RuntimeExportCallback_EditorComponentFailsToExportItself)
  1159. {
  1160. // Create an editor component that has a RuntimeExportCallback and suppresses itself from exporting, but says it wasn't handled
  1161. CreateTestExportEditorEntity("EntityWithEditorComponent", SliceTestExportEditorComponent::ExportComponentType::EXPORT_EDITOR_COMPONENT, true);
  1162. // We expect the slice compilation to fail, since an editor component is being exported as a game component
  1163. CompileSlice(false);
  1164. }
  1165. } // namespace UnitTest