/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include // This should be the first include to make sure Windows.h is defined with NOMINMAX #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Application.h" #include #include #include static const char* s_azFrameworkWarningWindow = "AzFramework"; namespace AzFramework { namespace ApplicationInternal { static constexpr const char s_prefabSystemKey[] = "/Amazon/Preferences/EnablePrefabSystem"; static constexpr const char s_prefabWipSystemKey[] = "/Amazon/Preferences/EnablePrefabSystemWipFeatures"; static constexpr const char s_legacySlicesAssertKey[] = "/Amazon/Preferences/ShouldAssertForLegacySlicesUsage"; // A Helper function that can load an app descriptor from file. AZ::Outcome, AZStd::string> LoadDescriptorFromFilePath(const char* appDescriptorFilePath, AZ::SerializeContext& serializeContext) { AZStd::unique_ptr loadedDescriptor; AZ::IO::SystemFile appDescriptorFile; if (!appDescriptorFile.Open(appDescriptorFilePath, AZ::IO::SystemFile::SF_OPEN_READ_ONLY)) { return AZ::Failure(AZStd::string::format("Failed to open file: %s", appDescriptorFilePath)); } AZ::IO::SystemFileStream appDescriptorFileStream(&appDescriptorFile, true); if (!appDescriptorFileStream.IsOpen()) { return AZ::Failure(AZStd::string::format("Failed to stream file: %s", appDescriptorFilePath)); } // Callback function for allocating the root elements in the file. AZ::ObjectStream::InplaceLoadRootInfoCB inplaceLoadCb = [](void** rootAddress, const AZ::SerializeContext::ClassData**, const AZ::Uuid& classId, AZ::SerializeContext*) { if (rootAddress && classId == azrtti_typeid()) { // ComponentApplication::Descriptor is normally a singleton. // Force a unique instance to be created. *rootAddress = aznew AZ::ComponentApplication::Descriptor(); } }; // Callback function for saving the root elements in the file. AZ::ObjectStream::ClassReadyCB classReadyCb = [&loadedDescriptor](void* classPtr, const AZ::Uuid& classId, AZ::SerializeContext* context) { // Save descriptor, delete anything else loaded from file. if (classId == azrtti_typeid()) { loadedDescriptor.reset(static_cast(classPtr)); } else if (const AZ::SerializeContext::ClassData* classData = context->FindClassData(classId)) { classData->m_factory->Destroy(classPtr); } else { AZ_Error("Application", false, "Unexpected type %s found in application descriptor file. This memory will leak.", classId.ToString().c_str()); } }; // There's other stuff in the file we may not recognize (system components), but we're not interested in that stuff. AZ::ObjectStream::FilterDescriptor loadFilter(&AZ::Data::AssetFilterNoAssetLoading, AZ::ObjectStream::FILTERFLAG_IGNORE_UNKNOWN_CLASSES); if (!AZ::ObjectStream::LoadBlocking(&appDescriptorFileStream, serializeContext, classReadyCb, loadFilter, inplaceLoadCb)) { return AZ::Failure(AZStd::string::format("Failed to load objects from file: %s", appDescriptorFilePath)); } if (!loadedDescriptor) { return AZ::Failure(AZStd::string::format("Failed to find descriptor object in file: %s", appDescriptorFilePath)); } return AZ::Success(AZStd::move(loadedDescriptor)); } } Application::Application() : Application(nullptr, nullptr) { } Application::Application(int* argc, char*** argv) : ComponentApplication( argc ? *argc : 0, argv ? *argv : nullptr ) { // Startup default local FileIO (hits OSAllocator) if not already setup. if (AZ::IO::FileIOBase::GetDirectInstance() == nullptr) { m_directFileIO = AZStd::make_unique(); AZ::IO::FileIOBase::SetDirectInstance(m_directFileIO.get()); } // Initializes the IArchive for reading archive(.pak) files if (auto archive = AZ::Interface::Get(); archive == nullptr) { m_archive = AZStd::make_unique(); AZ::Interface::Register(m_archive.get()); } // Set the ArchiveFileIO as the default FileIOBase instance if (AZ::IO::FileIOBase::GetInstance() == nullptr) { m_archiveFileIO = AZStd::make_unique(m_archive.get()); AZ::IO::FileIOBase::SetInstance(m_archiveFileIO.get()); SetFileIOAliases(); } if (auto nativeUI = AZ::Interface::Get(); nativeUI == nullptr) { m_nativeUI = AZStd::make_unique(); AZ::Interface::Register(m_nativeUI.get()); } ApplicationRequests::Bus::Handler::BusConnect(); AZ::UserSettingsFileLocatorBus::Handler::BusConnect(); NetSystemRequestBus::Handler::BusConnect(); } Application::~Application() { if (m_isStarted) { Stop(); } NetSystemRequestBus::Handler::BusDisconnect(); AZ::UserSettingsFileLocatorBus::Handler::BusDisconnect(); ApplicationRequests::Bus::Handler::BusDisconnect(); if (AZ::Interface::Get() == m_nativeUI.get()) { AZ::Interface::Unregister(m_nativeUI.get()); } m_nativeUI.reset(); // Unset the Archive file IO if it is set as the direct instance if (AZ::IO::FileIOBase::GetInstance() == m_archiveFileIO.get()) { AZ::IO::FileIOBase::SetInstance(nullptr); } m_archiveFileIO.reset(); // Destroy the IArchive instance if (AZ::Interface::Get() == m_archive.get()) { AZ::Interface::Unregister(m_archive.get()); } m_archive.reset(); // Unset the Local file IO if it is set as the direct instance if (AZ::IO::FileIOBase::GetDirectInstance() == m_directFileIO.get()) { AZ::IO::FileIOBase::SetDirectInstance(nullptr); } // Destroy the Direct instance after the IArchive has been destroyed // Archive classes relies on the FileIOBase DirectInstance to close // files properly m_directFileIO.reset(); // The AZ::Console skips destruction and always leaks to allow it to be used in static memory } void Application::Start(const Descriptor& descriptor, const StartupParameters& startupParameters) { AZ::Entity* systemEntity = Create(descriptor, startupParameters); // Sets FileIOAliases again in case the App root was overridden by the // startupParameters in ComponentApplication::Create SetFileIOAliases(); if (systemEntity) { StartCommon(systemEntity); } } void Application::StartCommon(AZ::Entity* systemEntity) { m_pimpl.reset(Implementation::Create()); systemEntity->Init(); systemEntity->Activate(); AZ_Assert(systemEntity->GetState() == AZ::Entity::State::Active, "System Entity failed to activate."); m_isStarted = true; } void Application::PreModuleLoad() { SetRootPath(RootPathType::EngineRoot, m_engineRoot.c_str()); AZ_TracePrintf(s_azFrameworkWarningWindow, "Engine Path: %s\n", m_engineRoot.c_str()); } void Application::Stop() { if (m_isStarted) { ApplicationLifecycleEvents::Bus::Broadcast(&ApplicationLifecycleEvents::OnApplicationAboutToStop); m_pimpl.reset(); /* The following line of code is a temporary fix. * GridMate's ReplicaChunkDescriptor is stored in a global environment variable 'm_globalDescriptorTable' * which does not get cleared when Application shuts down. We need to un-reflect here to clear ReplicaChunkDescriptor * so that ReplicaChunkDescriptor::m_vdt doesn't get flooded when we repeatedly instantiate Application in unit tests. */ AZ::ReflectionEnvironment::GetReflectionManager()->RemoveReflectContext(); // Free any memory owned by the command line container. m_commandLine = CommandLine(); Destroy(); m_isStarted = false; } } void Application::RegisterCoreComponents() { AZ::ComponentApplication::RegisterCoreComponents(); // This is internal Amazon code, so register it's components for metrics tracking, otherwise the name of the component won't get sent back. AZStd::vector componentUuidsForMetricsCollection { azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), #if !defined(_RELEASE) azrtti_typeid(), #endif azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), #if !defined(AZCORE_EXCLUDE_LUA) azrtti_typeid(), azrtti_typeid(), #endif // #if !defined(AZCORE_EXCLUDE_LUA) }; EBUS_EVENT(AzFramework::MetricsPlainTextNameRegistrationBus, RegisterForNameSending, componentUuidsForMetricsCollection); } void Application::Reflect(AZ::ReflectContext* context) { AZ::ComponentApplication::Reflect(context); AZ::DataPatch::Reflect(context); AZ::EntityUtils::Reflect(context); AZ::NonUniformScaleRequests::Reflect(context); AzFramework::BehaviorEntity::Reflect(context); AzFramework::EntityContext::Reflect(context); AzFramework::SliceEntityOwnershipService::Reflect(context); AzFramework::SimpleAssetReferenceBase::Reflect(context); AzFramework::ConsoleRequests::Reflect(context); AzFramework::ConsoleNotifications::Reflect(context); AzFramework::ViewportRequests::Reflect(context); AzFramework::BoundsRequests::Reflect(context); AzFramework::ScreenGeometryReflect(context); AzFramework::RemoteStorageDriveConfig::Reflect(context); Physics::ReflectionUtils::ReflectPhysicsApi(context); AzFramework::Terrain::TerrainDataRequests::Reflect(context); if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { AzFramework::AssetRegistry::ReflectSerialize(serializeContext); CameraState::Reflect(*serializeContext); AzFramework::AssetBundleManifest::ReflectSerialize(serializeContext); } } AZ::ComponentTypeList Application::GetRequiredSystemComponents() const { AZ::ComponentTypeList components = ComponentApplication::GetRequiredSystemComponents(); components.insert(components.end(), { azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), AZ::Uuid("{624a7be2-3c7e-4119-aee2-1db2bdb6cc89}"), // ScriptDebugAgent }); return components; } // UserSettingsFileLocatorBus AZStd::string Application::ResolveFilePath([[maybe_unused]] AZ::u32 providerId) { AZ::IO::Path userSettingsPath; m_settingsRegistry->Get(userSettingsPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath); userSettingsPath /= "UserSettings.xml"; return userSettingsPath.Native(); } AZ::Component* Application::EnsureComponentAdded(AZ::Entity* systemEntity, const AZ::Uuid& typeId) { AZ::Component* component = systemEntity->FindComponent(typeId); if (!component) { if (systemEntity->IsComponentReadyToAdd(typeId)) { component = systemEntity->CreateComponent(typeId); } else { AZ_Assert(false, "Failed to add component of type %s because conditions are not met.", typeId.ToString().c_str()); } } return component; } void Application::CreateStaticModules(AZStd::vector& outModules) { AZ::ComponentApplication::CreateStaticModules(outModules); outModules.emplace_back(aznew AzFrameworkModule()); } const char* Application::GetAppRoot() const { return m_appRoot.c_str(); } const char* Application::GetCurrentConfigurationName() const { #if defined(_RELEASE) return "Release"; #elif defined(_DEBUG) return "Debug"; #else return "Profile"; #endif } void Application::CreateReflectionManager() { ComponentApplication::CreateReflectionManager(); // Setup NetworkContext AZ::ReflectionEnvironment::GetReflectionManager()->AddReflectContext(); } //////////////////////////////////////////////////////////////////////////// AZ::Uuid Application::GetComponentTypeId(const AZ::EntityId& entityId, const AZ::ComponentId& componentId) { AZ::Uuid uuid(AZ::Uuid::CreateNull()); AZ::Entity* entity = nullptr; EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId); if (entity) { AZ::Component* component = entity->FindComponent(componentId); if (component) { uuid = component->RTTI_GetType(); } } return uuid; } //////////////////////////////////////////////////////////////////////////// NetworkContext* Application::GetNetworkContext() { NetworkContext* result = nullptr; if (auto reflectionManager = AZ::ReflectionEnvironment::GetReflectionManager()) { result = reflectionManager->GetReflectContext(); } return result; } void Application::ResolveEnginePath(AZStd::string& engineRelativePath) const { AZ::IO::FixedMaxPath fullPath = m_engineRoot / engineRelativePath; engineRelativePath = fullPath.String(); } void Application::CalculateBranchTokenForEngineRoot(AZStd::string& token) const { AzFramework::StringFunc::AssetPath::CalculateBranchToken(m_engineRoot.String(), token); } //////////////////////////////////////////////////////////////////////////// void Application::MakePathRootRelative(AZStd::string& fullPath) { MakePathRelative(fullPath, m_engineRoot.c_str()); } //////////////////////////////////////////////////////////////////////////// void Application::MakePathAssetRootRelative(AZStd::string& fullPath) { // relative file paths wrt AssetRoot are always lowercase AZStd::to_lower(fullPath.begin(), fullPath.end()); AZStd::string cacheAssetPath; if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) { settingsRegistry->Get(cacheAssetPath, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder); } MakePathRelative(fullPath, cacheAssetPath.c_str()); } //////////////////////////////////////////////////////////////////////////// void Application::MakePathRelative(AZStd::string& fullPath, const char* rootPath) { AZ_Assert(rootPath, "Provided root path is null."); NormalizePathKeepCase(fullPath); AZStd::string root(rootPath); NormalizePathKeepCase(root); if (!azstrnicmp(fullPath.c_str(), root.c_str(), root.length())) { fullPath = fullPath.substr(root.length()); } while (!fullPath.empty() && fullPath[0] == AZ_CORRECT_DATABASE_SEPARATOR) { fullPath.erase(fullPath.begin()); } } //////////////////////////////////////////////////////////////////////////// void Application::NormalizePath(AZStd::string& path) { ComponentApplication::NormalizePath(path.begin(), path.end(), true); } //////////////////////////////////////////////////////////////////////////// void Application::NormalizePathKeepCase(AZStd::string& path) { ComponentApplication::NormalizePath(path.begin(), path.end(), false); } //////////////////////////////////////////////////////////////////////////// void Application::PumpSystemEventLoopOnce() { if (m_pimpl) { m_pimpl->PumpSystemEventLoopOnce(); } } //////////////////////////////////////////////////////////////////////////// void Application::PumpSystemEventLoopUntilEmpty() { if (m_pimpl) { m_pimpl->PumpSystemEventLoopUntilEmpty(); } } //////////////////////////////////////////////////////////////////////////// void Application::PumpSystemEventLoopWhileDoingWorkInNewThread(const AZStd::chrono::milliseconds& eventPumpFrequency, const AZStd::function& workForNewThread, const char* newThreadName) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzFramework); AZStd::thread_desc newThreadDesc; newThreadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS; newThreadDesc.m_name = newThreadName; AZStd::binary_semaphore binarySemaphore; AZStd::thread newThread([&workForNewThread, &binarySemaphore, &newThreadName] { AZ_PROFILE_SCOPE_DYNAMIC(AZ::Debug::ProfileCategory::AzFramework, "Application::PumpSystemEventLoopWhileDoingWorkInNewThread:ThreadWorker %s", newThreadName); workForNewThread(); binarySemaphore.release(); }, &newThreadDesc); while (!binarySemaphore.try_acquire_for(eventPumpFrequency)) { PumpSystemEventLoopUntilEmpty(); } { AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzFramework, "Application::PumpSystemEventLoopWhileDoingWorkInNewThread:WaitOnThread %s", newThreadName); newThread.join(); } // Pump once at the end so we're back at 0 instead of potentially eventPumpFrequency - 1 ms since the last event pump PumpSystemEventLoopUntilEmpty(); } //////////////////////////////////////////////////////////////////////////// void Application::RunMainLoop() { while (!m_exitMainLoopRequested) { PumpSystemEventLoopUntilEmpty(); Tick(); } } //////////////////////////////////////////////////////////////////////////// AZ_CVAR(float, t_frameTimeOverride, 0.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "If > 0, overrides the application delta frame-time with the provided value"); void Application::Tick(float deltaOverride /*= -1.f*/) { ComponentApplication::Tick((t_frameTimeOverride > 0.0f) ? t_frameTimeOverride : deltaOverride); } //////////////////////////////////////////////////////////////////////////// void Application::TerminateOnError(int errorCode) { if (m_pimpl) { m_pimpl->TerminateOnError(errorCode); } else { exit(errorCode); } } void Application::SetRootPath(RootPathType type, const char* source) { const size_t sourceLen = strlen(source); // Copy the source path to the intended root path and correct the path separators as well switch (type) { case RootPathType::AppRoot: { AZ_Assert(sourceLen < m_appRoot.Native().max_size(), "String overflow for App Root: %s", source); m_appRoot = AZ::IO::PathView(source).LexicallyNormal(); } break; case RootPathType::EngineRoot: { AZ_Assert(sourceLen < m_engineRoot.Native().max_size(), "String overflow for Engine Root: %s", source); m_engineRoot = AZ::IO::PathView(source).LexicallyNormal(); } break; default: AZ_Assert(false, "Invalid RootPathType (%d)", static_cast(type)); } } static void CreateUserCache(const AZ::IO::FixedMaxPath& cacheUserPath, AZ::IO::FileIOBase& fileIoBase) { // The number of max attempts ultimately dictates the number of Lumberyard instances that can run // simultaneously. This should be a reasonably high number so that it doesn't artificially limit // the number of instances (ex: parallel level exports via multiple Editor runs). It also shouldn't // be set *infinitely* high - each cache folder is GBs in size, and finding a free directory is a // linear search, so the more instances we allow, the longer the search will take. // 128 seems like a reasonable compromise. constexpr int maxAttempts = 128; constexpr const char* userCachePathFilename{ "Cache" }; AZ::IO::FixedMaxPath userCachePath = cacheUserPath / userCachePathFilename; #if AZ_TRAIT_OS_IS_HOST_OS_PLATFORM int attemptNumber; for (attemptNumber = 0; attemptNumber < maxAttempts; ++attemptNumber) { if (attemptNumber != 0) { userCachePath.ReplaceFilename(AZStd::string_view{ AZ::IO::FixedMaxPathString::format("%s%i", userCachePathFilename, attemptNumber) }); } // if the directory already exists, check for locked file auto cacheLockFilePath = userCachePath / "lockfile.txt"; constexpr auto LockFileMode = AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY | AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH; if (AZ::IO::SystemFile lockFileHandle; lockFileHandle.Open(cacheLockFilePath.c_str(), LockFileMode)) { break; } } if (attemptNumber >= maxAttempts) { userCachePath.ReplaceFilename(userCachePathFilename); AZ_TracePrintf("Application", "Couldn't find a valid asset cache folder after %i attempts." " Setting cache folder to %s\n", maxAttempts, userCachePath.c_str()); } #endif fileIoBase.SetAlias("@usercache@", userCachePath.c_str()); } void Application::SetFileIOAliases() { if (m_archiveFileIO) { auto fileIoBase = m_archiveFileIO.get(); // Set up the default file aliases based on the settings registry fileIoBase->SetAlias("@assets@", ""); fileIoBase->SetAlias("@root@", GetEngineRoot()); fileIoBase->SetAlias("@engroot@", GetEngineRoot()); fileIoBase->SetAlias("@projectroot@", GetEngineRoot()); fileIoBase->SetAlias("@exefolder@", GetExecutableFolder()); { AZ::IO::FixedMaxPath pathAliases; if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder)) { fileIoBase->SetAlias("@projectcache@", pathAliases.c_str()); } pathAliases.clear(); if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder)) { fileIoBase->SetAlias("@projectplatformcache@", pathAliases.c_str()); fileIoBase->SetAlias("@assets@", pathAliases.c_str()); fileIoBase->SetAlias("@root@", pathAliases.c_str()); // Deprecated Use @projectplatformcache@ } pathAliases.clear(); if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder)) { fileIoBase->SetAlias("@engroot@", pathAliases.c_str()); fileIoBase->SetAlias("@devroot@", pathAliases.c_str()); // Deprecated - Use @engroot@ } pathAliases.clear(); if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath)) { fileIoBase->SetAlias("@devassets@", pathAliases.c_str()); // Deprecated - Use @projectsourceassets@ fileIoBase->SetAlias("@projectroot@", pathAliases.c_str()); fileIoBase->SetAlias("@projectsourceassets@", (pathAliases / "Assets").c_str()); } } AZ::IO::FixedMaxPath projectUserPath; if (m_settingsRegistry->Get(projectUserPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath)) { fileIoBase->SetAlias("@user@", projectUserPath.c_str()); AZ::IO::FixedMaxPath projectLogPath = projectUserPath / "log"; fileIoBase->SetAlias("@log@", projectLogPath.c_str()); fileIoBase->CreatePath(projectLogPath.c_str()); // Create the log directory at this point CreateUserCache(projectUserPath, *fileIoBase); } } } bool Application::IsPrefabSystemEnabled() const { bool value = true; if (auto* registry = AZ::SettingsRegistry::Get()) { registry->Get(value, ApplicationInternal::s_prefabSystemKey); } return value; } bool Application::ArePrefabWipFeaturesEnabled() const { bool value = false; if (auto* registry = AZ::SettingsRegistry::Get()) { registry->Get(value, ApplicationInternal::s_prefabWipSystemKey); } return value; } void Application::SetPrefabSystemEnabled(bool enable) { if (auto* registry = AZ::SettingsRegistry::Get()) { registry->Set(ApplicationInternal::s_prefabSystemKey, enable); } } bool Application::IsPrefabSystemForLevelsEnabled() const { return IsPrefabSystemEnabled(); } bool Application::ShouldAssertForLegacySlicesUsage() const { bool value = false; if (auto* registry = AZ::SettingsRegistry::Get()) { registry->Get(value, ApplicationInternal::s_legacySlicesAssertKey); } return value; } } // namespace AzFramework