Dumper.cpp 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  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 <Dumper.h> // Moved to the top because AssetSerializer requires include for the SerializeContext
  9. #include <AzCore/Asset/AssetManager.h>
  10. #include <AzCore/Asset/AssetSerializer.h>
  11. #include <AzCore/Asset/AssetManagerBus.h>
  12. #include <AzFramework/Helpers/AssetHelpers.h>
  13. #include <AzCore/Casting/lossy_cast.h>
  14. #include <AzCore/Debug/Trace.h>
  15. #include <AzCore/IO/FileIO.h>
  16. #include <AzCore/IO/GenericStreams.h>
  17. #include <AzCore/IO/Path/Path.h>
  18. #include <AzCore/IO/SystemFile.h>
  19. #include <AzCore/JSON/stringbuffer.h>
  20. #include <AzCore/JSON/pointer.h>
  21. #include <AzCore/JSON/prettywriter.h>
  22. #include <AzCore/Serialization/EditContext.h>
  23. #include <AzCore/Serialization/Json/JsonSerialization.h>
  24. #include <AzCore/Serialization/Json/JsonSerializationSettings.h>
  25. #include <AzCore/Settings/TextParser.h>
  26. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  27. #include <AzCore/std/algorithm.h>
  28. #include <AzCore/std/sort.h>
  29. #include <AzCore/StringFunc/StringFunc.h>
  30. #include <AzCore/Utils/Utils.h>
  31. #include <Application.h>
  32. #include <Utilities.h>
  33. #include <AzFramework/Asset/AssetSystemBus.h>
  34. namespace AZ::SerializeContextTools
  35. {
  36. inline namespace Streams
  37. {
  38. // Using an inline namespace for the Function Object Stream
  39. /*
  40. * Implementation of the GenericStream interface
  41. * that uses a function object for writing
  42. */
  43. class FunctorStream
  44. : public AZ::IO::GenericStream
  45. {
  46. public:
  47. using WriteCallback = AZStd::function<AZ::IO::SizeType(AZStd::span<AZStd::byte const>)>;
  48. FunctorStream() = default;
  49. FunctorStream(WriteCallback writeCallback);
  50. bool IsOpen() const override;
  51. bool CanSeek() const override;
  52. bool CanRead() const override;
  53. bool CanWrite() const override;
  54. void Seek(AZ::IO::OffsetType, SeekMode) override;
  55. AZ::IO::SizeType Read(AZ::IO::SizeType, void*) override;
  56. AZ::IO::SizeType Write(AZ::IO::SizeType bytes, const void* iBuffer) override;
  57. AZ::IO::SizeType GetCurPos() const override;
  58. AZ::IO::SizeType GetLength() const override;
  59. AZ::IO::OpenMode GetModeFlags() const override;
  60. const char* GetFilename() const override;
  61. private:
  62. WriteCallback m_streamWriter;
  63. };
  64. FunctorStream::FunctorStream(WriteCallback writeCallback)
  65. : m_streamWriter { AZStd::move(writeCallback)}
  66. {}
  67. bool FunctorStream::IsOpen() const
  68. {
  69. return static_cast<bool>(m_streamWriter);
  70. }
  71. bool FunctorStream::CanSeek() const
  72. {
  73. return false;
  74. }
  75. bool FunctorStream::CanRead() const
  76. {
  77. return false;
  78. }
  79. bool FunctorStream::CanWrite() const
  80. {
  81. return true;
  82. }
  83. void FunctorStream::Seek(AZ::IO::OffsetType, SeekMode)
  84. {
  85. AZ_Assert(false, "Cannot seek in stdout stream");
  86. }
  87. AZ::IO::SizeType FunctorStream::Read(AZ::IO::SizeType, void*)
  88. {
  89. AZ_Assert(false, "The stdout file handle cannot be read from");
  90. return 0;
  91. }
  92. AZ::IO::SizeType FunctorStream::Write(AZ::IO::SizeType bytes, const void* iBuffer)
  93. {
  94. if (m_streamWriter)
  95. {
  96. return m_streamWriter(AZStd::span(reinterpret_cast<const AZStd::byte*>(iBuffer), bytes));
  97. }
  98. return 0;
  99. }
  100. AZ::IO::SizeType FunctorStream::GetCurPos() const
  101. {
  102. return 0;
  103. }
  104. AZ::IO::SizeType FunctorStream::GetLength() const
  105. {
  106. return 0;
  107. }
  108. AZ::IO::OpenMode FunctorStream::GetModeFlags() const
  109. {
  110. return AZ::IO::OpenMode::ModeWrite;
  111. }
  112. const char* FunctorStream::GetFilename() const
  113. {
  114. return "<function object>";
  115. }
  116. }
  117. } // namespace AZ::SerializeContextTools::inline Stream
  118. namespace AZ::SerializeContextTools
  119. {
  120. static auto GetWriteBypassStdoutCapturerFunctor(Application& application)
  121. {
  122. return [&application](AZStd::span<AZStd::byte const> outputBytes)
  123. {
  124. // If the application is currently capturing stdout, use stdout capturer to write
  125. // directly to the file descriptor of stdout
  126. if (AZ::IO::FileDescriptorCapturer* stdoutCapturer = application.GetStdoutCapturer();
  127. stdoutCapturer != nullptr)
  128. {
  129. return stdoutCapturer->WriteBypassingCapture(outputBytes.data(), aznumeric_caster(outputBytes.size()));
  130. }
  131. else
  132. {
  133. constexpr int StdoutDescriptor = 1;
  134. return AZ::IO::PosixInternal::Write(StdoutDescriptor, outputBytes.data(), aznumeric_caster(outputBytes.size()));
  135. }
  136. };
  137. }
  138. const char* CleanupRelativePath(const char* path)
  139. {
  140. if (!path)
  141. {
  142. AZ_Assert(path, "(SerializeContextTools) - CleanupRelativePath(nullptr)!");
  143. }
  144. else
  145. {
  146. while (*path == '.' || *path == '/' || *path == '\\') // remove leading slashes in relative paths
  147. {
  148. ++path;
  149. }
  150. AZ_Warning("SerializeContextTools", *path, "CleanupRelativePath(path) Evaluates to empty path!");
  151. }
  152. return path;
  153. }
  154. bool Dumper::CreateDependencyList(Application& application)
  155. {
  156. SerializeContext* sc = application.GetSerializeContext();
  157. if (!sc)
  158. {
  159. AZ_Error("SerializeContextTools", false, "No serialize context found.");
  160. return false;
  161. }
  162. AZStd::string outputFolder = Utilities::ReadOutputTargetFromCommandLine(application);
  163. AZ::IO::Path sourceGameFolder;
  164. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  165. {
  166. settingsRegistry->Get(sourceGameFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
  167. }
  168. bool result = true;
  169. AZStd::vector<AZStd::string> fileList = Utilities::ReadFileListFromCommandLine(application, "files");
  170. for (const AZStd::string& filePath : fileList)
  171. {
  172. AZ::IO::FixedMaxPath outputPath{ AZStd::string_view{ outputFolder } };
  173. outputPath /= AZ::IO::PathView{ filePath }.Filename();
  174. outputPath.Native() += ".json";
  175. rapidjson::Document doc;
  176. rapidjson::Value& root = doc.SetObject();
  177. rapidjson::Value scObject;
  178. scObject.SetArray();
  179. IO::SystemFile outputFile;
  180. if (!outputFile.Open(outputPath.c_str(), IO::SystemFile::OpenMode::SF_OPEN_CREATE | IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
  181. IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
  182. {
  183. AZ_Error("SerializeContextTools", false, "Unable to open file '%s' for writing.", outputPath.c_str());
  184. result = false;
  185. continue;
  186. }
  187. auto relativePath = CleanupRelativePath(filePath.c_str());
  188. AZ::Data::AssetId assetId;
  189. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, relativePath, AZ::AzTypeInfo<AZ::DynamicSliceAsset>::Uuid(), false);
  190. AZStd::vector<AZ::Data::AssetInfo> dependencyInfoList;
  191. Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> dependenciesResult = Failure(AZStd::string());
  192. AZ::Data::AssetCatalogRequestBus::BroadcastResult(dependenciesResult, &AZ::Data::AssetCatalogRequestBus::Events::GetAllProductDependencies, assetId);
  193. for (const auto& ass : dependenciesResult.GetValue())
  194. {
  195. AZStd::string assetPath;
  196. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, ass.m_assetId);
  197. if (!assetPath.empty())
  198. {
  199. rapidjson::Value str(rapidjson::kStringType);
  200. str.SetString(assetPath.c_str(), doc.GetAllocator());
  201. scObject.PushBack(AZStd::move(str), doc.GetAllocator());
  202. }
  203. }
  204. root.AddMember("assets", AZStd::move(scObject), doc.GetAllocator());
  205. rapidjson::StringBuffer buffer;
  206. rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
  207. doc.Accept(writer);
  208. outputFile.Write(buffer.GetString(), buffer.GetSize());
  209. outputFile.Close();
  210. }
  211. return result;
  212. }
  213. bool Dumper::DumpFiles(Application& application)
  214. {
  215. SerializeContext* sc = application.GetSerializeContext();
  216. if (!sc)
  217. {
  218. AZ_Error("SerializeContextTools", false, "No serialize context found.");
  219. return false;
  220. }
  221. AZStd::string outputFolder = Utilities::ReadOutputTargetFromCommandLine(application);
  222. AZ::IO::Path sourceGameFolder;
  223. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  224. {
  225. settingsRegistry->Get(sourceGameFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
  226. }
  227. bool result = true;
  228. AZStd::vector<AZStd::string> fileList = Utilities::ReadFileListFromCommandLine(application, "files");
  229. for (const AZStd::string& filePath : fileList)
  230. {
  231. AZ_Printf("DumpFiles", "Dumping file '%.*s'\n", aznumeric_cast<int>(filePath.size()), filePath.data());
  232. AZ::IO::FixedMaxPath outputPath{ AZStd::string_view{ outputFolder }};
  233. outputPath /= AZ::IO::FixedMaxPath(filePath).LexicallyRelative(sourceGameFolder);
  234. outputPath.Native() += ".dump.txt";
  235. IO::SystemFile outputFile;
  236. if (!outputFile.Open(outputPath.c_str(),
  237. IO::SystemFile::OpenMode::SF_OPEN_CREATE |
  238. IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
  239. IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
  240. {
  241. AZ_Error("SerializeContextTools", false, "Unable to open file '%s' for writing.", outputPath.c_str());
  242. result = false;
  243. continue;
  244. }
  245. AZStd::string content;
  246. content.reserve(1 * 1024 * 1024); // Reserve 1mb to avoid frequently resizing the string.
  247. auto callback = [&content, &result](void* classPtr, const Uuid& classId, SerializeContext* context)
  248. {
  249. result = DumpClassContent(content, classPtr, classId, context) && result;
  250. const SerializeContext::ClassData* classData = context->FindClassData(classId);
  251. if (classData && classData->m_factory)
  252. {
  253. classData->m_factory->Destroy(classPtr);
  254. }
  255. else
  256. {
  257. AZ_Error("SerializeContextTools", false, "Missing class factory, so data will leak.");
  258. result = false;
  259. }
  260. };
  261. if (!Utilities::InspectSerializedFile(filePath.c_str(), sc, callback))
  262. {
  263. result = false;
  264. continue;
  265. }
  266. outputFile.Write(content.data(), content.length());
  267. }
  268. return result;
  269. }
  270. bool Dumper::DumpSerializeContext(Application& application)
  271. {
  272. AZStd::string outputPath = Utilities::ReadOutputTargetFromCommandLine(application, "SerializeContext.json");
  273. AZ_Printf("dumpsc", "Writing Serialize Context at '%s'.\n", outputPath.c_str());
  274. IO::SystemFile outputFile;
  275. if (!outputFile.Open(outputPath.c_str(),
  276. IO::SystemFile::OpenMode::SF_OPEN_CREATE |
  277. IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
  278. IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
  279. {
  280. AZ_Error("SerializeContextTools", false, "Unable to open output file '%s'.", outputPath.c_str());
  281. return false;
  282. }
  283. SerializeContext* context = application.GetSerializeContext();
  284. AZStd::vector<Uuid> systemComponents = Utilities::GetSystemComponents(application);
  285. AZStd::sort(systemComponents.begin(), systemComponents.end());
  286. rapidjson::Document doc;
  287. rapidjson::Value& root = doc.SetObject();
  288. rapidjson::Value scObject;
  289. scObject.SetObject();
  290. AZStd::string temp;
  291. temp.reserve(256 * 1024); // Reserve 256kb of memory to avoid the string constantly resizing.
  292. bool result = true;
  293. auto callback = [context, &doc, &scObject, &temp, &systemComponents, &result](const SerializeContext::ClassData* classData, const Uuid& /*typeId*/) -> bool
  294. {
  295. if (!DumpClassContent(classData, scObject, doc, systemComponents, context, temp))
  296. {
  297. result = false;
  298. }
  299. return true;
  300. };
  301. context->EnumerateAll(callback, true);
  302. root.AddMember("SerializeContext", AZStd::move(scObject), doc.GetAllocator());
  303. rapidjson::StringBuffer buffer;
  304. rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
  305. doc.Accept(writer);
  306. outputFile.Write(buffer.GetString(), buffer.GetSize());
  307. outputFile.Close();
  308. return result;
  309. }
  310. bool Dumper::DumpTypes(Application& application)
  311. {
  312. // outputStream defaults to writing to stdout
  313. AZStd::variant<FunctorStream, AZ::IO::SystemFileStream> outputStream(AZStd::in_place_type<FunctorStream>,
  314. GetWriteBypassStdoutCapturerFunctor(application));
  315. AZ::CommandLine& commandLine = *application.GetAzCommandLine();
  316. // If the output-file parameter has been supplied open the file path using FileIOStream
  317. if (size_t optionCount = commandLine.GetNumSwitchValues("output-file"); optionCount > 0)
  318. {
  319. AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
  320. // If the output file name is a single dash, use the default output stream value which writes to stdout
  321. if (outputPathView != "-")
  322. {
  323. AZ::IO::FixedMaxPath outputPath;
  324. if (outputPathView.IsRelative())
  325. {
  326. AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
  327. }
  328. else
  329. {
  330. outputPath = outputPathView.LexicallyNormal();
  331. }
  332. constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
  333. if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
  334. !fileStream.IsOpen())
  335. {
  336. AZ_Printf(
  337. "dumptypes",
  338. R"(Unable to open specified output-file "%s". Object will not be dumped)"
  339. "\n",
  340. outputPath.c_str());
  341. return false;
  342. }
  343. }
  344. }
  345. AZ::SerializeContext* context = application.GetSerializeContext();
  346. struct TypeNameIdPair
  347. {
  348. AZStd::string m_name;
  349. AZ::TypeId m_id;
  350. bool operator==(const TypeNameIdPair& other) const
  351. {
  352. return m_name == other.m_name && m_id == other.m_id;
  353. }
  354. bool operator!=(const TypeNameIdPair& other) const
  355. {
  356. return !operator==(other);
  357. }
  358. struct Hash
  359. {
  360. size_t operator()(const TypeNameIdPair& key) const
  361. {
  362. size_t hashValue{};
  363. AZStd::hash_combine(hashValue, key.m_name);
  364. AZStd::hash_combine(hashValue, key.m_id);
  365. return hashValue;
  366. }
  367. };
  368. };
  369. // Append the Type names and type ids to a unordered_set to filter out duplicates
  370. AZStd::unordered_set<TypeNameIdPair, TypeNameIdPair::Hash> typeNameIdPairsSet;
  371. auto AppendTypeInfo = [&typeNameIdPairsSet](const SerializeContext::ClassData* classData, const Uuid&) -> bool
  372. {
  373. typeNameIdPairsSet.emplace(TypeNameIdPair{ classData->m_name, classData->m_typeId });
  374. return true;
  375. };
  376. context->EnumerateAll(AppendTypeInfo, true);
  377. // Move over the unordered set over to an array for later
  378. // The array is potentially sorted depending on the sort option
  379. AZStd::vector<TypeNameIdPair> typeNameIdPairs(
  380. AZStd::make_move_iterator(typeNameIdPairsSet.begin()),
  381. AZStd::make_move_iterator(typeNameIdPairsSet.end())
  382. );
  383. // Clear out the Unordered set container
  384. typeNameIdPairsSet = {};
  385. // Sort the TypeNameIdPair based on the --sort option value or by type name if not supplied
  386. enum class SortOptions
  387. {
  388. Name,
  389. TypeId,
  390. None
  391. };
  392. SortOptions sortOption{ SortOptions::Name };
  393. if (size_t sortOptionCount = commandLine.GetNumSwitchValues("sort"); sortOptionCount > 0)
  394. {
  395. AZStd::string sortOptionString = commandLine.GetSwitchValue("sort", sortOptionCount - 1);
  396. if (sortOptionString == "name")
  397. {
  398. sortOption = SortOptions::Name;
  399. }
  400. if (sortOptionString == "typeid")
  401. {
  402. sortOption = SortOptions::TypeId;
  403. }
  404. else if (sortOptionString == "none")
  405. {
  406. sortOption = SortOptions::None;
  407. }
  408. else
  409. {
  410. AZ_Error(
  411. "dumptypes", false,
  412. R"(Invalid --sort option supplied "%s".)"
  413. " Sorting by type name will be used. See --help for valid values)",
  414. sortOptionString.c_str());
  415. }
  416. }
  417. switch (sortOption)
  418. {
  419. case SortOptions::Name:
  420. {
  421. auto SortByName = [](const TypeNameIdPair& lhs, const TypeNameIdPair& rhs)
  422. {
  423. return azstricmp(lhs.m_name.c_str(), rhs.m_name.c_str()) < 0;
  424. };
  425. AZStd::sort(typeNameIdPairs.begin(), typeNameIdPairs.end(), SortByName);
  426. break;
  427. }
  428. case SortOptions::TypeId:
  429. {
  430. auto SortByTypeId = [](const TypeNameIdPair& lhs, const TypeNameIdPair& rhs)
  431. {
  432. return lhs.m_id < rhs.m_id;
  433. };
  434. AZStd::sort(typeNameIdPairs.begin(), typeNameIdPairs.end(), SortByTypeId);
  435. break;
  436. }
  437. case SortOptions::None:
  438. [[fallthrough]];
  439. default:
  440. break;
  441. }
  442. auto GetOutputPathString = [](auto&& stream) -> const char*
  443. {
  444. return stream.GetFilename();
  445. };
  446. AZ_Printf(
  447. "dumptypes",
  448. R"(Writing reflected types to "%s".)"
  449. "\n",
  450. AZStd::visit(GetOutputPathString, outputStream));
  451. auto WriteReflectedTypes = [&typeNameIdPairs](auto&& stream) -> bool
  452. {
  453. AZStd::string typeListContents;
  454. for (auto&& [typeName, typeId] : typeNameIdPairs)
  455. {
  456. typeListContents += AZStd::string::format("%s %s\n", typeName.c_str(), typeId.ToFixedString().c_str());
  457. }
  458. stream.Write(typeListContents.size(), typeListContents.data());
  459. stream.Close();
  460. return true;
  461. };
  462. const bool result = AZStd::visit(WriteReflectedTypes, outputStream);
  463. return result;
  464. }
  465. bool Dumper::CreateType(Application& application)
  466. {
  467. // outputStream defaults to writing to stdout
  468. AZStd::variant<FunctorStream, AZ::IO::SystemFileStream> outputStream(AZStd::in_place_type<FunctorStream>,
  469. GetWriteBypassStdoutCapturerFunctor(application));
  470. AZ::CommandLine& commandLine = *application.GetAzCommandLine();
  471. // If the output-file parameter has been supplied open the file path using FileIOStream
  472. if (size_t optionCount = commandLine.GetNumSwitchValues("output-file"); optionCount > 0)
  473. {
  474. AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
  475. // If the output file name is a single dash, use the default output stream value which writes to stdout
  476. if (outputPathView != "-")
  477. {
  478. AZ::IO::FixedMaxPath outputPath;
  479. if (outputPathView.IsRelative())
  480. {
  481. AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
  482. }
  483. else
  484. {
  485. outputPath = outputPathView.LexicallyNormal();
  486. }
  487. constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
  488. if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
  489. !fileStream.IsOpen())
  490. {
  491. AZ_Printf(
  492. "createtype",
  493. R"(Unable to open specified output-file "%s". Object will not be dumped)"
  494. "\n",
  495. outputPath.c_str());
  496. return false;
  497. }
  498. }
  499. }
  500. size_t typeIdOptionCount = commandLine.GetNumSwitchValues("type-id");
  501. size_t typeNameOptionCount = commandLine.GetNumSwitchValues("type-name");
  502. if (typeIdOptionCount == 0 && typeNameOptionCount == 0)
  503. {
  504. AZ_Error("createtype", false, "One of the following options must be supplied: --type-id or --type-name");
  505. return false;
  506. }
  507. if (typeIdOptionCount > 0 && typeNameOptionCount > 0)
  508. {
  509. AZ_Error("createtype", false, "The --type-id and --type-name options are mutally exclusive. Only one can be specified");
  510. return false;
  511. }
  512. AZ::SerializeContext* context = application.GetSerializeContext();
  513. const AZ::SerializeContext::ClassData* classData = nullptr;
  514. if (typeIdOptionCount > 0)
  515. {
  516. AZStd::string typeIdValue = commandLine.GetSwitchValue("type-id", typeIdOptionCount - 1);
  517. classData = context->FindClassData(AZ::TypeId(typeIdValue.c_str(), typeIdValue.size()));
  518. if (classData == nullptr)
  519. {
  520. AZ_Error("createtype", false, "Type with ID %s is not registered with the SerializeContext", typeIdValue.c_str());
  521. return false;
  522. }
  523. }
  524. else
  525. {
  526. AZStd::string typeNameValue = commandLine.GetSwitchValue("type-name", typeNameOptionCount - 1);
  527. AZStd::vector<AZ::TypeId> classIds = context->FindClassId(AZ::Crc32{ AZStd::string_view{ typeNameValue } });
  528. if (classIds.size() != 1)
  529. {
  530. if (classIds.empty())
  531. {
  532. AZ_Error("createtype", false, "Type with name %s is not registered with the SerializeContext", typeNameValue.c_str());
  533. }
  534. else
  535. {
  536. const char* prependComma = "";
  537. AZStd::string classIdString;
  538. for (const AZ::TypeId& classId : classIds)
  539. {
  540. classIdString += prependComma + classId.ToString<AZStd::string>();
  541. prependComma = ", ";
  542. }
  543. AZ_Error(
  544. "createtype", classIds.size() < 2,
  545. "Multiple types with name %s have been registered with the SerializeContext,\n"
  546. "In order to disambiguate which type to use, the --type-id argument must be supplied with one of the following "
  547. "Uuids:\n",
  548. "%s", typeNameValue.c_str(), classIdString.c_str());
  549. }
  550. return false;
  551. }
  552. // Only one class with this typename has been registered with the serialize context, so look up its ClassData
  553. classData = context->FindClassData(classIds.front());
  554. }
  555. // Create a rapidjson document to store the default constructed object
  556. const AZStd::any typeInst = context->CreateAny(classData->m_typeId);
  557. rapidjson::Document document;
  558. rapidjson::Value& root = document.SetObject();
  559. AZ::JsonSerializerSettings serializerSettings;
  560. serializerSettings.m_serializeContext = context;
  561. serializerSettings.m_registrationContext = application.GetJsonRegistrationContext();
  562. serializerSettings.m_keepDefaults = true;
  563. using JsonResultCode = AZ::JsonSerializationResult::ResultCode;
  564. const JsonResultCode parseResult = AZ::JsonSerialization::Store(
  565. root, document.GetAllocator(), AZStd::any_cast<void>(&typeInst), nullptr, typeInst.type(), serializerSettings);
  566. if (parseResult.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
  567. {
  568. AZ_Printf("createtype", " Failed to store type %s in JSON format.\n", classData->m_name);
  569. return false;
  570. }
  571. auto GetOutputPathString = [](auto&& stream)
  572. {
  573. using StreamType = AZStd::remove_cvref_t<decltype(stream)>;
  574. if constexpr (AZStd::is_same_v<StreamType, AZ::IO::StdoutStream>)
  575. {
  576. return AZ::IO::FixedMaxPath{ "<stdout>" };
  577. }
  578. else if (AZStd::is_same_v<StreamType, AZ::IO::FileIOStream>)
  579. {
  580. return AZ::IO::FixedMaxPath{ stream.GetFilename() };
  581. }
  582. else
  583. {
  584. AZ_Assert(false, "OutputStream has invalid stream type. It must be StdoutStream or FileIOStream");
  585. return AZ::IO::FixedMaxPath{};
  586. }
  587. };
  588. AZ_Printf(
  589. "createtype",
  590. R"(Writing Type "%s" to "%s" using Json Serialization.)"
  591. "\n",
  592. classData->m_name, AZStd::visit(GetOutputPathString, outputStream).c_str());
  593. AZStd::string jsonDocumentRootPrefix;
  594. if (commandLine.HasSwitch("json-prefix"))
  595. {
  596. jsonDocumentRootPrefix = commandLine.GetSwitchValue("json-prefix", 0);
  597. }
  598. auto VisitStream = [&document, &jsonDocumentRootPrefix](auto&& stream) -> bool
  599. {
  600. if (WriteDocumentToStream(stream, document, jsonDocumentRootPrefix))
  601. {
  602. // Write out a newline to the end of the stream
  603. constexpr AZStd::string_view newline = "\n";
  604. stream.Write(newline.size(), newline.data());
  605. return true;
  606. }
  607. return false;
  608. };
  609. const bool result = AZStd::visit(VisitStream, outputStream);
  610. return result;
  611. }
  612. bool Dumper::CreateUuid(Application& application)
  613. {
  614. // outputStream defaults to writing to stdout
  615. AZStd::variant<FunctorStream, AZ::IO::SystemFileStream> outputStream(AZStd::in_place_type<FunctorStream>,
  616. GetWriteBypassStdoutCapturerFunctor(application));
  617. AZ::CommandLine& commandLine = *application.GetAzCommandLine();
  618. // If the output-file parameter has been supplied open the file path using FileIOStream
  619. if (size_t optionCount = commandLine.GetNumSwitchValues("output-file"); optionCount > 0)
  620. {
  621. AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
  622. // If the output file name is a single dash, use the default output stream value which writes to stdout
  623. if (outputPathView != "-")
  624. {
  625. AZ::IO::FixedMaxPath outputPath;
  626. if (outputPathView.IsRelative())
  627. {
  628. AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
  629. }
  630. else
  631. {
  632. outputPath = outputPathView.LexicallyNormal();
  633. }
  634. constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
  635. if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
  636. !fileStream.IsOpen())
  637. {
  638. AZ_Printf(
  639. "createuuid",
  640. R"(Unable to open specified output-file "%s". Uuid will not be output to stream)"
  641. "\n",
  642. outputPath.c_str());
  643. return false;
  644. }
  645. }
  646. }
  647. size_t valuesOptionCount = commandLine.GetNumSwitchValues("values");
  648. size_t valuesFileOptionCount = commandLine.GetNumSwitchValues("values-file");
  649. if (valuesOptionCount == 0 && valuesFileOptionCount == 0)
  650. {
  651. AZ_Error("createuuid", false, "One of following options must be supplied: --values or --values-file");
  652. return false;
  653. }
  654. bool withCurlyBraces = true;
  655. if (size_t withCurlyBracesOptionCount = commandLine.GetNumSwitchValues("with-curly-braces");
  656. withCurlyBracesOptionCount > 0)
  657. {
  658. withCurlyBraces = AZ::StringFunc::ToBool(commandLine.GetSwitchValue("with-curly-braces", withCurlyBracesOptionCount - 1).c_str());
  659. }
  660. bool withDashes = true;
  661. if (size_t withDashesOptionCount = commandLine.GetNumSwitchValues("with-dashes");
  662. withDashesOptionCount > 0)
  663. {
  664. withDashes = AZ::StringFunc::ToBool(commandLine.GetSwitchValue("with-dashes", withDashesOptionCount - 1).c_str());
  665. }
  666. const bool quietOutput = commandLine.HasSwitch("q") || commandLine.HasSwitch("quiet");
  667. bool result = true;
  668. struct UuidStringPair
  669. {
  670. AZ::Uuid m_uuid;
  671. AZStd::string m_value;
  672. };
  673. AZStd::vector<UuidStringPair> uuidsToWrite;
  674. for (size_t i = 0; i < valuesOptionCount; ++i)
  675. {
  676. AZStd::string value = commandLine.GetSwitchValue("values", i);
  677. auto uuidFromName = AZ::Uuid::CreateName(value);
  678. uuidsToWrite.push_back({ AZStd::move(uuidFromName), AZStd::move(value) });
  679. }
  680. // Read string values from each --values-file argument
  681. for (size_t i = 0; i < valuesFileOptionCount; ++i)
  682. {
  683. AZ::IO::FixedMaxPath inputValuePath(AZ::IO::PathView(commandLine.GetSwitchValue("values-file", i)));
  684. AZ::IO::SystemFileStream valuesFileStream;
  685. if (inputValuePath == "-")
  686. {
  687. // If the input file is dash read from stdin
  688. valuesFileStream = AZ::IO::SystemFileStream(AZ::IO::SystemFile::GetStdin());
  689. }
  690. else
  691. {
  692. // Open the path from the values-file option
  693. constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeRead;
  694. valuesFileStream.Open(inputValuePath.c_str(), openMode);
  695. }
  696. if (valuesFileStream.IsOpen())
  697. {
  698. // Use the text parser to parse plain text lines
  699. AZ::Settings::TextParserSettings textParserSettings;
  700. textParserSettings.m_parseTextEntryFunc = [&uuidsToWrite](AZStd::string_view token)
  701. {
  702. // Remove leading and surrounding spaces and carriage returns
  703. token = AZ::StringFunc::StripEnds(token, " \r");
  704. auto uuidFromName = AZ::Uuid::CreateName(token);
  705. uuidsToWrite.push_back({ AZStd::move(uuidFromName), token });
  706. return true;
  707. };
  708. AZ::Settings::ParseTextFile(valuesFileStream, textParserSettings);
  709. }
  710. }
  711. for (const UuidStringPair& uuidStringPair : uuidsToWrite)
  712. {
  713. auto VisitStream = [&uuidToWrite = uuidStringPair.m_uuid, &value = uuidStringPair.m_value,
  714. withCurlyBraces, withDashes, quietOutput](auto&& stream) -> bool
  715. {
  716. AZStd::fixed_string<256> uuidString;
  717. if (quietOutput)
  718. {
  719. uuidString = AZStd::fixed_string<256>::format("%s\n",
  720. uuidToWrite.ToFixedString(withCurlyBraces, withDashes).c_str());
  721. }
  722. else
  723. {
  724. uuidString = AZStd::fixed_string<256>::format(R"(%s %s)" "\n",
  725. uuidToWrite.ToFixedString(withCurlyBraces, withDashes).c_str(),
  726. value.c_str());
  727. }
  728. size_t bytesWritten = stream.Write(uuidString.size(), uuidString.c_str());
  729. return bytesWritten == uuidString.size();
  730. };
  731. result = AZStd::visit(VisitStream, outputStream) && result;
  732. }
  733. return result;
  734. }
  735. AZStd::vector<Uuid> Dumper::CreateFilterListByNames(SerializeContext* context, AZStd::string_view name)
  736. {
  737. AZStd::vector<AZStd::string_view> names;
  738. auto AppendNames = [&names](AZStd::string_view filename)
  739. {
  740. names.emplace_back(filename);
  741. };
  742. AZ::StringFunc::TokenizeVisitor(name, AppendNames, ';');
  743. AZStd::vector<Uuid> filterIds;
  744. filterIds.reserve(names.size());
  745. for (const AZStd::string_view& singleName : names)
  746. {
  747. AZStd::vector<Uuid> foundFilters = context->FindClassId(Crc32(singleName.data(), singleName.length(), true));
  748. filterIds.insert(filterIds.end(), foundFilters.begin(), foundFilters.end());
  749. }
  750. return filterIds;
  751. }
  752. AZStd::string_view Dumper::ExtractNamespace(const AZStd::string& name)
  753. {
  754. size_t offset = 0;
  755. const char* startChar = name.data();
  756. const char* currentChar = name.data();
  757. while (*currentChar != 0 && *currentChar != '<')
  758. {
  759. if (*currentChar != ':')
  760. {
  761. ++currentChar;
  762. }
  763. else
  764. {
  765. ++currentChar;
  766. if (*currentChar == ':')
  767. {
  768. AZ_Assert(currentChar - startChar >= 1, "Offset out of bounds while trying to extract namespace from name '%s'.", name.c_str());
  769. offset = currentChar - startChar - 1; // -1 to exclude the last "::"
  770. }
  771. }
  772. }
  773. return AZStd::string_view(startChar, offset);
  774. }
  775. rapidjson::Value Dumper::WriteToJsonValue(const Uuid& uuid, rapidjson::Document& document)
  776. {
  777. char buffer[Uuid::MaxStringBuffer];
  778. int writtenCount = uuid.ToString(buffer, AZ_ARRAY_SIZE(buffer));
  779. if (writtenCount > 0)
  780. {
  781. return rapidjson::Value(buffer, writtenCount - 1, document.GetAllocator()); //-1 as the null character shouldn't be written.
  782. }
  783. else
  784. {
  785. return rapidjson::Value(rapidjson::StringRef("{uuid conversion failed}"));
  786. }
  787. }
  788. bool Dumper::DumpClassContent(const SerializeContext::ClassData* classData, rapidjson::Value& parent, rapidjson::Document& document,
  789. const AZStd::vector<Uuid>& systemComponents, SerializeContext* context, AZStd::string& scratchStringBuffer)
  790. {
  791. AZ_Assert(scratchStringBuffer.empty(), "Provided scratch string buffer wasn't empty.");
  792. rapidjson::Value classNode(rapidjson::kObjectType);
  793. DumpClassName(classNode, context, classData, document, scratchStringBuffer);
  794. Edit::ClassData* editData = classData->m_editData;
  795. GenericClassInfo* genericClassInfo = context->FindGenericClassInfo(classData->m_typeId);
  796. if (editData && editData->m_description)
  797. {
  798. AZStd::string_view description = editData->m_description;
  799. // Skipping if there's only one character as there are several cases where a blank description is given.
  800. if (description.size() > 1)
  801. {
  802. classNode.AddMember("Description", rapidjson::Value(description.data(), document.GetAllocator()), document.GetAllocator());
  803. }
  804. }
  805. classNode.AddMember("Id", rapidjson::StringRef(classData->m_name), document.GetAllocator());
  806. classNode.AddMember("Version", classData->IsDeprecated() ?
  807. rapidjson::Value(rapidjson::StringRef("Deprecated")) : rapidjson::Value(classData->m_version), document.GetAllocator());
  808. auto systemComponentIt = AZStd::lower_bound(systemComponents.begin(), systemComponents.end(), classData->m_typeId);
  809. const bool isSystemComponent = systemComponentIt != systemComponents.end() && *systemComponentIt == classData->m_typeId;
  810. classNode.AddMember("IsSystemComponent", isSystemComponent, document.GetAllocator());
  811. const bool isComponent = isSystemComponent || (classData->m_azRtti != nullptr && classData->m_azRtti->IsTypeOf<AZ::Component>());
  812. classNode.AddMember("IsComponent", isComponent, document.GetAllocator());
  813. classNode.AddMember("IsPrimitive", Utilities::IsSerializationPrimitive(genericClassInfo ? genericClassInfo->GetGenericTypeId() : classData->m_typeId), document.GetAllocator());
  814. classNode.AddMember("IsContainer", classData->m_container != nullptr, document.GetAllocator());
  815. if (genericClassInfo)
  816. {
  817. classNode.AddMember("GenericUuid", WriteToJsonValue(genericClassInfo->GetGenericTypeId(), document), document.GetAllocator());
  818. classNode.AddMember("Generics", DumpGenericStructure(genericClassInfo, context, document, scratchStringBuffer), document.GetAllocator());
  819. }
  820. if (!classData->m_elements.empty())
  821. {
  822. rapidjson::Value fields(rapidjson::kArrayType);
  823. rapidjson::Value bases(rapidjson::kArrayType);
  824. for (const SerializeContext::ClassElement& element : classData->m_elements)
  825. {
  826. DumpElementInfo(element, classData, context, fields, bases, document, scratchStringBuffer);
  827. }
  828. if (!bases.Empty())
  829. {
  830. classNode.AddMember("Bases", AZStd::move(bases), document.GetAllocator());
  831. }
  832. if (!fields.Empty())
  833. {
  834. classNode.AddMember("Fields", AZStd::move(fields), document.GetAllocator());
  835. }
  836. }
  837. parent.AddMember(WriteToJsonValue(classData->m_typeId, document), AZStd::move(classNode), document.GetAllocator());
  838. return true;
  839. }
  840. bool Dumper::DumpClassContent(AZStd::string& output, void* classPtr, const Uuid& classId, SerializeContext* context)
  841. {
  842. const SerializeContext::ClassData* classData = context->FindClassData(classId);
  843. if (!classData)
  844. {
  845. AZ_Printf("", " Class data for '%s' is missing.\n", classId.ToString<AZStd::string>().c_str());
  846. return false;
  847. }
  848. size_t indention = 0;
  849. auto begin = [context, &output, &indention](void* /*instance*/, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* classElement) -> bool
  850. {
  851. for (size_t i = 0; i < indention; ++i)
  852. {
  853. output += ' ';
  854. }
  855. if (classData)
  856. {
  857. output += classData->m_name;
  858. }
  859. DumpElementInfo(output, classElement, context);
  860. DumpPrimitiveTag(output, classData, classElement);
  861. output += '\n';
  862. indention += 2;
  863. return true;
  864. };
  865. auto end = [&indention]() -> bool
  866. {
  867. indention = indention > 0 ? indention - 2 : 0;
  868. return true;
  869. };
  870. SerializeContext::EnumerateInstanceCallContext callContext(begin, end, context, SerializeContext::ENUM_ACCESS_FOR_WRITE, nullptr);
  871. context->EnumerateInstance(&callContext, classPtr, classId, classData, nullptr);
  872. return true;
  873. }
  874. void Dumper::DumpElementInfo(const SerializeContext::ClassElement& element, const SerializeContext::ClassData* classData, SerializeContext* context,
  875. rapidjson::Value& fields, rapidjson::Value& bases, rapidjson::Document& document, AZStd::string& scratchStringBuffer)
  876. {
  877. AZ_Assert(fields.IsArray(), "Expected 'fields' to be an array.");
  878. AZ_Assert(bases.IsArray(), "Expected 'bases' to be an array.");
  879. AZ_Assert(scratchStringBuffer.empty(), "Provided scratch string buffer wasn't empty.");
  880. const SerializeContext::ClassData* elementClass = context->FindClassData(element.m_typeId, classData);
  881. AppendTypeName(scratchStringBuffer, elementClass, element.m_typeId);
  882. Uuid elementTypeId = element.m_typeId;
  883. if (element.m_genericClassInfo)
  884. {
  885. DumpGenericStructure(scratchStringBuffer, element.m_genericClassInfo, context);
  886. elementTypeId = element.m_genericClassInfo->GetSpecializedTypeId();
  887. }
  888. if ((element.m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0)
  889. {
  890. scratchStringBuffer += '*';
  891. }
  892. rapidjson::Value elementTypeString(scratchStringBuffer.c_str(), document.GetAllocator());
  893. scratchStringBuffer.clear();
  894. if ((element.m_flags & SerializeContext::ClassElement::FLG_BASE_CLASS) != 0)
  895. {
  896. rapidjson::Value baseNode(rapidjson::kObjectType);
  897. baseNode.AddMember("Type", AZStd::move(elementTypeString), document.GetAllocator());
  898. baseNode.AddMember("Uuid", WriteToJsonValue(elementTypeId, document), document.GetAllocator());
  899. bases.PushBack(AZStd::move(baseNode), document.GetAllocator());
  900. }
  901. else
  902. {
  903. rapidjson::Value elementNode(rapidjson::kObjectType);
  904. elementNode.AddMember("Name", rapidjson::StringRef(element.m_name), document.GetAllocator());
  905. elementNode.AddMember("Type", AZStd::move(elementTypeString), document.GetAllocator());
  906. elementNode.AddMember("Uuid", WriteToJsonValue(elementTypeId, document), document.GetAllocator());
  907. elementNode.AddMember("HasDefault", (element.m_flags & SerializeContext::ClassElement::FLG_NO_DEFAULT_VALUE) == 0, document.GetAllocator());
  908. elementNode.AddMember("IsDynamic", (element.m_flags & SerializeContext::ClassElement::FLG_DYNAMIC_FIELD) != 0, document.GetAllocator());
  909. elementNode.AddMember("IsPointer", (element.m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0, document.GetAllocator());
  910. elementNode.AddMember("IsUiElement", (element.m_flags & SerializeContext::ClassElement::FLG_UI_ELEMENT) != 0, document.GetAllocator());
  911. elementNode.AddMember("DataSize", static_cast<uint64_t>(element.m_dataSize), document.GetAllocator());
  912. elementNode.AddMember("Offset", static_cast<uint64_t>(element.m_offset), document.GetAllocator());
  913. Edit::ElementData* elementEditData = element.m_editData;
  914. if (elementEditData)
  915. {
  916. elementNode.AddMember("Description", rapidjson::StringRef(elementEditData->m_description), document.GetAllocator());
  917. }
  918. if (element.m_genericClassInfo)
  919. {
  920. rapidjson::Value genericArray(rapidjson::kArrayType);
  921. rapidjson::Value classObject(rapidjson::kObjectType);
  922. const SerializeContext::ClassData* genericClassData = element.m_genericClassInfo->GetClassData();
  923. classObject.AddMember("Type", rapidjson::StringRef(genericClassData->m_name), document.GetAllocator());
  924. classObject.AddMember("GenericUuid", WriteToJsonValue(element.m_genericClassInfo->GetGenericTypeId(), document), document.GetAllocator());
  925. classObject.AddMember("SpecializedUuid", WriteToJsonValue(element.m_genericClassInfo->GetSpecializedTypeId(), document), document.GetAllocator());
  926. classObject.AddMember("Generics", DumpGenericStructure(element.m_genericClassInfo, context, document, scratchStringBuffer), document.GetAllocator());
  927. genericArray.PushBack(AZStd::move(classObject), document.GetAllocator());
  928. elementNode.AddMember("Generics", AZStd::move(genericArray), document.GetAllocator());
  929. }
  930. fields.PushBack(AZStd::move(elementNode), document.GetAllocator());
  931. }
  932. }
  933. void Dumper::DumpElementInfo(AZStd::string& output, const SerializeContext::ClassElement* classElement, SerializeContext* context)
  934. {
  935. if (classElement)
  936. {
  937. if (classElement->m_genericClassInfo)
  938. {
  939. DumpGenericStructure(output, classElement->m_genericClassInfo, context);
  940. }
  941. if ((classElement->m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0)
  942. {
  943. output += '*';
  944. }
  945. output += ' ';
  946. output += classElement->m_name;
  947. if ((classElement->m_flags & SerializeContext::ClassElement::FLG_BASE_CLASS) != 0)
  948. {
  949. output += " [Base]";
  950. }
  951. }
  952. }
  953. void Dumper::DumpGenericStructure(AZStd::string& output, GenericClassInfo* genericClassInfo, SerializeContext* context)
  954. {
  955. output += '<';
  956. const SerializeContext::ClassData* classData = genericClassInfo->GetClassData();
  957. if (classData && classData->m_container)
  958. {
  959. bool firstArgument = true;
  960. auto callback = [&output, context, &firstArgument](const Uuid& elementClassId, const SerializeContext::ClassElement* genericClassElement) -> bool
  961. {
  962. if (!firstArgument)
  963. {
  964. output += ',';
  965. }
  966. else
  967. {
  968. firstArgument = false;
  969. }
  970. const SerializeContext::ClassData* argClassData = context->FindClassData(elementClassId);
  971. AppendTypeName(output, argClassData, elementClassId);
  972. if (genericClassElement->m_genericClassInfo)
  973. {
  974. DumpGenericStructure(output, genericClassElement->m_genericClassInfo, context);
  975. }
  976. if ((genericClassElement->m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0)
  977. {
  978. output += '*';
  979. }
  980. return true;
  981. };
  982. classData->m_container->EnumTypes(callback);
  983. }
  984. else
  985. {
  986. // No container information available, so as much as possible through other means, although
  987. // this might not be complete information.
  988. size_t numArgs = genericClassInfo->GetNumTemplatedArguments();
  989. for (size_t i = 0; i < numArgs; ++i)
  990. {
  991. if (i != 0)
  992. {
  993. output += ',';
  994. }
  995. const Uuid& argClassId = genericClassInfo->GetTemplatedTypeId(i);
  996. const SerializeContext::ClassData* argClass = context->FindClassData(argClassId);
  997. AppendTypeName(output, argClass, argClassId);
  998. }
  999. }
  1000. output += '>';
  1001. }
  1002. rapidjson::Value Dumper::DumpGenericStructure(GenericClassInfo* genericClassInfo, SerializeContext* context,
  1003. rapidjson::Document& parentDoc, AZStd::string& scratchStringBuffer)
  1004. {
  1005. AZ_Assert(scratchStringBuffer.empty(), "Provided scratch string buffer still contains data.");
  1006. rapidjson::Value result(rapidjson::kArrayType);
  1007. const SerializeContext::ClassData* classData = genericClassInfo->GetClassData();
  1008. if (classData && classData->m_container)
  1009. {
  1010. auto callback = [&result, context, &parentDoc, &scratchStringBuffer](const Uuid& elementClassId,
  1011. const SerializeContext::ClassElement* genericClassElement) -> bool
  1012. {
  1013. rapidjson::Value classObject(rapidjson::kObjectType);
  1014. const SerializeContext::ClassData* argClassData = context->FindClassData(elementClassId);
  1015. AppendTypeName(scratchStringBuffer, argClassData, elementClassId);
  1016. classObject.AddMember("Type", rapidjson::Value(scratchStringBuffer.c_str(), parentDoc.GetAllocator()), parentDoc.GetAllocator());
  1017. scratchStringBuffer.clear();
  1018. classObject.AddMember("IsPointer", (genericClassElement->m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0, parentDoc.GetAllocator());
  1019. if (genericClassElement->m_genericClassInfo)
  1020. {
  1021. GenericClassInfo* genericClassInfo = genericClassElement->m_genericClassInfo;
  1022. classObject.AddMember("GenericUuid", WriteToJsonValue(genericClassInfo->GetGenericTypeId(), parentDoc), parentDoc.GetAllocator());
  1023. classObject.AddMember("SpecializedUuid", WriteToJsonValue(genericClassInfo->GetSpecializedTypeId(), parentDoc), parentDoc.GetAllocator());
  1024. classObject.AddMember("Generics", DumpGenericStructure(genericClassInfo, context, parentDoc, scratchStringBuffer), parentDoc.GetAllocator());
  1025. }
  1026. else
  1027. {
  1028. classObject.AddMember("GenericUuid", WriteToJsonValue(elementClassId, parentDoc), parentDoc.GetAllocator());
  1029. classObject.AddMember("SpecializedUuid", WriteToJsonValue(elementClassId, parentDoc), parentDoc.GetAllocator());
  1030. }
  1031. result.PushBack(AZStd::move(classObject), parentDoc.GetAllocator());
  1032. return true;
  1033. };
  1034. classData->m_container->EnumTypes(callback);
  1035. }
  1036. else
  1037. {
  1038. // No container information available, so as much as possible through other means, although
  1039. // this might not be complete information.
  1040. size_t numArgs = genericClassInfo->GetNumTemplatedArguments();
  1041. for (size_t i = 0; i < numArgs; ++i)
  1042. {
  1043. const Uuid& elementClassId = genericClassInfo->GetTemplatedTypeId(i);
  1044. rapidjson::Value classObject(rapidjson::kObjectType);
  1045. const SerializeContext::ClassData* argClassData = context->FindClassData(elementClassId);
  1046. AppendTypeName(scratchStringBuffer, argClassData, elementClassId);
  1047. classObject.AddMember("Type", rapidjson::Value(scratchStringBuffer.c_str(), parentDoc.GetAllocator()), parentDoc.GetAllocator());
  1048. scratchStringBuffer.clear();
  1049. classObject.AddMember("GenericUuid",
  1050. WriteToJsonValue(argClassData ? argClassData->m_typeId : elementClassId, parentDoc), parentDoc.GetAllocator());
  1051. classObject.AddMember("SpecializedUuid", WriteToJsonValue(elementClassId, parentDoc), parentDoc.GetAllocator());
  1052. classObject.AddMember("IsPointer", false, parentDoc.GetAllocator());
  1053. result.PushBack(AZStd::move(classObject), parentDoc.GetAllocator());
  1054. }
  1055. }
  1056. return result;
  1057. }
  1058. void Dumper::DumpPrimitiveTag(AZStd::string& output, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* classElement)
  1059. {
  1060. if (classData)
  1061. {
  1062. Uuid classId = classData->m_typeId;
  1063. if (classElement && classElement->m_genericClassInfo)
  1064. {
  1065. classId = classElement->m_genericClassInfo->GetGenericTypeId();
  1066. }
  1067. if (Utilities::IsSerializationPrimitive(classId))
  1068. {
  1069. output += " [Primitive]";
  1070. }
  1071. }
  1072. }
  1073. void Dumper::DumpClassName(rapidjson::Value& parent, SerializeContext* context, const SerializeContext::ClassData* classData,
  1074. rapidjson::Document& parentDoc, AZStd::string& scratchStringBuffer)
  1075. {
  1076. AZ_Assert(scratchStringBuffer.empty(), "Scratch string buffer is not empty.");
  1077. Edit::ClassData* editData = classData->m_editData;
  1078. GenericClassInfo* genericClassInfo = context->FindGenericClassInfo(classData->m_typeId);
  1079. if (genericClassInfo)
  1080. {
  1081. // If the type itself is a generic, dump it's information.
  1082. scratchStringBuffer = classData->m_name;
  1083. DumpGenericStructure(scratchStringBuffer, genericClassInfo, context);
  1084. }
  1085. else
  1086. {
  1087. bool hasEditName = editData && editData->m_name && strlen(editData->m_name) > 0;
  1088. scratchStringBuffer = hasEditName ? editData->m_name : classData->m_name;
  1089. }
  1090. AZStd::string_view namespacePortion = ExtractNamespace(scratchStringBuffer);
  1091. if (!namespacePortion.empty())
  1092. {
  1093. parent.AddMember("Namespace",
  1094. rapidjson::Value(namespacePortion.data(), azlossy_caster(namespacePortion.length()), parentDoc.GetAllocator()),
  1095. parentDoc.GetAllocator());
  1096. parent.AddMember("Name", rapidjson::Value(scratchStringBuffer.c_str() + namespacePortion.length() + 2, parentDoc.GetAllocator()), parentDoc.GetAllocator());
  1097. }
  1098. else
  1099. {
  1100. parent.AddMember("Name", rapidjson::Value(scratchStringBuffer.c_str(), parentDoc.GetAllocator()), parentDoc.GetAllocator());
  1101. }
  1102. scratchStringBuffer.clear();
  1103. }
  1104. void Dumper::AppendTypeName(AZStd::string& output, const SerializeContext::ClassData* classData, const Uuid& classId)
  1105. {
  1106. if (classData)
  1107. {
  1108. output += classData->m_name;
  1109. }
  1110. else if (classId == GetAssetClassId())
  1111. {
  1112. output += "Asset";
  1113. }
  1114. else
  1115. {
  1116. output += classId.ToString<AZStd::string>();
  1117. }
  1118. }
  1119. bool Dumper::WriteDocumentToStream(AZ::IO::GenericStream& outputStream, const rapidjson::Document& document,
  1120. AZStd::string_view pointerRoot)
  1121. {
  1122. rapidjson::StringBuffer scratchBuffer;
  1123. rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(scratchBuffer);
  1124. // rapidjson::Pointer constructor attempts to dereference the const char* index 0 even if the size is 0
  1125. // so make sure the string_view isn't referencing a nullptr
  1126. rapidjson::Pointer jsonPointerAnchor(pointerRoot.data() ? pointerRoot.data() : "", pointerRoot.size());
  1127. // Anchor the content in the Json Document under the Json Pointer root path
  1128. rapidjson::Document rootDocument;
  1129. rapidjson::SetValueByPointer(rootDocument, jsonPointerAnchor, document);
  1130. rootDocument.Accept(writer);
  1131. outputStream.Write(scratchBuffer.GetSize(), scratchBuffer.GetString());
  1132. scratchBuffer.Clear();
  1133. return true;
  1134. }
  1135. // namespace AZ::SerializeContextTools
  1136. }