AssetBuilderComponent.cpp 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  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 <AssetBuilderApplication.h>
  9. #include <AzCore/Asset/AssetManager.h>
  10. #include <AzCore/Component/TickBus.h>
  11. #include <AzCore/Component/ComponentApplicationBus.h>
  12. #include <AzCore/Component/ComponentApplication.h>
  13. #include <AzCore/IO/IStreamer.h>
  14. #include <AzCore/IO/Path/Path.h>
  15. #include <AzCore/RTTI/RTTI.h>
  16. #include <AzCore/Serialization/Utils.h>
  17. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  18. #include <AzCore/StringFunc/StringFunc.h>
  19. #include <AzCore/std/algorithm.h>
  20. #include <AzCore/Utils/Utils.h>
  21. #include <AzFramework/Asset/AssetProcessorMessages.h>
  22. #include <AzFramework/Asset/AssetSystemBus.h>
  23. #include <AzFramework/IO/LocalFileIO.h>
  24. #include <AzFramework/Network/AssetProcessorConnection.h>
  25. #include <AzFramework/Platform/PlatformDefaults.h>
  26. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  27. #include <AzToolsFramework/Debug/TraceContext.h>
  28. #include <AssetBuilderSDK/AssetBuilderSDK.h>
  29. #include <AssetBuilderComponent.h>
  30. #include <AssetBuilderInfo.h>
  31. #include <AzCore/Memory/AllocatorManager.h>
  32. #include <AssetBuilderSDK/AssetBuilderBusses.h>
  33. #include <AzCore/Interface/Interface.h>
  34. #include <AzFramework/Asset/AssetSystemComponent.h>
  35. #include <ToolsComponents/ToolsAssetCatalogComponent.h>
  36. // Command-line parameter options:
  37. static const char* const s_paramHelp = "help"; // Print help information.
  38. static const char* const s_paramTask = "task"; // Task to run.
  39. static const char* const s_paramProjectName = "project-name"; // Name of the current project.
  40. static const char* const s_paramProjectCacheRoot = "project-cache-path"; // Full path to the project cache folder.
  41. static const char* const s_paramModule = "module"; // For resident mode, the path to the builder dll folder, otherwise the full path to a single builder dll to use.
  42. static const char* const s_paramPort = "port"; // Optional, port number to use to connect to the AP.
  43. static const char* const s_paramIp = "remoteip"; // optional, IP address to use to connect to the AP
  44. static const char* const s_paramId = "id"; // UUID string that identifies the builder. Only used for resident mode when the AP directly starts up the AssetBuilder.
  45. static const char* const s_paramInput = "input"; // For non-resident mode, full path to the file containing the serialized job request.
  46. static const char* const s_paramOutput = "output"; // For non-resident mode, full path to the file to write the job response to.
  47. static const char* const s_paramDebug = "debug"; // Debug mode for the create and process job of the specified file.
  48. static const char* const s_paramDebugCreate = "debug_create"; // Debug mode for the create job of the specified file.
  49. static const char* const s_paramDebugProcess = "debug_process"; // Debug mode for the process job of the specified file.
  50. static const char* const s_paramPlatformTags = "tags"; // Additional list of tags to add platform tag list.
  51. static const char* const s_paramPlatform = "platform"; // Platform to use
  52. // Task modes:
  53. static const char* const s_taskResident = "resident"; // stays up and running indefinitely, accepting jobs via network connection
  54. static const char* const s_taskRegisterBuilder = "register"; // outputs all the builder descriptors
  55. static const char* const s_taskCreateJob = "create"; // runs a builders createJobs function
  56. static const char* const s_taskProcessJob = "process"; // runs processJob function
  57. static const char* const s_taskDebug = "debug"; // runs a one shot job in a fake environment for a specified file.
  58. static const char* const s_taskDebugCreate = "debug_create"; // runs a one shot job in a fake environment for a specified file.
  59. static const char* const s_taskDebugProcess = "debug_process"; // runs a one shot job in a fake environment for a specified file.
  60. //! Scoped Setters for the SettingsRegistry to its previous value on destruction
  61. struct ScopedSettingsRegistrySetter
  62. {
  63. using SettingsRegistrySetterTypes = AZStd::variant<bool, AZ::s64, AZ::u64, double, AZStd::string_view>;
  64. using SettingsRegistryGetterTypes = AZStd::variant<bool, AZ::s64, AZ::u64, double, AZStd::string>;
  65. ScopedSettingsRegistrySetter(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view jsonPointer,
  66. SettingsRegistrySetterTypes newValue)
  67. : m_settingsRegistry(settingsRegistry)
  68. , m_jsonPointer(jsonPointer)
  69. {
  70. AZStd::string oldValue;
  71. if (m_settingsRegistry.Get(oldValue, jsonPointer))
  72. {
  73. m_oldValue = AZStd::move(oldValue);
  74. }
  75. AZStd::visit([this](auto&& value) {m_settingsRegistry.Set(m_jsonPointer, AZStd::move(value)); }, AZStd::move(newValue));
  76. }
  77. ~ScopedSettingsRegistrySetter()
  78. {
  79. // Reset the old value within the Settings Registry if it was set
  80. // Or remove it if not
  81. if (m_oldValue)
  82. {
  83. AZStd::visit([this](auto&& value) {m_settingsRegistry.Set(m_jsonPointer, AZStd::move(value)); }, AZStd::move(*m_oldValue));
  84. }
  85. else
  86. {
  87. m_settingsRegistry.Remove(m_jsonPointer);
  88. }
  89. }
  90. AZ::SettingsRegistryInterface& m_settingsRegistry;
  91. AZStd::string_view m_jsonPointer;
  92. AZStd::optional<SettingsRegistryGetterTypes> m_oldValue;
  93. };
  94. //! FileIO classes which resets the set key to its previous value on destruction
  95. struct ScopedAliasSetter
  96. {
  97. ScopedAliasSetter(AZ::IO::FileIOBase& fileIoBase, const char* alias,
  98. const char* newValue)
  99. : m_fileIoBase(fileIoBase)
  100. , m_alias(alias)
  101. {
  102. if (const char* oldValue = m_fileIoBase.GetAlias(m_alias); oldValue != nullptr)
  103. {
  104. m_oldValue = oldValue;
  105. }
  106. m_fileIoBase.SetAlias(alias, newValue);
  107. }
  108. ~ScopedAliasSetter()
  109. {
  110. // Reset the old alias if it was set or clear it if not
  111. if (m_oldValue)
  112. {
  113. m_fileIoBase.SetAlias(m_alias, m_oldValue->c_str());
  114. }
  115. else
  116. {
  117. m_fileIoBase.ClearAlias(m_alias);
  118. }
  119. }
  120. AZ::IO::FileIOBase& m_fileIoBase;
  121. const char* m_alias;
  122. AZStd::optional<AZStd::string> m_oldValue;
  123. };
  124. //////////////////////////////////////////////////////////////////////////
  125. void AssetBuilderComponent::PrintHelp()
  126. {
  127. AZ_TracePrintf("Help", "\nAssetBuilder is part of the Asset Processor so tasks are run in an isolated environment.\n");
  128. AZ_TracePrintf("Help", "The following command line options are available for the AssetBuilder.\n");
  129. AZ_TracePrintf("Help", "%s - Print help information.\n", s_paramHelp);
  130. AZ_TracePrintf("Help", "%s - Task to run.\n", s_paramTask);
  131. AZ_TracePrintf("Help", "%s - Name of the current project.\n", s_paramProjectName);
  132. AZ_TracePrintf("Help", "%s - Full path to the project cache folder.\n", s_paramProjectCacheRoot);
  133. AZ_TracePrintf("Help", "%s - For resident mode, the path to the builder dll folder, otherwise the full path to a single builder dll to use.\n", s_paramModule);
  134. AZ_TracePrintf("Help", "%s - Optional, port number to use to connect to the AP.\n", s_paramPort);
  135. AZ_TracePrintf("Help", "%s - UUID string that identifies the builder. Only used for resident mode when the AP directly starts up the AssetBuilder.\n", s_paramId);
  136. AZ_TracePrintf("Help", "%s - For non-resident mode, full path to the file containing the serialized job request.\n", s_paramInput);
  137. AZ_TracePrintf("Help", "%s - For non-resident mode, full path to the file to write the job response to.\n", s_paramOutput);
  138. AZ_TracePrintf("Help", "%s - Debug mode for the create and process job of the specified file.\n", s_paramDebug);
  139. AZ_TracePrintf("Help", " Debug mode optionally uses -%s, -%s, -%s, -%s and -gameroot.\n", s_paramInput, s_paramOutput, s_paramModule, s_paramPort);
  140. AZ_TracePrintf("Help", " Example: -%s Objects\\Tutorials\\shapes.fbx\n", s_paramDebug);
  141. AZ_TracePrintf("Help", "%s - Debug mode for the create job of the specified file.\n", s_paramDebugCreate);
  142. AZ_TracePrintf("Help", "%s - Debug mode for the process job of the specified file.\n", s_paramDebugProcess);
  143. AZ_TracePrintf("Help", "%s - Additional tags to add to the debug platform for job processing. One tag can be supplied per option\n", s_paramPlatformTags);
  144. AZ_TracePrintf("Help", "%s - Platform to use for debugging. ex: pc\n", s_paramPlatform);
  145. }
  146. bool AssetBuilderComponent::IsInDebugMode(const AzFramework::CommandLine& commandLine)
  147. {
  148. if (commandLine.HasSwitch(s_paramDebug) || commandLine.HasSwitch(s_paramDebugCreate) || commandLine.HasSwitch(s_paramDebugProcess))
  149. {
  150. return true;
  151. }
  152. if (commandLine.HasSwitch(s_paramTask))
  153. {
  154. const AZStd::string& task = commandLine.GetSwitchValue(s_paramTask, 0);
  155. if (task == s_taskDebug || task == s_taskDebugCreate || task == s_taskDebugProcess)
  156. {
  157. return true;
  158. }
  159. }
  160. return false;
  161. }
  162. void AssetBuilderComponent::Activate()
  163. {
  164. BuilderBus::Handler::BusConnect();
  165. AssetBuilderSDK::AssetBuilderBus::Handler::BusConnect();
  166. AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusConnect();
  167. // the asset builder app never writes source files, only assets, so there is no need to do any kind of asset upgrading
  168. AZ::Data::AssetManager::Instance().SetAssetInfoUpgradingEnabled(false);
  169. }
  170. void AssetBuilderComponent::Deactivate()
  171. {
  172. BuilderBus::Handler::BusDisconnect();
  173. AssetBuilderSDK::AssetBuilderBus::Handler::BusDisconnect();
  174. AzFramework::EngineConnectionEvents::Bus::Handler::BusDisconnect();
  175. AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect();
  176. }
  177. void AssetBuilderComponent::Reflect(AZ::ReflectContext* context)
  178. {
  179. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  180. {
  181. serializeContext->Class<AssetBuilderComponent, AZ::Component>()
  182. ->Version(1);
  183. }
  184. }
  185. bool AssetBuilderComponent::Run()
  186. {
  187. AZ_TracePrintf("AssetBuilderComponent", "Run: Parsing command line.\n");
  188. const AzFramework::CommandLine* commandLine = nullptr;
  189. AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
  190. if (commandLine->HasSwitch(s_paramHelp))
  191. {
  192. PrintHelp();
  193. UnloadBuilders();
  194. return true;
  195. }
  196. AZStd::string task;
  197. AZStd::string debugFile;
  198. if (GetParameter(s_paramDebug, debugFile, false))
  199. {
  200. task = s_taskDebug;
  201. }
  202. else if (GetParameter(s_paramDebugCreate, debugFile, false))
  203. {
  204. task = s_taskDebugCreate;
  205. }
  206. else if (GetParameter(s_paramDebugProcess, debugFile, false))
  207. {
  208. task = s_taskDebugProcess;
  209. }
  210. else if (!GetParameter(s_paramTask, task))
  211. {
  212. AZ_Error("AssetBuilder", false, "No task specified. Use -help for options.");
  213. UnloadBuilders();
  214. return false;
  215. }
  216. bool isDebugTask = (task == s_taskDebug || task == s_taskDebugCreate || task == s_taskDebugProcess);
  217. if (!GetParameter(s_paramProjectName, m_gameName, !isDebugTask))
  218. {
  219. m_gameName = AZ::Utils::GetProjectName();
  220. }
  221. if (!GetParameter(s_paramProjectCacheRoot, m_gameCache, !isDebugTask))
  222. {
  223. if (!isDebugTask)
  224. {
  225. UnloadBuilders();
  226. return false;
  227. }
  228. }
  229. AZ_TracePrintf("AssetBuilderComponent", "Run: Connecting back to Asset Processor...\n");
  230. bool connectedToAssetProcessor = ConnectToAssetProcessor();
  231. //AP connection is required to access the asset catalog
  232. AZ_Error("AssetBuilder", connectedToAssetProcessor, "Failed to establish a network connection to the AssetProcessor. Use -help for options.");;
  233. IBuilderApplication* builderApplication = AZ::Interface<IBuilderApplication>::Get();
  234. if(!builderApplication)
  235. {
  236. AZ_Error("AssetBuilder", false, "Failed to retreive IBuilderApplication interface");
  237. return false;
  238. }
  239. builderApplication->InitializeBuilderComponents();
  240. bool result = false;
  241. if (connectedToAssetProcessor)
  242. {
  243. if (task == s_taskResident)
  244. {
  245. result = RunInResidentMode();
  246. }
  247. else if (task == s_taskDebug)
  248. {
  249. static const bool runCreateJobs = true;
  250. static const bool runProcessJob = true;
  251. result = RunDebugTask(AZStd::move(debugFile), runCreateJobs, runProcessJob);
  252. }
  253. else if (task == s_taskDebugCreate)
  254. {
  255. static const bool runCreateJobs = true;
  256. static const bool runProcessJob = false;
  257. result = RunDebugTask(AZStd::move(debugFile), runCreateJobs, runProcessJob);
  258. }
  259. else if (task == s_taskDebugProcess)
  260. {
  261. static const bool runCreateJobs = false;
  262. static const bool runProcessJob = true;
  263. result = RunDebugTask(AZStd::move(debugFile), runCreateJobs, runProcessJob);
  264. }
  265. else
  266. {
  267. result = RunOneShotTask(task);
  268. }
  269. }
  270. // note that we destroy (unload) the builder dlls soon after this (see UnloadBuilders() below),
  271. // so we must tick here before that occurs.
  272. // ticking here causes assets that have a 0 refcount (and are thus in the destroy list) to actually be destroyed.
  273. AZ::SystemTickBus::Broadcast(&AZ::SystemTickBus::Events::OnSystemTick);
  274. AZ_Error("AssetBuilder", result, "Failed to handle `%s` request", task.c_str());
  275. AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor);
  276. UnloadBuilders();
  277. return result;
  278. }
  279. bool AssetBuilderComponent::ConnectToAssetProcessor()
  280. {
  281. //get the asset processor connection params from the bootstrap
  282. AzFramework::AssetSystem::ConnectionSettings connectionSettings;
  283. bool succeeded = AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings);
  284. if (!succeeded)
  285. {
  286. AZ_Error("Asset Builder", false, "Getting bootstrap params failed");
  287. return false;
  288. }
  289. //override bootstrap params
  290. //the asset builder may have been given an optional ip to use
  291. AZStd::string overrideIp;
  292. if (GetParameter(s_paramIp, overrideIp, false))
  293. {
  294. connectionSettings.m_assetProcessorIp = overrideIp;
  295. }
  296. //the asset builder may have been given an optional port to use
  297. AZStd::string overridePort;
  298. if (GetParameter(s_paramPort, overridePort, false))
  299. {
  300. connectionSettings.m_assetProcessorPort = static_cast<AZ::u16>(AZStd::stoi(overridePort));
  301. }
  302. //the asset builder may have been given an optional asset platform to use
  303. AZStd::string overrideAssetPlatform;
  304. if (GetParameter(s_paramPlatform, overrideAssetPlatform, false))
  305. {
  306. connectionSettings.m_assetPlatform = overrideAssetPlatform;
  307. }
  308. //the asset builder may have been given an optional project name to use
  309. AZStd::string overrideProjectName;
  310. if (GetParameter(s_paramProjectName, overrideProjectName, false))
  311. {
  312. connectionSettings.m_projectName = overrideProjectName;
  313. }
  314. connectionSettings.m_connectionIdentifier = "Asset Builder";
  315. connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
  316. connectionSettings.m_launchAssetProcessorOnFailedConnection = false; // builders shouldn't launch the AssetProcessor
  317. connectionSettings.m_waitUntilAssetProcessorIsReady = false; // builders are what make the AssetProcessor ready, so the cannot wait until the AssetProcessor is ready
  318. connectionSettings.m_waitForConnect = true; // application is a builder so it needs to wait for a connection
  319. //connect to Asset Processor.
  320. bool connectedToAssetProcessor = false;
  321. AzFramework::AssetSystemRequestBus::BroadcastResult(connectedToAssetProcessor,
  322. &AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
  323. return connectedToAssetProcessor;
  324. }
  325. //////////////////////////////////////////////////////////////////////////
  326. bool AssetBuilderComponent::RunInResidentMode()
  327. {
  328. using namespace AssetBuilderSDK;
  329. using namespace AZStd::placeholders;
  330. AZ_TracePrintf("AssetBuilderComponent", "RunInResidentMode: Starting resident mode (waiting for commands to arrive)\n");
  331. AZStd::string port, id, builderFolder;
  332. if (!GetParameter(s_paramId, id)
  333. || !GetParameter(s_paramModule, builderFolder))
  334. {
  335. return false;
  336. }
  337. if (!LoadBuilders(builderFolder))
  338. {
  339. return false;
  340. }
  341. AzFramework::SocketConnection::GetInstance()->AddMessageHandler(CreateJobsNetRequest::MessageType(), AZStd::bind(&AssetBuilderComponent::CreateJobsResidentHandler, this, _1, _2, _3, _4));
  342. AzFramework::SocketConnection::GetInstance()->AddMessageHandler(ProcessJobNetRequest::MessageType(), AZStd::bind(&AssetBuilderComponent::ProcessJobResidentHandler, this, _1, _2, _3, _4));
  343. BuilderHelloRequest request;
  344. BuilderHelloResponse response;
  345. request.m_uuid = AZ::Uuid::CreateString(id.c_str());
  346. AZ_TracePrintf("AssetBuilderComponent", "RunInResidentMode: Pinging asset processor with the builder UUID %s\n", request.m_uuid.ToString<AZStd::string>().c_str());
  347. bool result = AzFramework::AssetSystem::SendRequest(request, response);
  348. AZ_Error("AssetBuilder", result, "Failed to send hello request to Asset Processor");
  349. // This error is only shown if we successfully got a response AND the response explicitly indicates the AP rejected the builder
  350. AZ_Error("AssetBuilder", !result || response.m_accepted, "Asset Processor rejected connection request");
  351. if (result && response.m_accepted)
  352. {
  353. m_running = true;
  354. m_jobThreadDesc.m_name = "Builder Job Thread";
  355. m_jobThread = AZStd::thread(m_jobThreadDesc, AZStd::bind(&AssetBuilderComponent::JobThread, this));
  356. AzFramework::EngineConnectionEvents::Bus::Handler::BusConnect(); // Listen for disconnects
  357. AZ_TracePrintf("AssetBuilder", "Builder ID: %s\n", response.m_uuid.ToString<AZStd::string>().c_str());
  358. AZ_TracePrintf("AssetBuilder", "Resident mode ready\n");
  359. m_mainEvent.acquire();
  360. AZ_TracePrintf("AssetBuilder", "Shutting down\n");
  361. m_running = false;
  362. }
  363. if (m_jobThread.joinable())
  364. {
  365. m_jobEvent.release();
  366. m_jobThread.join();
  367. }
  368. return result;
  369. }
  370. bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCreateJobs, bool runProcessJob)
  371. {
  372. AZ_TracePrintf("AssetBuilderComponent", "RunDebugTask - running debug task on file : %s\n", debugFile.c_str());
  373. AZ_TracePrintf("AssetBuilderComponent", "RunDebugTask - CreateJobs: %s\n", runCreateJobs ? "True" : "False");
  374. AZ_TracePrintf("AssetBuilderComponent", "RunDebugTask - ProcessJob: %s\n", runProcessJob ? "True" : "False");
  375. if (debugFile.empty())
  376. {
  377. if (!GetParameter(s_paramInput, debugFile))
  378. {
  379. AZ_Error("AssetBuilder", false, "No input file was specified. Use -help for options.");
  380. return false;
  381. }
  382. }
  383. AZ::StringFunc::Path::Normalize(debugFile);
  384. if (!GetParameter(s_paramProjectCacheRoot, m_gameCache, false))
  385. {
  386. if (m_gameCache.empty())
  387. {
  388. // Query the project cache root path from the Settings Registry
  389. auto settingsRegistry = AZ::SettingsRegistry::Get();
  390. if (!settingsRegistry || !settingsRegistry->Get(m_gameCache, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder))
  391. {
  392. m_gameCache = ".";
  393. }
  394. }
  395. }
  396. bool result = false;
  397. AZ::Data::AssetInfo info;
  398. AZStd::string watchFolder;
  399. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, debugFile.c_str(), info, watchFolder);
  400. if (!result)
  401. {
  402. AZ_Error("AssetBuilder", false, "Failed to locate asset info for '%s'.", debugFile.c_str());
  403. return false;
  404. }
  405. AZStd::string binDir;
  406. AZStd::string module;
  407. if (GetParameter(s_paramModule, module, false))
  408. {
  409. AZ::StringFunc::Path::GetFullPath(module.c_str(), binDir);
  410. if (!LoadBuilder(module))
  411. {
  412. AZ_Error("AssetBuilder", false, "Failed to load module '%s'.", module.c_str());
  413. return false;
  414. }
  415. }
  416. else
  417. {
  418. const char* executableFolder = nullptr;
  419. AZ::ComponentApplicationBus::BroadcastResult(executableFolder, &AZ::ComponentApplicationBus::Events::GetExecutableFolder);
  420. if (!executableFolder)
  421. {
  422. AZ_Error("AssetBuilder", false, "Unable to determine application root.");
  423. return false;
  424. }
  425. AZ::StringFunc::Path::Join(executableFolder, "Builders", binDir);
  426. if (!LoadBuilders(binDir))
  427. {
  428. AZ_Error("AssetBuilder", false, "Failed to load one or more builders from '%s'.", binDir.c_str());
  429. return false;
  430. }
  431. }
  432. AZStd::string baseTempDirPath;
  433. if (!GetParameter(s_paramOutput, baseTempDirPath, false))
  434. {
  435. AZStd::string fileName;
  436. AZ::StringFunc::Path::GetFullFileName(debugFile.c_str(), fileName);
  437. AZStd::replace(fileName.begin(), fileName.end(), '.', '_');
  438. AZ::StringFunc::Path::Join(binDir.c_str(), "Debug", baseTempDirPath);
  439. AZ::StringFunc::Path::Join(baseTempDirPath.c_str(), fileName.c_str(), baseTempDirPath);
  440. }
  441. // Default tags for the debug task are "tools" and "debug"
  442. // Additional tags are parsed from command line parameters
  443. AZStd::unordered_set<AZStd::string> platformTags{"tools", "debug"};
  444. {
  445. const AzFramework::CommandLine* commandLine = nullptr;
  446. AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
  447. if (commandLine)
  448. {
  449. size_t tagSwitchSize = commandLine->GetNumSwitchValues(s_paramPlatformTags);
  450. for (int tagIndex = 0; tagIndex < tagSwitchSize; ++tagIndex)
  451. {
  452. platformTags.emplace(commandLine->GetSwitchValue(s_paramPlatformTags, tagIndex));
  453. }
  454. }
  455. }
  456. AZStd::string platform;
  457. if(!GetParameter(s_paramPlatform, platform, false))
  458. {
  459. platform = "debug platform";
  460. }
  461. auto* fileIO = AZ::IO::FileIOBase::GetInstance();
  462. for (auto& it : m_assetBuilderDescMap)
  463. {
  464. AZStd::unique_ptr<AssetBuilderSDK::AssetBuilderDesc>& builder = it.second;
  465. AZ_Assert(builder, "Invalid description for builder registered.");
  466. if (!IsBuilderForFile(info.m_relativePath, *builder))
  467. {
  468. AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Skipping '%s'.\n", builder->m_name.c_str());
  469. continue;
  470. }
  471. AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Debugging builder '%s'.\n", builder->m_name.c_str());
  472. AZStd::string tempDirPath;
  473. AZ::StringFunc::Path::Join(baseTempDirPath.c_str(), builder->m_name.c_str(), tempDirPath);
  474. AZStd::vector<AssetBuilderSDK::PlatformInfo> enabledDebugPlatformInfos =
  475. {
  476. {
  477. platform.c_str(), platformTags
  478. }
  479. };
  480. AZStd::vector<AssetBuilderSDK::JobDescriptor> jobDescriptions;
  481. if (runCreateJobs)
  482. {
  483. AZStd::string createJobsTempDirPath;
  484. AZ::StringFunc::Path::Join(tempDirPath.c_str(), "CreateJobs", createJobsTempDirPath);
  485. AZ::IO::Result fileResult = fileIO->CreatePath(createJobsTempDirPath.c_str());
  486. if (!fileResult)
  487. {
  488. AZ_Error("AssetBuilder", false, "Unable to create or clear debug folder '%s'.", createJobsTempDirPath.c_str());
  489. return false;
  490. }
  491. AssetBuilderSDK::CreateJobsRequest createRequest(builder->m_busId, info.m_relativePath, watchFolder,
  492. enabledDebugPlatformInfos, info.m_assetId.m_guid);
  493. AZ_TraceContext("Source", debugFile);
  494. AZ_TraceContext("Platforms", AssetBuilderSDK::PlatformInfo::PlatformVectorAsString(createRequest.m_enabledPlatforms));
  495. AssetBuilderSDK::CreateJobsResponse createResponse;
  496. builder->m_createJobFunction(createRequest, createResponse);
  497. AZStd::string responseFile;
  498. AZ::StringFunc::Path::Join(createJobsTempDirPath.c_str(), "CreateJobsResponse.xml", responseFile);
  499. if (!AZ::Utils::SaveObjectToFile(responseFile, AZ::DataStream::ST_XML, &createResponse))
  500. {
  501. AZ_Error("AssetBuilder", false, "Failed to serialize response to file: %s", responseFile.c_str());
  502. return false;
  503. }
  504. if (runProcessJob)
  505. {
  506. jobDescriptions = AZStd::move(createResponse.m_createJobOutputs);
  507. }
  508. }
  509. AZ::SystemTickBus::Broadcast(&AZ::SystemTickBus::Events::OnSystemTick); // flush assets in case any are present with 0 refcount.
  510. if (runProcessJob)
  511. {
  512. AZStd::string processJobTempDirPath;
  513. AZ::StringFunc::Path::Join(tempDirPath.c_str(), "ProcessJobs", processJobTempDirPath);
  514. AZ::IO::Result fileResult = fileIO->CreatePath(processJobTempDirPath.c_str());
  515. if (!fileResult)
  516. {
  517. AZ_Error("AssetBuilder", false, "Unable to create debug or clear folder '%s'.", processJobTempDirPath.c_str());
  518. return false;
  519. }
  520. AssetBuilderSDK::PlatformInfo enabledDebugPlatformInfo = {
  521. platform.c_str(), { "tools", "debug" }
  522. };
  523. AssetBuilderSDK::ProcessJobRequest processRequest;
  524. processRequest.m_watchFolder = watchFolder;
  525. processRequest.m_sourceFile = info.m_relativePath;
  526. processRequest.m_platformInfo = enabledDebugPlatformInfo;
  527. processRequest.m_sourceFileUUID = info.m_assetId.m_guid;
  528. AZ::StringFunc::AssetDatabasePath::Join(processRequest.m_watchFolder.c_str(), processRequest.m_sourceFile.c_str(), processRequest.m_fullPath);
  529. processRequest.m_tempDirPath = processJobTempDirPath;
  530. processRequest.m_jobId = 0;
  531. processRequest.m_builderGuid = builder->m_busId;
  532. processRequest.m_platformInfo.m_tags = platformTags;
  533. AZ_TraceContext("Source", debugFile);
  534. if (jobDescriptions.empty())
  535. {
  536. for (const auto& platformInfo : enabledDebugPlatformInfos)
  537. {
  538. AssetBuilderSDK::JobDescriptor placeholder;
  539. placeholder.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
  540. placeholder.m_jobKey = AZStd::string::format("%s_DEBUG", builder->m_name.c_str());
  541. placeholder.m_jobParameters[AZ_CRC("Debug", 0x6ca547a7)] = "true";
  542. jobDescriptions.emplace_back(AZStd::move(placeholder));
  543. }
  544. }
  545. for (size_t i = 0; i < jobDescriptions.size(); ++i)
  546. {
  547. AssetBuilderSDK::AssetBuilderTraceBus::Broadcast(&AssetBuilderSDK::AssetBuilderTraceBus::Events::ResetErrorCount);
  548. AssetBuilderSDK::AssetBuilderTraceBus::Broadcast(&AssetBuilderSDK::AssetBuilderTraceBus::Events::ResetWarningCount);
  549. processRequest.m_jobDescription = jobDescriptions[i];
  550. AssetBuilderSDK::ProcessJobResponse processResponse;
  551. ProcessJob(builder->m_processJobFunction, processRequest, processResponse);
  552. AZStd::string responseFile;
  553. AZ::StringFunc::Path::Join(processJobTempDirPath.c_str(),
  554. AZStd::string::format("%zu_%s", i, AssetBuilderSDK::s_processJobResponseFileName).c_str(), responseFile);
  555. if (!AZ::Utils::SaveObjectToFile(responseFile, AZ::DataStream::ST_XML, &processResponse))
  556. {
  557. AZ_Error("AssetBuilder", false, "Failed to serialize response to file: %s", responseFile.c_str());
  558. return false;
  559. }
  560. }
  561. }
  562. }
  563. return true;
  564. }
  565. void AssetBuilderComponent::FlushFileStreamerCache()
  566. {
  567. // Force a file streamer flush to ensure that file handles don't remain used or locked between jobs.
  568. auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
  569. AZStd::binary_semaphore wait;
  570. AZ::IO::FileRequestPtr flushRequest = streamer->FlushCaches();
  571. streamer->SetRequestCompleteCallback(flushRequest, [&wait]([[maybe_unused]] AZ::IO::FileRequestHandle request)
  572. {
  573. wait.release();
  574. });
  575. streamer->QueueRequest(flushRequest);
  576. wait.acquire();
  577. }
  578. void AssetBuilderComponent::ProcessJob(const AssetBuilderSDK::ProcessJobFunction& job, const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& outResponse)
  579. {
  580. // Setup the alias' as appropriate to the job in question.
  581. auto ioBase = AZ::IO::FileIOBase::GetInstance();
  582. AZ_Assert(ioBase != nullptr, "AZ::IO::FileIOBase must be ready for use.");
  583. auto settingsRegistry = AZ::SettingsRegistry::Get();
  584. AZ_Assert(settingsRegistry != nullptr, "SettingsRegistry must be ready for use in the AssetBuilder.");
  585. // The root path is the cache plus the platform name.
  586. AZ::IO::FixedMaxPath newRoot(m_gameCache);
  587. // Check if the platform identifier is a valid "asset platform"
  588. // If so, use it, other wise use the OS default platform as a fail safe
  589. // This is to make sure the "debug platform" isn't added as a path segment
  590. // the Cache Root folder
  591. if (AzFramework::PlatformHelper::GetPlatformIdFromName(request.m_platformInfo.m_identifier) != AzFramework::PlatformId::Invalid)
  592. {
  593. newRoot /= request.m_platformInfo.m_identifier;
  594. }
  595. else
  596. {
  597. newRoot /= AzFramework::OSPlatformToDefaultAssetPlatform(AZ_TRAIT_OS_PLATFORM_CODENAME);
  598. }
  599. // The asset path is root and the lower case game name.
  600. AZ::IO::FixedMaxPath newAssets = newRoot;
  601. // Now set the paths and run the job.
  602. {
  603. // Save out the prior paths.
  604. ScopedAliasSetter assetAliasScope(*ioBase, "@assets@", newAssets.c_str());
  605. ScopedAliasSetter rootAliasScope(*ioBase, "@root@", newRoot.c_str());
  606. ScopedAliasSetter projectplatformCacheAliasScope(*ioBase, "@projectplatformcache@", newRoot.c_str());
  607. ScopedSettingsRegistrySetter cacheRootFolderScope(*settingsRegistry,
  608. AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder, newRoot.Native());
  609. // Invoke the Process Job function
  610. job(request, outResponse);
  611. }
  612. // The asset building ProcessJob method might read any number of source files while processing the asset.
  613. // Ensure that any exclusive file handle locks caused by this are cleared so that other AssetBuilder processes
  614. // running in parallel have the ability to read those files as well.
  615. // This needs to occur after the ProcessJob call, but before the file aliases get cleared.
  616. FlushFileStreamerCache();
  617. UpdateResultCode(request, outResponse);
  618. }
  619. bool AssetBuilderComponent::RunOneShotTask(const AZStd::string& task)
  620. {
  621. AZ_TracePrintf("AssetBuilderComponent", "RunOneShotTask - running one-shot task [%s]\n", task.c_str());
  622. // Load the requested module. This is not a required param for the task, since the builders can be in gems.
  623. AZStd::string modulePath;
  624. if (GetParameter(s_paramModule, modulePath) && !LoadBuilder(modulePath))
  625. {
  626. return false;
  627. }
  628. AZStd::string inputFilePath, outputFilePath;
  629. if (!GetParameter(s_paramInput, inputFilePath)
  630. || !GetParameter(s_paramOutput, outputFilePath))
  631. {
  632. return false;
  633. }
  634. AZ::StringFunc::Path::Normalize(inputFilePath);
  635. AZ::StringFunc::Path::Normalize(outputFilePath);
  636. if (task == s_taskRegisterBuilder)
  637. {
  638. return HandleRegisterBuilder(inputFilePath, outputFilePath);
  639. }
  640. else if (task == s_taskCreateJob)
  641. {
  642. auto func = [this](const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
  643. {
  644. AZ_TraceContext("Source", request.m_sourceFile);
  645. AZ_TraceContext("Platforms", AssetBuilderSDK::PlatformInfo::PlatformVectorAsString(request.m_enabledPlatforms));
  646. auto assetBuilderDescIt = m_assetBuilderDescMap.find(request.m_builderid);
  647. if (assetBuilderDescIt != m_assetBuilderDescMap.end())
  648. {
  649. assetBuilderDescIt->second->m_createJobFunction(request, response);
  650. }
  651. else
  652. {
  653. AZ_Error("AssetBuilder", false, "Builder UUID [%s] does not exist in the AssetBuilderDescMap for source file %s",
  654. request.m_builderid.ToString<AZStd::fixed_string<64>>().c_str(), request.m_sourceFile.c_str());
  655. }
  656. // The asset building CreateJob method might read any number of source files to gather a dependency list.
  657. // Ensure that any exclusive file handle locks caused by this are cleared so that other AssetBuilder processes
  658. // running in parallel have the ability to read those files as well.
  659. FlushFileStreamerCache();
  660. };
  661. return HandleTask<AssetBuilderSDK::CreateJobsRequest, AssetBuilderSDK::CreateJobsResponse>(inputFilePath, outputFilePath, func);
  662. }
  663. else if (task == s_taskProcessJob)
  664. {
  665. auto func = [this](const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
  666. {
  667. AZ_TraceContext("Source", request.m_fullPath);
  668. AZ_TraceContext("Platform", request.m_platformInfo.m_identifier);
  669. auto assetBuilderDescIt = m_assetBuilderDescMap.find(request.m_builderGuid);
  670. if (assetBuilderDescIt != m_assetBuilderDescMap.end())
  671. {
  672. ProcessJob(assetBuilderDescIt->second->m_processJobFunction, request, response);
  673. }
  674. else
  675. {
  676. AZ_Error("AssetBuilder", false, "Builder UUID [%s] does not exist in the AssetBuilderDescMap for source file %s",
  677. request.m_builderGuid.ToString<AZStd::fixed_string<64>>().c_str(), request.m_sourceFile.c_str());
  678. }
  679. };
  680. return HandleTask<AssetBuilderSDK::ProcessJobRequest, AssetBuilderSDK::ProcessJobResponse>(inputFilePath, outputFilePath, func);
  681. }
  682. else
  683. {
  684. AZ_Error("AssetBuilder", false, "Unknown task");
  685. return false;
  686. }
  687. }
  688. void AssetBuilderComponent::Disconnected(AzFramework::SocketConnection* /*connection*/)
  689. {
  690. // If we lose connection to the AP, print out an error and shut down.
  691. // This prevents builders from running indefinitely if the AP crashes
  692. AZ_Error("AssetBuilder", false, "Lost connection to Asset Processor, shutting down");
  693. m_mainEvent.release();
  694. }
  695. bool AssetBuilderComponent::GetAssetDatabaseLocation(AZStd::string& location)
  696. {
  697. AZ_Error("AssetBuilder", false,
  698. "Accessing the database directly from a builder is not supported. Many queries behave unexpectedly from builders as the Asset"
  699. "Processor continuously updates tables as well as risking dead locks. Please use the AssetSystemRequestBus or similar buses "
  700. "to safely query information from the database.");
  701. location = "<Unsupported>";
  702. return false;
  703. }
  704. template<typename TNetRequest, typename TNetResponse>
  705. void AssetBuilderComponent::ResidentJobHandler(AZ::u32 serial, const void* data, AZ::u32 dataLength, JobType jobType)
  706. {
  707. auto job = AZStd::make_unique<Job>();
  708. job->m_netResponse = AZStd::make_unique<TNetResponse>();
  709. job->m_requestSerial = serial;
  710. job->m_jobType = jobType;
  711. auto* request = AZ::Utils::LoadObjectFromBuffer<TNetRequest>(data, dataLength);
  712. if (!request)
  713. {
  714. AZ_Error("AssetBuilder", false, "Problem deserializing net request");
  715. AzFramework::AssetSystem::SendResponse(*(job->m_netResponse), serial);
  716. return;
  717. }
  718. job->m_netRequest = AZStd::unique_ptr<TNetRequest>(request);
  719. // Queue up the job for the worker thread
  720. {
  721. AZStd::lock_guard<AZStd::mutex> lock(m_jobMutex);
  722. if (!m_queuedJob)
  723. {
  724. m_queuedJob.swap(job);
  725. }
  726. else
  727. {
  728. AZ_Error("AssetBuilder", false, "Builder already has a job queued");
  729. AzFramework::AssetSystem::SendResponse(*(job->m_netResponse), serial);
  730. return;
  731. }
  732. }
  733. // Wake up the job thread
  734. m_jobEvent.release();
  735. }
  736. bool AssetBuilderComponent::IsBuilderForFile(const AZStd::string& filePath, const AssetBuilderSDK::AssetBuilderDesc& builderDescription) const
  737. {
  738. for (const AssetBuilderSDK::AssetBuilderPattern& pattern : builderDescription.m_patterns)
  739. {
  740. AssetBuilderSDK::FilePatternMatcher matcher(pattern);
  741. if (matcher.MatchesPath(filePath))
  742. {
  743. return true;
  744. }
  745. }
  746. return false;
  747. }
  748. void AssetBuilderComponent::JobThread()
  749. {
  750. while (m_running)
  751. {
  752. m_jobEvent.acquire();
  753. AZStd::unique_ptr<Job> job;
  754. {
  755. AZStd::lock_guard<AZStd::mutex> lock(m_jobMutex);
  756. job.swap(m_queuedJob);
  757. }
  758. if (!job)
  759. {
  760. if (m_running)
  761. {
  762. AZ_TracePrintf("AssetBuilder", "JobThread woke up, but there was no queued job\n");
  763. }
  764. continue;
  765. }
  766. AssetBuilderSDK::AssetBuilderTraceBus::Broadcast(&AssetBuilderSDK::AssetBuilderTraceBus::Events::ResetErrorCount);
  767. AssetBuilderSDK::AssetBuilderTraceBus::Broadcast(&AssetBuilderSDK::AssetBuilderTraceBus::Events::ResetWarningCount);
  768. switch (job->m_jobType)
  769. {
  770. case JobType::Create:
  771. {
  772. using namespace AssetBuilderSDK;
  773. auto* netRequest = azrtti_cast<CreateJobsNetRequest*>(job->m_netRequest.get());
  774. auto* netResponse = azrtti_cast<CreateJobsNetResponse*>(job->m_netResponse.get());
  775. AZ_Assert(netRequest && netResponse, "Request or response is null");
  776. AZ::IO::FixedMaxPath fullPath(netRequest->m_request.m_watchFolder);
  777. fullPath /= netRequest->m_request.m_sourceFile;
  778. AZ_TracePrintf("AssetBuilder", "Source = %s\n", fullPath.c_str());
  779. AZ_TracePrintf("AssetBuilder", "Platforms = %s\n", AssetBuilderSDK::PlatformInfo::PlatformVectorAsString(netRequest->m_request.m_enabledPlatforms).c_str());
  780. auto assetBuilderDescIt = m_assetBuilderDescMap.find(netRequest->m_request.m_builderid);
  781. if (assetBuilderDescIt != m_assetBuilderDescMap.end())
  782. {
  783. assetBuilderDescIt->second->m_createJobFunction(netRequest->m_request, netResponse->m_response);
  784. }
  785. else
  786. {
  787. AZ_Error("AssetBuilder", false, "Builder UUID [%s] does not exist in the AssetBuilderDescMap for source file %s",
  788. netRequest->m_request.m_builderid.ToString<AZStd::fixed_string<64>>().c_str(), netRequest->m_request.m_sourceFile.c_str());
  789. }
  790. break;
  791. }
  792. case JobType::Process:
  793. {
  794. using namespace AssetBuilderSDK;
  795. AZ_TracePrintf("AssetBuilder", "Running processJob task\n");
  796. auto* netRequest = azrtti_cast<ProcessJobNetRequest*>(job->m_netRequest.get());
  797. auto* netResponse = azrtti_cast<ProcessJobNetResponse*>(job->m_netResponse.get());
  798. AZ_Assert(netRequest && netResponse, "Request or response is null");
  799. AZ_TracePrintf("AssetBuilder", "Source = %s\n", netRequest->m_request.m_fullPath.c_str());
  800. AZ_TracePrintf("AssetBuilder", "Platform = %s\n", netRequest->m_request.m_jobDescription.GetPlatformIdentifier().c_str());
  801. auto assetBuilderDescIt = m_assetBuilderDescMap.find(netRequest->m_request.m_builderGuid);
  802. if (assetBuilderDescIt != m_assetBuilderDescMap.end())
  803. {
  804. auto* toolsCatalog = AZ::Interface<AssetProcessor::IToolsAssetCatalog>::Get();
  805. if (toolsCatalog)
  806. {
  807. toolsCatalog->SetActivePlatform(netRequest->m_request.m_jobDescription.GetPlatformIdentifier());
  808. }
  809. else
  810. {
  811. AZ_Warning("AssetBuilder", false, "Failed to retrieve IToolsAssetCatalog interface, cannot set current platform");
  812. }
  813. ProcessJob(assetBuilderDescIt->second->m_processJobFunction, netRequest->m_request, netResponse->m_response);
  814. }
  815. else
  816. {
  817. AZ_Error("AssetBuilder", false, "Builder UUID [%s] does not exist in the AssetBuilderDescMap for source file %s",
  818. netRequest->m_request.m_builderGuid.ToString<AZStd::fixed_string<64>>().c_str(), netRequest->m_request.m_sourceFile.c_str());
  819. }
  820. break;
  821. }
  822. default:
  823. AZ_Error("AssetBuilder", false, "Unhandled job request type");
  824. continue;
  825. }
  826. AZ::u32 warningCount, errorCount;
  827. AssetBuilderSDK::AssetBuilderTraceBus::BroadcastResult(warningCount, &AssetBuilderSDK::AssetBuilderTraceBus::Events::GetWarningCount);
  828. AssetBuilderSDK::AssetBuilderTraceBus::BroadcastResult(errorCount, &AssetBuilderSDK::AssetBuilderTraceBus::Events::GetErrorCount);
  829. AZ_TracePrintf("S", "%d errors, %d warnings\n", errorCount, warningCount);
  830. //Flush our output so the AP can properly associate all output with the current job
  831. std::fflush(stdout);
  832. std::fflush(stderr);
  833. AZ::SystemTickBus::Broadcast(&AZ::SystemTickBus::Events::OnSystemTick);
  834. AZ::TickBus::Broadcast(&AZ::TickEvents::OnTick, 0.00f, AZ::ScriptTimePoint(AZStd::chrono::system_clock::now()));
  835. AZ::AllocatorManager::Instance().GarbageCollect();
  836. AzFramework::AssetSystem::SendResponse(*(job->m_netResponse), job->m_requestSerial);
  837. }
  838. }
  839. void AssetBuilderComponent::CreateJobsResidentHandler(AZ::u32 /*typeId*/, AZ::u32 serial, const void* data, AZ::u32 dataLength)
  840. {
  841. using namespace AssetBuilderSDK;
  842. ResidentJobHandler<CreateJobsNetRequest, CreateJobsNetResponse>(serial, data, dataLength, JobType::Create);
  843. }
  844. void AssetBuilderComponent::ProcessJobResidentHandler(AZ::u32 /*typeId*/, AZ::u32 serial, const void* data, AZ::u32 dataLength)
  845. {
  846. using namespace AssetBuilderSDK;
  847. ResidentJobHandler<ProcessJobNetRequest, ProcessJobNetResponse>(serial, data, dataLength, JobType::Process);
  848. }
  849. //////////////////////////////////////////////////////////////////////////
  850. template<typename TRequest, typename TResponse>
  851. bool AssetBuilderComponent::HandleTask(const AZStd::string& inputFilePath, const AZStd::string& outputFilePath, const AZStd::function<void(const TRequest& request, TResponse& response)>& assetBuilderFunc)
  852. {
  853. TRequest request;
  854. TResponse response;
  855. if (!AZ::Utils::LoadObjectFromFileInPlace(inputFilePath, request))
  856. {
  857. AZ_Error("AssetBuilder", false, "Failed to deserialize request from file: %s", inputFilePath.c_str());
  858. return false;
  859. }
  860. assetBuilderFunc(request, response);
  861. if (!AZ::Utils::SaveObjectToFile(outputFilePath, AZ::DataStream::ST_XML, &response))
  862. {
  863. AZ_Error("AssetBuilder", false, "Failed to serialize response to file: %s", outputFilePath.c_str());
  864. return false;
  865. }
  866. return true;
  867. }
  868. bool AssetBuilderComponent::HandleRegisterBuilder(const AZStd::string& /*inputFilePath*/, const AZStd::string& outputFilePath) const
  869. {
  870. AssetBuilderSDK::RegisterBuilderResponse response;
  871. for (const auto& pair : m_assetBuilderDescMap)
  872. {
  873. response.m_assetBuilderDescList.push_back(*pair.second);
  874. }
  875. return AZ::Utils::SaveObjectToFile(outputFilePath, AZ::DataStream::ST_XML, &response);
  876. }
  877. void AssetBuilderComponent::UpdateResultCode(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
  878. {
  879. if (request.m_jobDescription.m_failOnError)
  880. {
  881. AZ::u32 errorCount = 0;
  882. AssetBuilderSDK::AssetBuilderTraceBus::BroadcastResult(errorCount, &AssetBuilderSDK::AssetBuilderTraceBus::Events::GetErrorCount);
  883. if (errorCount > 0 && response.m_resultCode == AssetBuilderSDK::ProcessJobResult_Success)
  884. {
  885. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  886. }
  887. }
  888. }
  889. bool AssetBuilderComponent::GetParameter(const char* paramName, AZStd::string& outValue, [[maybe_unused]] bool required /*= true*/) const
  890. {
  891. const AzFramework::CommandLine* commandLine = nullptr;
  892. AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
  893. size_t optionCount = commandLine->GetNumSwitchValues(paramName);
  894. if (optionCount > 0)
  895. {
  896. outValue = commandLine->GetSwitchValue(paramName, optionCount - 1);
  897. }
  898. if (outValue.empty())
  899. {
  900. AZ_Error("AssetBuilder", !required, "Missing required parameter `%s`. Use -help for options.", paramName);
  901. return false;
  902. }
  903. return true;
  904. }
  905. const char* AssetBuilderComponent::GetLibraryExtension()
  906. {
  907. return "*" AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
  908. }
  909. bool AssetBuilderComponent::LoadBuilders([[maybe_unused]] const AZStd::string& builderFolder)
  910. {
  911. // LoadBuilders by folder has been removed. Builders should all live within gems
  912. AZ_TracePrintf("AssetBuilderComponent", "LoadBuilders - Called LoadBuilders for [%s] - SKIPPING\n", builderFolder.c_str());
  913. return true;
  914. }
  915. bool AssetBuilderComponent::LoadBuilder(const AZStd::string& filePath)
  916. {
  917. using AssetBuilder::AssetBuilderType;
  918. auto assetBuilderInfo = AZStd::make_unique<AssetBuilder::ExternalModuleAssetBuilderInfo>(QString::fromUtf8(filePath.c_str()));
  919. if (assetBuilderInfo->GetAssetBuilderType() == AssetBuilderType::Valid)
  920. {
  921. if (!assetBuilderInfo->IsLoaded())
  922. {
  923. AZ_Warning("AssetBuilder", false, "AssetBuilder was not able to load the library: %s\n", filePath.c_str());
  924. return false;
  925. }
  926. }
  927. AssetBuilderType builderType = assetBuilderInfo->GetAssetBuilderType();
  928. if (builderType == AssetBuilderType::Valid)
  929. {
  930. AZ_TracePrintf("AssetBuilder", "LoadBuilder - Initializing and registering builder [%s]\n", assetBuilderInfo->GetName().toUtf8().constData());
  931. m_currentAssetBuilder = assetBuilderInfo.get();
  932. m_currentAssetBuilder->Initialize();
  933. m_currentAssetBuilder = nullptr;
  934. m_assetBuilderInfoList.push_back(AZStd::move(assetBuilderInfo));
  935. return true;
  936. }
  937. if (builderType == AssetBuilderType::Invalid)
  938. {
  939. return false;
  940. }
  941. return true;
  942. }
  943. void AssetBuilderComponent::UnloadBuilders()
  944. {
  945. m_assetBuilderDescMap.clear();
  946. for (auto& assetBuilderInfo : m_assetBuilderInfoList)
  947. {
  948. AZ_TracePrintf("AssetBuilderComponent", "UnloadBuilders - unloading builder [%s]\n", assetBuilderInfo->GetName().toUtf8().constData());
  949. assetBuilderInfo->UnInitialize();
  950. }
  951. m_assetBuilderInfoList.clear();
  952. }
  953. bool AssetBuilderComponent::FindBuilderInformation(const AZ::Uuid& builderGuid, AssetBuilderSDK::AssetBuilderDesc& descriptionOut)
  954. {
  955. auto iter = m_assetBuilderDescMap.find(builderGuid);
  956. if (iter != m_assetBuilderDescMap.end())
  957. {
  958. descriptionOut = *iter->second;
  959. return true;
  960. }
  961. else
  962. {
  963. return false;
  964. }
  965. }
  966. void AssetBuilderComponent::RegisterBuilderInformation(const AssetBuilderSDK::AssetBuilderDesc& builderDesc)
  967. {
  968. m_assetBuilderDescMap.insert({ builderDesc.m_busId, AZStd::make_unique<AssetBuilderSDK::AssetBuilderDesc>(builderDesc) });
  969. if (m_currentAssetBuilder)
  970. {
  971. m_currentAssetBuilder->RegisterBuilderDesc(builderDesc.m_busId);
  972. }
  973. }
  974. void AssetBuilderComponent::RegisterComponentDescriptor(AZ::ComponentDescriptor* descriptor)
  975. {
  976. if (m_currentAssetBuilder)
  977. {
  978. m_currentAssetBuilder->RegisterComponentDesc(descriptor);
  979. }
  980. }