You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/AzFramework/AzFramework/Application/Application.cpp

831 lines
34 KiB
C++

/*
* 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 <AzCore/PlatformIncl.h> // This should be the first include to make sure Windows.h is defined with NOMINMAX
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/NonUniformScaleBus.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/Slice/SliceSystemComponent.h>
#include <AzCore/Jobs/JobManagerComponent.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Script/ScriptSystemComponent.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/string/regex.h>
#include <AzCore/Serialization/DataPatch.h>
#include <AzCore/Debug/FrameProfilerComponent.h>
#include <AzCore/NativeUI/NativeUISystemComponent.h>
#include <AzCore/Module/ModuleManagerBus.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/Asset/SimpleAsset.h>
#include <AzFramework/Asset/AssetBundleManifest.h>
#include <AzFramework/Asset/AssetCatalogComponent.h>
#include <AzFramework/Asset/CustomAssetTypeComponent.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzFramework/Asset/AssetRegistry.h>
#include <AzFramework/Components/ConsoleBus.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Entity/BehaviorEntity.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
#include <AzFramework/FileFunc/FileFunc.h>
#include <AzFramework/FileTag/FileTagComponent.h>
#include <AzFramework/Input/System/InputSystemComponent.h>
#include <AzFramework/Scene/SceneSystemComponent.h>
#include <AzFramework/Components/AzFrameworkConfigurationSystemComponent.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/IO/RemoteStorageDrive.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
#include <AzFramework/Physics/Utils.h>
#include <AzFramework/Render/GameIntersectorComponent.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzFramework/Archive/Archive.h>
#include <AzFramework/Archive/ArchiveFileIO.h>
#include <AzFramework/Script/ScriptRemoteDebugging.h>
#include <AzFramework/Script/ScriptComponent.h>
#include <AzFramework/Spawnable/SpawnableSystemComponent.h>
#include <AzFramework/StreamingInstall/StreamingInstall.h>
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Driller/RemoteDrillerInterface.h>
#include <AzFramework/Network/NetworkContext.h>
#include <AzFramework/Metrics/MetricsPlainTextNameRegistration.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <AzCore/Console/Console.h>
#include <AzFramework/Viewport/ViewportBus.h>
#include <GridMate/Memory.h>
#include "Application.h"
#include <AzFramework/AzFrameworkModule.h>
#include <cctype>
#include <stdio.h>
static const char* s_azFrameworkWarningWindow = "AzFramework";
static const char* s_engineConfigFileName = "engine.json";
static const char* s_engineConfigEngineVersionKey = "LumberyardVersion";
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::unique_ptr<AZ::ComponentApplication::Descriptor>, AZStd::string> LoadDescriptorFromFilePath(const char* appDescriptorFilePath, AZ::SerializeContext& serializeContext)
{
AZStd::unique_ptr<AZ::ComponentApplication::Descriptor> 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<AZ::ComponentApplication::Descriptor>())
{
// 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<AZ::ComponentApplication::Descriptor>())
{
loadedDescriptor.reset(static_cast<AZ::ComponentApplication::Descriptor*>(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<AZStd::string>().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::LocalFileIO>();
AZ::IO::FileIOBase::SetDirectInstance(m_directFileIO.get());
}
// Initializes the IArchive for reading archive(.pak) files
if (auto archive = AZ::Interface<AZ::IO::IArchive>::Get(); !archive)
{
m_archive = AZStd::make_unique<AZ::IO::Archive>();
AZ::Interface<AZ::IO::IArchive>::Register(m_archive.get());
}
// Set the ArchiveFileIO as the default FileIOBase instance
if (AZ::IO::FileIOBase::GetInstance() == nullptr)
{
m_archiveFileIO = AZStd::make_unique<AZ::IO::ArchiveFileIO>(m_archive.get());
AZ::IO::FileIOBase::SetInstance(m_archiveFileIO.get());
SetFileIOAliases();
}
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();
// 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<AZ::IO::IArchive>::Get() == m_archive.get())
{
AZ::Interface<AZ::IO::IArchive>::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()
{
// Calculate the engine root by reading the engine.json file
AZStd::string engineJsonPath = AZStd::string_view{ m_engineRoot };
engineJsonPath += s_engineConfigFileName;
AzFramework::StringFunc::Path::Normalize(engineJsonPath);
AZ::IO::LocalFileIO localFileIO;
auto readJsonResult = AzFramework::FileFunc::ReadJsonFile(engineJsonPath, &localFileIO);
if (readJsonResult.IsSuccess())
{
SetRootPath(RootPathType::EngineRoot, m_engineRoot.c_str());
AZ_TracePrintf(s_azFrameworkWarningWindow, "Engine Path: %s\n", m_engineRoot.c_str());
}
else
{
// If there is any problem reading the engine.json file, then default to engine root to the app root
AZ_Warning(s_azFrameworkWarningWindow, false, "Unable to read engine.json file '%s' (%s). Defaulting the engine root to '%s'", engineJsonPath.c_str(), readJsonResult.GetError().c_str(), m_appRoot.c_str());
SetRootPath(RootPathType::EngineRoot, m_appRoot.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<NetworkContext>();
// 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<AZ::Uuid> componentUuidsForMetricsCollection
{
azrtti_typeid<AZ::MemoryComponent>(),
azrtti_typeid<AZ::StreamerComponent>(),
azrtti_typeid<AZ::JobManagerComponent>(),
azrtti_typeid<AZ::AssetManagerComponent>(),
azrtti_typeid<AZ::UserSettingsComponent>(),
azrtti_typeid<AZ::Debug::FrameProfilerComponent>(),
azrtti_typeid<AZ::NativeUI::NativeUISystemComponent>(),
azrtti_typeid<AZ::SliceComponent>(),
azrtti_typeid<AZ::SliceSystemComponent>(),
azrtti_typeid<AzFramework::AssetCatalogComponent>(),
azrtti_typeid<AzFramework::CustomAssetTypeComponent>(),
azrtti_typeid<AzFramework::FileTag::ExcludeFileComponent>(),
azrtti_typeid<AzFramework::NetBindingComponent>(),
azrtti_typeid<AzFramework::NetBindingSystemComponent>(),
azrtti_typeid<AzFramework::TransformComponent>(),
azrtti_typeid<AzFramework::SceneSystemComponent>(),
azrtti_typeid<AzFramework::AzFrameworkConfigurationSystemComponent>(),
azrtti_typeid<AzFramework::GameEntityContextComponent>(),
#if !defined(_RELEASE)
azrtti_typeid<AzFramework::TargetManagementComponent>(),
#endif
azrtti_typeid<AzFramework::AssetSystem::AssetSystemComponent>(),
azrtti_typeid<AzFramework::InputSystemComponent>(),
azrtti_typeid<AzFramework::DrillerNetworkAgentComponent>(),
#if !defined(AZCORE_EXCLUDE_LUA)
azrtti_typeid<AZ::ScriptSystemComponent>(),
azrtti_typeid<AzFramework::ScriptComponent>(),
#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<AZ::SerializeContext*>(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<AZ::MemoryComponent>(),
azrtti_typeid<AZ::StreamerComponent>(),
azrtti_typeid<AZ::AssetManagerComponent>(),
azrtti_typeid<AZ::UserSettingsComponent>(),
azrtti_typeid<AZ::ScriptSystemComponent>(),
azrtti_typeid<AZ::JobManagerComponent>(),
azrtti_typeid<AZ::NativeUI::NativeUISystemComponent>(),
azrtti_typeid<AZ::SliceSystemComponent>(),
azrtti_typeid<AzFramework::AssetCatalogComponent>(),
azrtti_typeid<AzFramework::CustomAssetTypeComponent>(),
azrtti_typeid<AzFramework::FileTag::ExcludeFileComponent>(),
azrtti_typeid<AzFramework::SceneSystemComponent>(),
azrtti_typeid<AzFramework::AzFrameworkConfigurationSystemComponent>(),
azrtti_typeid<AzFramework::GameEntityContextComponent>(),
azrtti_typeid<AzFramework::RenderGeometry::GameIntersectorComponent>(),
azrtti_typeid<AzFramework::AssetSystem::AssetSystemComponent>(),
azrtti_typeid<AzFramework::InputSystemComponent>(),
azrtti_typeid<AzFramework::DrillerNetworkAgentComponent>(),
azrtti_typeid<AzFramework::StreamingInstall::StreamingInstallSystemComponent>(),
azrtti_typeid<AzFramework::SpawnableSystemComponent>(),
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<AZStd::string>().c_str());
}
}
return component;
}
void Application::CreateStaticModules(AZStd::vector<AZ::Module*>& 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<NetworkContext>();
}
////////////////////////////////////////////////////////////////////////////
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<NetworkContext>();
}
return result;
}
void Application::ResolveEnginePath(AZStd::string& engineRelativePath) const
{
AZStd::string fullPath = AZStd::string(m_engineRoot) + AZStd::string(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING) + engineRelativePath;
engineRelativePath = fullPath;
}
void Application::CalculateBranchTokenForEngineRoot(AZStd::string& token) const
{
AzFramework::StringFunc::AssetPath::CalculateBranchToken(AZStd::string(m_engineRoot), 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<void()>& 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)
{
size_t sourceLen = strlen(source);
constexpr AZStd::string_view pathSeparators{ AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR };
// Determine if we need to append a trailing path separator
bool appendTrailingPathSep = sourceLen > 0 && pathSeparators.find_first_of(source[sourceLen - 1]) == AZStd::string_view::npos;
// 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.max_size(), "String overflow for App Root: %s", source);
m_appRoot = source;
AZStd::replace(std::begin(m_appRoot), std::end(m_appRoot), AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
if (appendTrailingPathSep)
{
m_appRoot.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
}
}
break;
case RootPathType::EngineRoot:
{
AZ_Assert(sourceLen < m_engineRoot.max_size(), "String overflow for Engine Root: %s", source);
m_engineRoot = source;
AZStd::replace(std::begin(m_engineRoot), std::end(m_engineRoot), AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
if (appendTrailingPathSep)
{
m_engineRoot.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
}
}
break;
default:
AZ_Assert(false, "Invalid RootPathType (%d)", static_cast<int>(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