Launcher.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  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 <Launcher.h>
  9. #include <AzCore/Casting/numeric_cast.h>
  10. #include <AzCore/Component/ComponentApplicationLifecycle.h>
  11. #include <AzCore/Console/IConsole.h>
  12. #include <AzCore/Debug/BudgetTracker.h>
  13. #include <AzCore/Debug/Trace.h>
  14. #include <AzCore/Interface/Interface.h>
  15. #include <AzCore/IO/Path/Path.h>
  16. #include <AzCore/IO/SystemFile.h>
  17. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  18. #include <AzCore/std/smart_ptr/make_shared.h>
  19. #include <AzCore/StringFunc/StringFunc.h>
  20. #include <AzCore/Utils/Utils.h>
  21. #include <AzFramework/Asset/AssetSystemBus.h>
  22. #include <AzFramework/IO/RemoteStorageDrive.h>
  23. #include <AzFramework/Windowing/NativeWindow.h>
  24. #include <AzFramework/Windowing/WindowBus.h>
  25. #include <AzGameFramework/Application/GameApplication.h>
  26. #include <ISystem.h>
  27. #include <Launcher_Traits_Platform.h>
  28. #if defined(AZ_MONOLITHIC_BUILD)
  29. extern "C" void CreateStaticModules(AZStd::vector<AZ::Module*>& modulesOut);
  30. #endif // defined(AZ_MONOLITHIC_BUILD)
  31. // Add the "REMOTE_ASSET_PROCESSOR" define except in release
  32. // this makes it so that asset processor functions. Without this, all assets must be present and on local media
  33. // with this, the asset processor can be used to remotely process assets.
  34. #if !defined(_RELEASE)
  35. # define REMOTE_ASSET_PROCESSOR
  36. #endif
  37. void CVar_OnViewportPosition(const AZ::Vector2& value);
  38. namespace
  39. {
  40. void CVar_OnViewportResize(const AZ::Vector2& value);
  41. AZ_CVAR(AZ::Vector2, r_viewportSize, AZ::Vector2::CreateZero(), CVar_OnViewportResize, AZ::ConsoleFunctorFlags::DontReplicate,
  42. "The default size for the launcher viewport, 0 0 means full screen");
  43. void CVar_OnViewportResize(const AZ::Vector2& value)
  44. {
  45. AzFramework::NativeWindowHandle windowHandle = nullptr;
  46. AzFramework::WindowSystemRequestBus::BroadcastResult(windowHandle, &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
  47. AzFramework::WindowSize newSize = AzFramework::WindowSize(aznumeric_cast<int32_t>(value.GetX()), aznumeric_cast<int32_t>(value.GetY()));
  48. AzFramework::WindowRequestBus::Broadcast(&AzFramework::WindowRequestBus::Events::ResizeClientArea, newSize, AzFramework::WindowPosOptions());
  49. }
  50. AZ_CVAR(AZ::Vector2, r_viewportPos, AZ::Vector2::CreateZero(), CVar_OnViewportPosition, AZ::ConsoleFunctorFlags::DontReplicate,
  51. "The default position for the launcher viewport, 0 0 means top left corner of your main desktop");
  52. void ExecuteConsoleCommandFile(AzFramework::Application& application)
  53. {
  54. const AZStd::string_view customConCmdKey = "console-command-file";
  55. const AZ::CommandLine* commandLine = application.GetCommandLine();
  56. AZStd::size_t numSwitchValues = commandLine->GetNumSwitchValues(customConCmdKey);
  57. if (numSwitchValues > 0)
  58. {
  59. // The expectations for command line parameters is that the "last one wins"
  60. // That way it allows users and test scripts to override previous command line options by just listing them later on the invocation line
  61. const AZStd::string& consoleCmd = commandLine->GetSwitchValue(customConCmdKey, numSwitchValues - 1);
  62. if (!consoleCmd.empty())
  63. {
  64. AZ::Interface<AZ::IConsole>::Get()->ExecuteConfigFile(consoleCmd.c_str());
  65. }
  66. }
  67. }
  68. void RunMainLoop(AzGameFramework::GameApplication& gameApplication)
  69. {
  70. // Ideally we'd just call GameApplication::RunMainLoop instead, but
  71. // we'd have to stop calling ISystem::UpdatePreTickBus / PostTickBus
  72. // directly, and instead have something subscribe to the TickBus in
  73. // order to call them, using order ComponentTickBus::TICK_FIRST - 1
  74. // and ComponentTickBus::TICK_LAST + 1 to ensure they get called at
  75. // the same time as they do now. Also, we'd need to pass a function
  76. // pointer to AzGameFramework::GameApplication::MainLoop that would
  77. // be used to call ITimer::GetFrameTime (unless we could also shift
  78. // our frame time to be managed by AzGameFramework::GameApplication
  79. // instead, which probably isn't going to happen anytime soon given
  80. // how many things depend on the ITimer interface).
  81. ISystem* system = gEnv ? gEnv->pSystem : nullptr;
  82. while (!gameApplication.WasExitMainLoopRequested())
  83. {
  84. // Pump the system event loop
  85. gameApplication.PumpSystemEventLoopUntilEmpty();
  86. if (gameApplication.WasExitMainLoopRequested())
  87. {
  88. break;
  89. }
  90. // Update the AzFramework system tick bus
  91. gameApplication.TickSystem();
  92. // Pre-update CrySystem
  93. if (system)
  94. {
  95. system->UpdatePreTickBus();
  96. }
  97. // Update the AzFramework application tick bus
  98. gameApplication.Tick();
  99. // Post-update CrySystem
  100. if (system)
  101. {
  102. system->UpdatePostTickBus();
  103. }
  104. }
  105. }
  106. }
  107. namespace O3DELauncher
  108. {
  109. inline constexpr AZStd::string_view LauncherTypeTag = "/O3DE/Runtime/LauncherType";
  110. inline constexpr AZStd::string_view LauncherFilenameTag = "launcher";
  111. AZ_CVAR(bool, bg_ConnectToAssetProcessor, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "If true, the process will launch and connect to the asset processor");
  112. bool PlatformMainInfo::CopyCommandLine(int argc, char** argv)
  113. {
  114. for (int argIndex = 0; argIndex < argc; ++argIndex)
  115. {
  116. if (!AddArgument(argv[argIndex]))
  117. {
  118. return false;
  119. }
  120. }
  121. return true;
  122. }
  123. bool PlatformMainInfo::AddArgument(const char* arg)
  124. {
  125. AZ_Error("Launcher", arg, "Attempting to add a nullptr command line argument!");
  126. bool needsQuote = (strstr(arg, " ") != nullptr);
  127. bool needsSpace = (m_commandLine[0] != 0);
  128. // strip the previous null-term from the count to prevent double counting
  129. m_commandLineLen = (m_commandLineLen == 0) ? 0 : (m_commandLineLen - 1);
  130. // compute the expected length with the added argument
  131. size_t argLen = strlen(arg);
  132. size_t pendingLen = m_commandLineLen + argLen + 1 + (needsSpace ? 1 : 0) + (needsQuote ? 2 : 0); // +1 null-term, [+1 space], [+2 quotes]
  133. if (pendingLen >= AZ_COMMAND_LINE_LEN)
  134. {
  135. AZ_Assert(false, "Command line exceeds the %d character limit!", AZ_COMMAND_LINE_LEN);
  136. return false;
  137. }
  138. if (needsSpace)
  139. {
  140. m_commandLine[m_commandLineLen++] = ' ';
  141. }
  142. if (needsQuote) // Branching instead of using a ternary on the format string to avoid warning 4774 (format literal expected)
  143. {
  144. azsnprintf(m_commandLine + m_commandLineLen,
  145. AZ_COMMAND_LINE_LEN - m_commandLineLen,
  146. "\"%s\"",
  147. arg);
  148. }
  149. else
  150. {
  151. azsnprintf(m_commandLine + m_commandLineLen,
  152. AZ_COMMAND_LINE_LEN - m_commandLineLen,
  153. "%s",
  154. arg);
  155. }
  156. // Inject the argument in the argument buffer to preserve/replicate argC and argV
  157. azstrncpy(&m_commandLineArgBuffer[m_nextCommandLineArgInsertPoint],
  158. AZ_COMMAND_LINE_LEN - m_nextCommandLineArgInsertPoint,
  159. arg,
  160. argLen+1);
  161. m_argV[m_argC++] = &m_commandLineArgBuffer[m_nextCommandLineArgInsertPoint];
  162. m_nextCommandLineArgInsertPoint += argLen + 1;
  163. m_commandLineLen = pendingLen;
  164. return true;
  165. }
  166. const char* GetReturnCodeString(ReturnCode code)
  167. {
  168. switch (code)
  169. {
  170. case ReturnCode::Success:
  171. return "Success";
  172. case ReturnCode::ErrCommandLine:
  173. return "Failed to copy command line arguments";
  174. case ReturnCode::ErrResourceLimit:
  175. return "A resource limit failed to update";
  176. case ReturnCode::ErrAppDescriptor:
  177. return "Application descriptor file was not found";
  178. case ReturnCode::ErrCrySystemLib:
  179. return "Failed to load the CrySystem library";
  180. case ReturnCode::ErrCrySystemInterface:
  181. return "Failed to initialize the CrySystem Interface";
  182. case ReturnCode::ErrCryEnvironment:
  183. return "Failed to initialize the global environment";
  184. case ReturnCode::ErrAssetProccessor:
  185. return "Failed to connect to AssetProcessor while the /Amazon/AzCore/Bootstrap/wait_for_connect value is 1\n."
  186. "wait_for_connect can be set to 0 within the bootstrap to allow connecting to the AssetProcessor"
  187. " to not be an error if unsuccessful.";
  188. default:
  189. return "Unknown error code";
  190. }
  191. }
  192. void CreateRemoteFileIO();
  193. // This function make sure the launcher has signaled the "CriticalAssetsCompiled"
  194. // lifecycle event as well as to load the "assetcatalog.xml" file if it exists
  195. void CompileCriticalAssets()
  196. {
  197. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  198. {
  199. // Reload the assetcatalog.xml at this point again
  200. // Start Monitoring Asset changes over the network and load the AssetCatalog.
  201. // Note: When using VFS this is the first time catalog will be loaded using remote's catalog file.
  202. auto LoadCatalog = [settingsRegistry](AZ::Data::AssetCatalogRequests* assetCatalogRequests)
  203. {
  204. if (AZ::IO::FixedMaxPath assetCatalogPath;
  205. settingsRegistry->Get(assetCatalogPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
  206. {
  207. assetCatalogPath /= "assetcatalog.xml";
  208. assetCatalogRequests->LoadCatalog(assetCatalogPath.c_str());
  209. }
  210. };
  211. AZ::Data::AssetCatalogRequestBus::Broadcast(AZStd::move(LoadCatalog));
  212. AZ_TracePrintf("Launcher", "CriticalAssetsCompiled\n");
  213. // Broadcast that critical assets are ready
  214. AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "CriticalAssetsCompiled", R"({})");
  215. }
  216. }
  217. // If the connect option is false, this function will return true
  218. // to make sure the Launcher passes the connected to AP check
  219. // If REMOTE_ASSET_PROCESSOR is not defined, then the launcher doesn't need
  220. // to connect to the AssetProcessor and therefore this function returns true
  221. bool ConnectToAssetProcessor([[maybe_unused]] bool connect)
  222. {
  223. bool connectedToAssetProcessor = true;
  224. #if defined(REMOTE_ASSET_PROCESSOR)
  225. if (connect)
  226. {
  227. // When the AssetProcessor is already launched it should take less than a second to perform a connection
  228. // but when the AssetProcessor needs to be launch it could take up to 15 seconds to have the AssetProcessor initialize
  229. // and able to negotiate a connection when running a debug build
  230. // and to negotiate a connection
  231. // Setting the connectTimeout to 3 seconds if not set within the settings registry
  232. AzFramework::AssetSystem::ConnectionSettings connectionSettings;
  233. AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings);
  234. connectionSettings.m_launchAssetProcessorOnFailedConnection = true;
  235. connectionSettings.m_connectionIdentifier = AzFramework::AssetSystem::ConnectionIdentifiers::Game;
  236. connectionSettings.m_loggingCallback = []([[maybe_unused]] AZStd::string_view logData)
  237. {
  238. AZ_TracePrintf("Launcher", "%.*s", aznumeric_cast<int>(logData.size()), logData.data());
  239. };
  240. AzFramework::AssetSystemRequestBus::BroadcastResult(connectedToAssetProcessor, &AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
  241. if (connectedToAssetProcessor)
  242. {
  243. AZ_TracePrintf("Launcher", "Connected to Asset Processor\n");
  244. CreateRemoteFileIO();
  245. }
  246. }
  247. #endif
  248. CompileCriticalAssets();
  249. return connectedToAssetProcessor;
  250. }
  251. //! Remote FileIO to use as a Virtual File System
  252. //! Communication of FileIOBase operations occur through an AssetProcessor connection
  253. void CreateRemoteFileIO()
  254. {
  255. AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
  256. AZ::s64 allowRemoteFilesystem{};
  257. AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, allowRemoteFilesystem,
  258. AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "remote_filesystem");
  259. if (allowRemoteFilesystem != 0)
  260. {
  261. using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
  262. // Application::StartCommon will set a LocalFileIO base first.
  263. // This provides an opportunity for the RemoteFileIO to override the direct instance
  264. auto remoteFileIo = new AZ::IO::RemoteFileIO(AZ::IO::FileIOBase::GetDirectInstance()); // Wrap LocalFileIO the direct instance
  265. // SetDirectInstance will assert if this has already been set and we don't clear first
  266. AZ::IO::FileIOBase::SetDirectInstance(nullptr);
  267. // Wrap AZ:IO::LocalFileIO the direct instance
  268. AZ::IO::FileIOBase::SetDirectInstance(remoteFileIo);
  269. // Set file paths to uses aliases, they will be resolved by the remote file system.
  270. // Prefixing alias with / so they are treated as absolute paths by Path class,
  271. // otherwise odd concatenations of aliases happen leading to invalid paths when
  272. // resolved by the remote system.
  273. settingsRegistry->Set(FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/engine_path", "/@engroot@");
  274. settingsRegistry->Set(FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path", "/@projectroot@");
  275. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder, "/@engroot@");
  276. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath, "/@projectroot@");
  277. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder, "/@products@");
  278. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath, "/@user@");
  279. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectLogPath, "/@log@");
  280. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_DevWriteStorage, "/@usercache@");
  281. }
  282. }
  283. ReturnCode Run(const PlatformMainInfo& mainInfo)
  284. {
  285. if (mainInfo.m_updateResourceLimits
  286. && !mainInfo.m_updateResourceLimits())
  287. {
  288. return ReturnCode::ErrResourceLimit;
  289. }
  290. // Game Application (AzGameFramework)
  291. int gameArgC = mainInfo.m_argC;
  292. char** gameArgV = const_cast<char**>(mainInfo.m_argV);
  293. constexpr size_t MaxCommandArgsCount = 128;
  294. using ArgumentContainer = AZStd::fixed_vector<char*, MaxCommandArgsCount>;
  295. ArgumentContainer argContainer(gameArgV, gameArgV + gameArgC);
  296. using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
  297. // Inject the Engine Path, Project Path and Project Name into the CommandLine parameters to the command line
  298. // in order to be used in the Settings Registry
  299. // The command line overrides are stored in the following fixed strings
  300. // until the ComponentApplication constructor can parse the command line parameters
  301. FixedValueString projectNameOptionOverride;
  302. // Insert the project_name option to the front
  303. const AZStd::string_view launcherProjectName = GetProjectName();
  304. if (!launcherProjectName.empty())
  305. {
  306. const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name";
  307. projectNameOptionOverride = FixedValueString::format(R"(--regset="%s=%.*s")",
  308. projectNameKey.c_str(), aznumeric_cast<int>(launcherProjectName.size()), launcherProjectName.data());
  309. argContainer.emplace_back(projectNameOptionOverride.data());
  310. }
  311. // Non-host platforms cannot use the project path that is #defined within the launcher.
  312. // In this case the the result of AZ::Utils::GetDefaultAppRoot is used instead
  313. #if !AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
  314. FixedValueString projectPathOptionOverride;
  315. FixedValueString enginePathOptionOverride;
  316. AZStd::string_view projectPath;
  317. // Make sure the defaultAppRootPath variable is in scope long enough until the projectPath string_view is used below
  318. AZStd::optional<AZ::IO::FixedMaxPathString> defaultAppRootPath = AZ::Utils::GetDefaultAppRootPath();
  319. if (defaultAppRootPath.has_value())
  320. {
  321. projectPath = *defaultAppRootPath;
  322. }
  323. if (!projectPath.empty())
  324. {
  325. const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
  326. + "/project_path";
  327. projectPathOptionOverride = FixedValueString::format(R"(--regset="%s=%.*s")",
  328. projectPathKey.c_str(), aznumeric_cast<int>(projectPath.size()), projectPath.data());
  329. argContainer.emplace_back(projectPathOptionOverride.data());
  330. // For non-host platforms set the engine root to be the project root
  331. // Since the directories available during execution are limited on those platforms
  332. AZStd::string_view enginePath = projectPath;
  333. const auto enginePathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
  334. + "/engine_path";
  335. enginePathOptionOverride = FixedValueString ::format(R"(--regset="%s=%.*s")",
  336. enginePathKey.c_str(), aznumeric_cast<int>(enginePath.size()), enginePath.data());
  337. argContainer.emplace_back(enginePathOptionOverride.data());
  338. }
  339. #endif
  340. AzGameFramework::GameApplication gameApplication(aznumeric_cast<int>(argContainer.size()), argContainer.data());
  341. // The settings registry has been created by the AZ::ComponentApplication constructor at this point
  342. auto settingsRegistry = AZ::SettingsRegistry::Get();
  343. if (settingsRegistry == nullptr)
  344. {
  345. // Settings registry must be available at this point in order to continue
  346. return ReturnCode::ErrValidation;
  347. }
  348. const AZStd::string_view buildTargetName = GetBuildTargetName();
  349. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(*settingsRegistry, buildTargetName);
  350. //Store the launcher type to the Settings Registry
  351. AZStd::string_view launcherType = GetLauncherTypeSpecialization();
  352. settingsRegistry->Set(LauncherTypeTag, launcherType);
  353. // Also add the launcher type as a specialization as well
  354. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddSpecialization(*settingsRegistry, launcherType);
  355. // Finally add the "launcher" specialization tag into the Settings Registry
  356. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddSpecialization(*settingsRegistry, LauncherFilenameTag);
  357. AZ_TracePrintf("Launcher", R"(Running project "%.*s")" "\n"
  358. R"(The project name has been successfully set in the Settings Registry at key "%s/project_name")"
  359. R"( for Launcher target "%.*s")" "\n",
  360. aznumeric_cast<int>(launcherProjectName.size()), launcherProjectName.data(),
  361. AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey,
  362. aznumeric_cast<int>(buildTargetName.size()), buildTargetName.data());
  363. AZ::SettingsRegistryInterface::FixedValueString pathToAssets;
  364. if (!settingsRegistry->Get(pathToAssets, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
  365. {
  366. // Default to mainInfo.m_appResource if the cache root folder is missing from the Settings Registry
  367. pathToAssets = mainInfo.m_appResourcesPath;
  368. AZ_Error("Launcher", false, "Unable to retrieve asset cache root folder from the settings registry at json pointer path %s",
  369. AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
  370. }
  371. else
  372. {
  373. AZ_TracePrintf("Launcher", "The asset cache folder of %s has been successfully read from the Settings Registry\n",
  374. pathToAssets.c_str());
  375. }
  376. // System Init Params ("Legacy" Open 3D Engine)
  377. SSystemInitParams systemInitParams;
  378. memset(&systemInitParams, 0, sizeof(SSystemInitParams));
  379. {
  380. AzGameFramework::GameApplication::StartupParameters gameApplicationStartupParams;
  381. #if defined(AZ_MONOLITHIC_BUILD)
  382. gameApplicationStartupParams.m_createStaticModulesCallback = CreateStaticModules;
  383. gameApplicationStartupParams.m_loadDynamicModules = false;
  384. #endif // defined(AZ_MONOLITHIC_BUILD)
  385. const char* isDedicatedServerCommand = IsDedicatedServer() ? "sv_isDedicated true" : "sv_isDedicated false";
  386. AZ::Interface<AZ::IConsole>::Get()->PerformCommand(isDedicatedServerCommand);
  387. gameApplication.Start({}, gameApplicationStartupParams);
  388. //connect to the asset processor using the bootstrap values
  389. const bool allowedEngineConnection = !systemInitParams.bToolMode && !systemInitParams.bTestMode && bg_ConnectToAssetProcessor;
  390. if (!ConnectToAssetProcessor(allowedEngineConnection))
  391. {
  392. AZ::s64 waitForConnect{};
  393. AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, waitForConnect,
  394. AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "wait_for_connect");
  395. if (waitForConnect != 0)
  396. {
  397. AZ_Error("Launcher", false, "Failed to connect to AssetProcessor.");
  398. return ReturnCode::ErrAssetProccessor;
  399. }
  400. }
  401. //Initialize the Debug trace instance to create necessary environment variables
  402. AZ::Debug::Trace::Instance().Init();
  403. if (!IsDedicatedServer() && !systemInitParams.bToolMode && !systemInitParams.bTestMode)
  404. {
  405. if (auto nativeUI = AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get(); nativeUI != nullptr)
  406. {
  407. nativeUI->SetMode(AZ::NativeUI::Mode::ENABLED);
  408. }
  409. }
  410. }
  411. if (mainInfo.m_onPostAppStart)
  412. {
  413. mainInfo.m_onPostAppStart();
  414. }
  415. azstrncpy(systemInitParams.szSystemCmdLine, sizeof(systemInitParams.szSystemCmdLine),
  416. mainInfo.m_commandLine, mainInfo.m_commandLineLen);
  417. systemInitParams.sLogFileName = GetLogFilename();
  418. systemInitParams.hInstance = mainInfo.m_instance;
  419. systemInitParams.hWnd = mainInfo.m_window;
  420. systemInitParams.pPrintSync = mainInfo.m_printSink;
  421. systemInitParams.bDedicatedServer = IsDedicatedServer();
  422. AZ::s64 remoteFileSystemEnabled{};
  423. AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, remoteFileSystemEnabled,
  424. AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "remote_filesystem");
  425. if (remoteFileSystemEnabled != 0)
  426. {
  427. // Reset local variable pathToAssets now that it's using remote file system
  428. pathToAssets = "";
  429. settingsRegistry->Get(pathToAssets, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
  430. AZ_TracePrintf("Launcher", "Application is configured for VFS");
  431. AZ_TracePrintf("Launcher", "Log and cache files will be written to the Cache directory on your host PC");
  432. #if defined(AZ_ENABLE_TRACING)
  433. constexpr const char* message = "If your game does not run, check any of the following:\n"
  434. "\t- Verify the remote_ip address is correct in bootstrap.cfg";
  435. #endif
  436. if (mainInfo.m_additionalVfsResolution)
  437. {
  438. AZ_TracePrintf("Launcher", "%s\n%s", message, mainInfo.m_additionalVfsResolution)
  439. }
  440. else
  441. {
  442. AZ_TracePrintf("Launcher", "%s", message)
  443. }
  444. }
  445. else
  446. {
  447. AZ_TracePrintf("Launcher", "Application is configured to use device local files at %s\n", pathToAssets.c_str());
  448. AZ_TracePrintf("Launcher", "Log and cache files will be written to device storage\n");
  449. }
  450. // Create CrySystem.
  451. #if !defined(AZ_MONOLITHIC_BUILD)
  452. constexpr const char* crySystemLibraryName = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX "CrySystem" AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
  453. AZStd::unique_ptr<AZ::DynamicModuleHandle> crySystemLibrary = AZ::DynamicModuleHandle::Create(crySystemLibraryName);
  454. if (crySystemLibrary->Load(true))
  455. {
  456. PFNCREATESYSTEMINTERFACE CreateSystemInterface =
  457. crySystemLibrary->GetFunction<PFNCREATESYSTEMINTERFACE>("CreateSystemInterface");
  458. if (CreateSystemInterface)
  459. {
  460. systemInitParams.pSystem = CreateSystemInterface(systemInitParams);
  461. }
  462. }
  463. #else
  464. systemInitParams.pSystem = CreateSystemInterface(systemInitParams);
  465. #endif // !defined(AZ_MONOLITHIC_BUILD)
  466. AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "LegacySystemInterfaceCreated", R"({})");
  467. ReturnCode status = ReturnCode::Success;
  468. if (systemInitParams.pSystem)
  469. {
  470. // Process queued events before main loop.
  471. AZ::TickBus::ExecuteQueuedEvents();
  472. #if !defined(SYS_ENV_AS_STRUCT)
  473. gEnv = systemInitParams.pSystem->GetGlobalEnvironment();
  474. #endif // !defined(SYS_ENV_AS_STRUCT)
  475. if (gEnv && gEnv->pConsole)
  476. {
  477. // Execute autoexec.cfg to load the initial level
  478. auto autoExecFile = AZ::IO::FixedMaxPath{pathToAssets} / "autoexec.cfg";
  479. AZ::Interface<AZ::IConsole>::Get()->ExecuteConfigFile(autoExecFile.Native());
  480. // Find out if console command file was passed
  481. // via --console-command-file=%filename% and execute it
  482. ExecuteConsoleCommandFile(gameApplication);
  483. gEnv->pSystem->ExecuteCommandLine(false);
  484. AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "LegacyCommandLineProcessed", R"({})");
  485. // Run the main loop
  486. RunMainLoop(gameApplication);
  487. }
  488. else
  489. {
  490. status = ReturnCode::ErrCryEnvironment;
  491. }
  492. }
  493. else
  494. {
  495. status = ReturnCode::ErrCrySystemInterface;
  496. }
  497. #if !defined(AZ_MONOLITHIC_BUILD)
  498. #if !defined(_RELEASE)
  499. // until CrySystem can be removed (or made to be managed by the component application),
  500. // we need to manually clear the BudgetTracker before CrySystem is unloaded so the Budget
  501. // pointer(s) it has references to are cleared properly
  502. if (auto budgetTracker = AZ::Interface<AZ::Debug::BudgetTracker>::Get(); budgetTracker)
  503. {
  504. budgetTracker->Reset();
  505. }
  506. #endif // !defined(_RELEASE)
  507. // The order of operations here is to delete CrySystem, stop the game application, then unload the CrySystem dll.
  508. // If we unloaded the CrySystem dll before stopping the game application, we can potentially have crashes
  509. // if the CrySystem dll created any EBus contexts, since those contexts would get destroyed before subsystems could
  510. // disconnect from the buses.
  511. SAFE_DELETE(systemInitParams.pSystem);
  512. gameApplication.Stop();
  513. crySystemLibrary.reset(nullptr);
  514. #else
  515. SAFE_DELETE(systemInitParams.pSystem);
  516. gameApplication.Stop();
  517. #endif // !defined(AZ_MONOLITHIC_BUILD)
  518. AZ::Debug::Trace::Instance().Destroy();
  519. return status;
  520. }
  521. }