Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,900 @@
/*
* 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 "native/utilities/ApplicationManager.h"
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Logging/LoggingComponent.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include "native/resourcecompiler/RCBuilder.h"
#include <QLocale>
#include <QTranslator>
#include <QCommandLineParser>
#include <QSettings>
#include <AzFramework/Asset/AssetCatalogComponent.h>
#include <AzToolsFramework/Entity/EditorEntityFixupComponent.h>
#include <AzToolsFramework/ToolsComponents/ToolsAssetCatalogComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
namespace AssetProcessor
{
void MessageHandler(QtMsgType type, [[maybe_unused]] const QMessageLogContext& context, [[maybe_unused]] const QString& msg)
{
switch (type)
{
case QtDebugMsg:
AZ_TracePrintf(AssetProcessor::DebugChannel, "Qt-Debug: %s (%s:%u, %s)\n", msg.toUtf8().constData(), context.file, context.line, context.function);
break;
case QtWarningMsg:
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Qt-Warning: %s (%s:%u, %s)\n", msg.toUtf8().constData(), context.file, context.line, context.function);
break;
case QtCriticalMsg:
AZ_Warning(AssetProcessor::ConsoleChannel, false, "Qt-Critical: %s (%s:%u, %s)\n", msg.toUtf8().constData(), context.file, context.line, context.function);
break;
case QtFatalMsg:
AZ_Error(AssetProcessor::ConsoleChannel, false, "Qt-Fatal: %s (%s:%u, %s)\n", msg.toUtf8().constData(), context.file, context.line, context.function);
abort();
}
}
//! we filter the main app logs to only include non-job-thread messages:
class FilteredLogComponent
: public AzFramework::LogComponent
{
public:
void OutputMessage(AzFramework::LogFile::SeverityLevel severity, const char* window, const char* message) override
{
// if we receive an exception it means we are likely to crash. in that case, even if it occurred in a job thread
// it occurred in THIS PROCESS, which will now die. So we log these even in the case of them happening in a job thread.
if ((m_inException)||(severity == AzFramework::LogFile::SEV_EXCEPTION))
{
if (!m_inException)
{
m_inException = true; // from this point on, consume all messages regardless of what severity they are.
AZ::Debug::Trace::HandleExceptions(false);
}
AzFramework::LogComponent::OutputMessage(AzFramework::LogFile::SEV_EXCEPTION, ConsoleChannel, message);
// note that OutputMessage will only output to the log, we want this kind of info to make its way into the
// regular stderr too
fprintf(stderr, "Exception log: %s - %s", window, message);
fflush(stderr);
return;
}
if (AssetProcessor::GetThreadLocalJobId() != 0)
{
// we are in a job thread - return early to make it so that the global log file does not get this message
// there will also be a log listener in the actual job log thread which will get the message too, and that one
// will write it to the individual log.
return;
}
AzFramework::LogComponent::OutputMessage(severity, window, message);
}
protected:
bool m_inException = false;
};
}
uint qHash(const AZ::Uuid& key, uint seed)
{
(void) seed;
return azlossy_caster(AZStd::hash<AZ::Uuid>()(key));
}
namespace AssetProcessorBuildTarget
{
// Added a declaration of GetBuiderTargetName which retrieves the name of the build target
// The build target changes depending on which shared library/executable ApplicationManager.cpp
// is linked to
AZStd::string_view GetBuildTargetName();
}
AssetProcessorAZApplication::AssetProcessorAZApplication(int* argc, char*** argv, QObject* parent)
: QObject(parent)
, AzToolsFramework::ToolsApplication(argc, argv)
{
// The settings registry has been created at this point, so add the CMake target
// specialization to the settings
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
*AZ::SettingsRegistry::Get(), AssetProcessorBuildTarget::GetBuildTargetName());
// Adding the PreModuleLoad event to the AssetProcessor application for logging when a gem loads
m_preModuleLoadHandler = AZ::ModuleManagerRequests::PreModuleLoadEvent::Handler{ []([[maybe_unused]] AZStd::string_view modulePath)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Loading (Gem) Module '%.*s'...\n", aznumeric_cast<int>(modulePath.size()), modulePath.data());
} };
m_preModuleLoadHandler.Connect(m_moduleManager->m_preModuleLoadEvent);
// Override the /Amazon/AzCore/Bootstrap/sys_game_folder entry in the Settings Registry using the -gamefolder parameter
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (m_commandLine.GetNumSwitchValues("gamefolder") > 0)
{
const AZStd::string& gameFolderOverride = m_commandLine.GetSwitchValue("gamefolder", 0);
auto gameFolderCommandLineOverride = AZStd::string::format("--regset=%s/sys_game_folder=%s", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey,
gameFolderOverride.c_str());
AZ::CommandLine::ParamContainer commandLineArgs;
m_commandLine.Dump(commandLineArgs);
commandLineArgs.emplace_back(gameFolderCommandLineOverride);
m_commandLine.Parse(commandLineArgs);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*settingsRegistry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry);
}
}
AZ::ComponentTypeList AssetProcessorAZApplication::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList components = AzFramework::Application::GetRequiredSystemComponents();
for (auto iter = components.begin(); iter != components.end();)
{
if (*iter == azrtti_typeid<AzFramework::AssetSystem::AssetSystemComponent>() // AP does not need asset system component to handle AssetRequestBus calls
|| *iter == azrtti_typeid<AzFramework::AssetCatalogComponent>() // AP will use its own AssetCatalogComponent
|| *iter == AZ::Uuid("{624a7be2-3c7e-4119-aee2-1db2bdb6cc89}") // ScriptDebugAgent
|| *iter == AZ::Uuid("{CAF3A025-FAC9-4537-B99E-0A800A9326DF}") // InputSystemComponent
|| *iter == azrtti_typeid<AssetProcessor::ToolsAssetCatalogComponent>()
)
{
// AP does not require the above components to be active
iter = components.erase(iter);
}
else
{
++iter;
}
}
components.push_back(azrtti_typeid<AzToolsFramework::PerforceComponent>());
return components;
}
void AssetProcessorAZApplication::RegisterCoreComponents()
{
AzToolsFramework::ToolsApplication::RegisterCoreComponents();
RegisterComponentDescriptor(AzToolsFramework::EditorEntityFixupComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::AssetBrowser::AssetBrowserComponent::CreateDescriptor());
}
void AssetProcessorAZApplication::ResolveModulePath(AZ::OSString& modulePath)
{
AssetProcessor::AssetProcessorStatusEntry entry(AssetProcessor::AssetProcessorStatus::Initializing_Gems, 0, QString(modulePath.c_str()));
Q_EMIT AssetProcessorStatus(entry);
AzFramework::Application::ResolveModulePath(modulePath);
}
void AssetProcessorAZApplication::SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations)
{
AzToolsFramework::ToolsApplication::SetSettingsRegistrySpecializations(specializations);
specializations.Append("assetprocessor");
}
ApplicationManager::ApplicationManager(int* argc, char*** argv, QObject* parent)
: QObject(parent)
, m_frameworkApp(argc, argv)
{
qInstallMessageHandler(&AssetProcessor::MessageHandler);
}
ApplicationManager::~ApplicationManager()
{
// if any of the threads are running, destroy them
// Any QObjects that have these ThreadWorker as parent will also be deleted
if (m_runningThreads.size())
{
for (int idx = 0; idx < m_runningThreads.size(); idx++)
{
m_runningThreads.at(idx)->Destroy();
}
}
for (int idx = 0; idx < m_appDependencies.size(); idx++)
{
delete m_appDependencies[idx];
}
qInstallMessageHandler(nullptr);
//deleting QCoreApplication/QApplication
delete m_qApp;
if (m_entity)
{
//Deactivate all the components
m_entity->Deactivate();
delete m_entity;
m_entity = nullptr;
}
//Unregistering and deleting all the components
EBUS_EVENT_ID(azrtti_typeid<AzFramework::LogComponent>(), AZ::ComponentDescriptorBus, ReleaseDescriptor);
//Stop AZFramework
m_frameworkApp.Stop();
AZ::Debug::Trace::HandleExceptions(false);
}
bool ApplicationManager::InitiatedShutdown() const
{
return m_duringShutdown;
}
void ApplicationManager::GetExternalBuilderFileList(QStringList& externalBuilderModules)
{
externalBuilderModules.clear();
static const char* builder_folder_name = "Builders";
// LY_ASSET_BUILDERS is defined by the CMakeLists.txt. The asset builders add themselves to a variable that
// is populated to allow selective building of those asset builder targets.
// This allows left over Asset builders in the output directory to not be loaded by the AssetProcessor
#if !defined(LY_ASSET_BUILDERS)
#error LY_ASSET_BUILDERS was not defined for ApplicationManager.cpp
#endif
QDir builderDir = QDir::toNativeSeparators(QString(this->m_frameworkApp.GetExecutableFolder()));
builderDir.cd(QString(builder_folder_name));
if (builderDir.exists())
{
AZStd::vector<AZStd::string> tokens;
AZ::StringFunc::Tokenize(AZStd::string_view(LY_ASSET_BUILDERS), tokens, ',');
AZStd::string builderLibrary;
for (const AZStd::string& token : tokens)
{
QString assetBuilderPath(token.c_str());
if (builderDir.exists(assetBuilderPath))
{
externalBuilderModules.push_back(builderDir.absoluteFilePath(assetBuilderPath));
}
}
}
if (externalBuilderModules.empty())
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "AssetProcessor was unable to locate any builders\n");
}
}
QDir ApplicationManager::GetSystemRoot() const
{
return m_systemRoot;
}
QString ApplicationManager::GetGameName() const
{
return m_gameName;
}
QCoreApplication* ApplicationManager::GetQtApplication()
{
return m_qApp;
}
void ApplicationManager::RegisterObjectForQuit(QObject* source, bool insertInFront)
{
Q_ASSERT(!m_duringShutdown);
if (m_duringShutdown)
{
AZ_Warning(AssetProcessor::DebugChannel, false, "You may not register objects for quit during shutdown.\n");
return;
}
QuitPair quitPair(source, false);
if (!m_objectsToNotify.contains(quitPair))
{
if (insertInFront)
{
m_objectsToNotify.push_front(quitPair);
}
else
{
m_objectsToNotify.push_back(quitPair);
}
if (!connect(source, SIGNAL(ReadyToQuit(QObject*)), this, SLOT(ReadyToQuit(QObject*))))
{
AZ_Warning(AssetProcessor::DebugChannel, false, "ApplicationManager::RegisterObjectForQuit was passed an object of type %s which has no ReadyToQuit(QObject*) signal.\n", source->metaObject()->className());
}
connect(source, SIGNAL(destroyed(QObject*)), this, SLOT(ObjectDestroyed(QObject*)));
}
}
void ApplicationManager::ObjectDestroyed(QObject* source)
{
for (int notifyIdx = 0; notifyIdx < m_objectsToNotify.size(); ++notifyIdx)
{
if (m_objectsToNotify[notifyIdx].first == source)
{
m_objectsToNotify.erase(m_objectsToNotify.begin() + notifyIdx);
if (m_duringShutdown)
{
if (!m_queuedCheckQuit)
{
QTimer::singleShot(0, this, SLOT(CheckQuit()));
m_queuedCheckQuit = true;
}
}
return;
}
}
}
void ApplicationManager::QuitRequested()
{
if (m_duringShutdown)
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "QuitRequested() - already during shutdown\n");
return;
}
if (m_duringStartup)
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "QuitRequested() - during startup - waiting\n");
// if we're still starting up, spin until we're ready to shut down.
QMetaObject::invokeMethod(this, "QuitRequested", Qt::QueuedConnection);
return;
}
AZ_TracePrintf(AssetProcessor::DebugChannel, "QuitRequested() - ready!\n");
m_duringShutdown = true;
//Inform all the builders to shutdown
EBUS_EVENT(AssetBuilderSDK::AssetBuilderCommandBus, ShutDown);
// the following call will invoke on the main thread of the application, since its a direct bus call.
EBUS_EVENT(AssetProcessor::ApplicationManagerNotifications::Bus, ApplicationShutdownRequested);
// while it may be tempting to just collapse all of this to a bus call, Qt Objects have the advantage of being
// able to automatically queue calls onto their own thread, and a lot of these involved objects are in fact
// on their own threads. So even if we used a bus call we'd ultimately still have to invoke a queued
// call there anyway.
for (const QuitPair& quitter : m_objectsToNotify)
{
if (!quitter.second)
{
QMetaObject::invokeMethod(quitter.first, "QuitRequested", Qt::QueuedConnection);
}
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "App quit requested %d listeners notified.\n", m_objectsToNotify.size());
if (!m_queuedCheckQuit)
{
QTimer::singleShot(0, this, SLOT(CheckQuit()));
m_queuedCheckQuit = true;
}
}
void ApplicationManager::CheckQuit()
{
m_queuedCheckQuit = false;
for (const QuitPair& quitter : m_objectsToNotify)
{
if (!quitter.second)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "App Quit: Object of type %s is not yet ready to quit.\n", quitter.first->metaObject()->className());
return;
}
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "App quit requested, and all objects are ready. Quitting app.\n");
// We will now loop over all the running threads and destroy them
// Any QObjects that have these ThreadWorker as parent will also be deleted
for (int idx = 0; idx < m_runningThreads.size(); idx++)
{
m_runningThreads.at(idx)->Destroy();
}
m_runningThreads.clear();
// all good.
qApp->quit();
}
void ApplicationManager::CheckForUpdate()
{
for (int idx = 0; idx < m_appDependencies.size(); ++idx)
{
ApplicationDependencyInfo* fileDependencyInfo = m_appDependencies[idx];
QString fileName = fileDependencyInfo->FileName();
QFileInfo fileInfo(fileName);
if (fileInfo.exists())
{
QDateTime fileLastModifiedTime = fileInfo.lastModified();
bool hasTimestampChanged = (fileDependencyInfo->Timestamp() != fileLastModifiedTime);
if (hasTimestampChanged)
{
QuitRequested();
}
}
else
{
// if one of the files is not present we construct a null datetime for it and
// continue checking
fileDependencyInfo->SetTimestamp(QDateTime());
}
}
}
void ApplicationManager::PopulateApplicationDependencies()
{
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(CheckForUpdate()));
m_updateTimer.start(5000);
QString currentDir(QCoreApplication::applicationDirPath());
QDir dir(currentDir);
QString applicationPath = QCoreApplication::applicationFilePath();
m_filesOfInterest.push_back(applicationPath);
// add some known-dependent files (this can be removed when they are no longer a dependency)
// Note that its not necessary for any of these files to actually exist. It is considered a "change" if they
// change their file modtime, or if they go from existing to not existing, or if they go from not existing, to existing.
// any of those should cause AP to drop.
for (const QString& pathName : { "CrySystem",
"SceneCore", "SceneData",
"FbxSceneBuilder", "AzQtComponents"
})
{
QString pathWithPlatformExtension = pathName + QString(AZ_DYNAMIC_LIBRARY_EXTENSION);
m_filesOfInterest.push_back(dir.absoluteFilePath(pathWithPlatformExtension));
}
// Get the external builder modules to add to the files of interest
QStringList builderModuleFileList;
GetExternalBuilderFileList(builderModuleFileList);
for (const QString& builderModuleFile : builderModuleFileList)
{
m_filesOfInterest.push_back(builderModuleFile);
}
QDir assetRoot;
AssetUtilities::ComputeAssetRoot(assetRoot);
QString globalConfigPath = assetRoot.filePath("AssetProcessorPlatformConfig.ini");
m_filesOfInterest.push_back(globalConfigPath);
QString gameName = AssetUtilities::ComputeGameName();
QString gamePlatformConfigPath = assetRoot.filePath(gameName + "/AssetProcessorGamePlatformConfig.ini");
m_filesOfInterest.push_back(gamePlatformConfigPath);
// add app modules
AZ::ModuleManagerRequestBus::Broadcast(&AZ::ModuleManagerRequestBus::Events::EnumerateModules,
[this](const AZ::ModuleData& moduleData)
{
AZ::DynamicModuleHandle* handle = moduleData.GetDynamicModuleHandle();
if (handle)
{
QFileInfo fi(handle->GetFilename().c_str());
if (fi.exists())
{
m_filesOfInterest.push_back(fi.absoluteFilePath());
}
}
return true; // keep iterating.
});
//find timestamps of all the files
for (int idx = 0; idx < m_filesOfInterest.size(); idx++)
{
QString fileName = m_filesOfInterest.at(idx);
QFileInfo fileInfo(fileName);
QDateTime fileLastModifiedTime = fileInfo.lastModified();
ApplicationDependencyInfo* applicationDependencyInfo = new ApplicationDependencyInfo(fileName, fileLastModifiedTime);
// if some file does not exist than null datetime will be stored
m_appDependencies.push_back(applicationDependencyInfo);
}
}
bool ApplicationManager::StartAZFramework(QString appRootOverride)
{
AzFramework::Application::Descriptor appDescriptor;
AZ::ComponentApplication::StartupParameters params;
QString gameName = AssetUtilities::ComputeGameName();
AZ::SettingsRegistryInterface& registry = *AZ::SettingsRegistry::Get();
// Add the supplied gameName as specialization key in the registry
if (!gameName.isEmpty())
{
auto gameNameSpecialization = QString("%1/%2").arg(AZ::SettingsRegistryMergeUtils::SpecializationsRootKey).arg(gameName);
QByteArray specializationByteArray = gameNameSpecialization.toUtf8();
registry.Set(AZStd::string_view(specializationByteArray.data(), specializationByteArray.size()), true);
}
else
{
// Add the project name as a registry specialization
auto projectKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/sys_game_folder", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey);
if (AZ::SettingsRegistryInterface::FixedValueString bootstrapProjectName; registry.Get(bootstrapProjectName, projectKey) && !bootstrapProjectName.empty())
{
registry.Set(AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::SpecializationsRootKey, bootstrapProjectName.c_str()),
true);
}
}
// The application will live in a bin folder one level up from the app root.
static char s_storageForRootPath[AZ_MAX_PATH_LEN] = { 0 };
if (!appRootOverride.isEmpty())
{
azstrcpy(s_storageForRootPath, AZ_MAX_PATH_LEN, appRootOverride.toUtf8().data());
params.m_appRootOverride = s_storageForRootPath;
}
else
{
params.m_appRootOverride = nullptr;
}
// Prevent loading of gems in the Create method of the ComponentApplication
params.m_loadDynamicModules = false;
// Prevent script reflection warnings from bringing down the AssetProcessor
appDescriptor.m_enableScriptReflection = false;
// start listening for exceptions occurring so if something goes wrong we have at least SOME output...
AZ::Debug::Trace::HandleExceptions(true);
m_frameworkApp.Start(appDescriptor, params);
//Registering all the Components
m_frameworkApp.RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor());
Reflect();
QDir engineRoot;
AssetUtilities::ComputeEngineRoot(engineRoot);
AZ::IO::FileIOBase::GetInstance()->SetAlias("@devroot@", engineRoot.absolutePath().toUtf8().data());
const AzFramework::CommandLine* commandLine = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
if (commandLine && commandLine->HasSwitch("logDir"))
{
AZ::IO::FileIOBase::GetInstance()->SetAlias("@log@", commandLine->GetSwitchValue("logDir", 0).c_str());
}
else
{
char executableDirectory[AZ_MAX_PATH_LEN];
if (AZ::Utils::GetExecutableDirectory(executableDirectory, AZStd::size(executableDirectory)) == AZ::Utils::ExecutablePathResult::Success)
{
AZ::IO::FileIOBase::GetInstance()->SetAlias("@log@", executableDirectory);
}
}
m_entity = aznew AZ::Entity("Application Entity");
if (m_entity)
{
AssetProcessor::FilteredLogComponent* logger = aznew AssetProcessor::FilteredLogComponent();
m_entity->AddComponent(logger);
if (logger)
{
// Prevent files overwriting each other if you run batch at same time as GUI (unit tests, for example)
logger->SetLogFileBaseName(GetLogBaseName());
}
//Activate all the components
m_entity->Init();
m_entity->Activate();
return true;
}
else
{
//aznew failed
return false;
}
}
bool ApplicationManager::ActivateModules()
{
// we load the editor xml for our modules since it contains the list of gems we need for tools to function (not just runtime)
connect(&m_frameworkApp, &AssetProcessorAZApplication::AssetProcessorStatus, this,
[this](AssetProcessor::AssetProcessorStatusEntry entry)
{
Q_EMIT AssetProcessorStatusChanged(entry);
QCoreApplication::processEvents(QEventLoop::AllEvents);
});
QDir assetRoot;
if (!AssetUtilities::ComputeAssetRoot(assetRoot))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Cannot compute the asset root folder. Is AssetProcessor being run from the appropriate folder?");
return false;
}
assetRoot.cd(AssetUtilities::ComputeGameName());
m_frameworkApp.LoadDynamicModules();
return true;
}
void ApplicationManager::addRunningThread(AssetProcessor::ThreadWorker* thread)
{
m_runningThreads.push_back(thread);
}
QString ApplicationManager::ParseOptionAppRootArgument()
{
AZ_Assert(m_qApp!=nullptr,"m_qApp not initialized. QT application must be created before this call.")
// Parse any parameters.
static const char* app_root_parameter = "app-root";
static const char* app_root_parameter_desc = "Optional external path outside of the current engine to set as the application root.";
QCommandLineOption appRootPathOption(QString(app_root_parameter), tr(app_root_parameter_desc), QString("path"));
QCommandLineParser parser;
parser.setApplicationDescription("Asset Processor");
parser.addOption(appRootPathOption);
parser.parse(m_qApp->arguments());
QString appRootArgValue = parser.value(appRootPathOption);
appRootArgValue.remove(QChar('\"'));
return appRootArgValue.trimmed();
}
bool ApplicationManager::ValidateExternalAppRoot(QString appRootPath) const
{
static const char* bootstrap_cfg_name = "bootstrap.cfg";
QDir testAppRootPath(appRootPath);
// Make sure the path exists
if (!testAppRootPath.exists())
{
AZ_Warning(AssetProcessor::ConsoleChannel, testAppRootPath.exists(), "Invalid Application Root path override (--app-root): %s. Directory does not exist.\n", appRootPath.toUtf8().data());
return false;
}
// Make sure the path contains bootstrap.cfg
if (!testAppRootPath.exists(bootstrap_cfg_name))
{
AZ_Warning(AssetProcessor::ConsoleChannel, testAppRootPath.exists(), "Invalid Application Root path override (--app-root): %s. Directory does not contain %s.\n", appRootPath.toUtf8().data(), bootstrap_cfg_name);
return false;
}
// Make sure we can read the 'sys_game_folder' settings from bootstrap.cfg
QSettings settings(testAppRootPath.absoluteFilePath(bootstrap_cfg_name), QSettings::Format::IniFormat);
static const char* sysGameFolderKeyName = "sys_game_folder";
auto sysGameFolderSettings = settings.value(sysGameFolderKeyName);
if (!sysGameFolderSettings.isValid() || sysGameFolderSettings.isNull())
{
AZ_Warning(AssetProcessor::ConsoleChannel, testAppRootPath.exists(), "Invalid Application Root path override (--app-root): %s. %s in the path is not valid.\n", appRootPath.toUtf8().data(), bootstrap_cfg_name);
return false;
}
// Make sure the 'sys_game_folder' value in the external bootstrap.cfg points to a valid subfolder in that path
QString sysGameFolder = sysGameFolderSettings.toString();
QDir gameFolderPath(appRootPath);
if (!gameFolderPath.cd(sysGameFolder))
{
AZ_Warning(AssetProcessor::ConsoleChannel, testAppRootPath.exists(), "Invalid Application Root path override (--app-root): %s. Configured Game folder %s in the path is not valid.\n", appRootPath.toUtf8().data(), sysGameFolder.toUtf8().data());
return false;
}
return true;
}
ApplicationManager::BeforeRunStatus ApplicationManager::BeforeRun()
{
// Create the Qt Application
CreateQtApplication();
// Calculate the override app root path if provided and validate it before passing it along
QString overrideAppRootPath = ParseOptionAppRootArgument();
if (!overrideAppRootPath.isEmpty())
{
if (ValidateExternalAppRoot(overrideAppRootPath))
{
QDir overrideAppRoot(overrideAppRootPath);
QDir resultAppRoot;
AssetUtilities::ComputeAssetRoot(resultAppRoot, &overrideAppRoot);
}
else
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Invalid override app root folder '%s'.", overrideAppRootPath.toUtf8().data());
return ApplicationManager::BeforeRunStatus::Status_Failure;
}
}
if (!StartAZFramework(overrideAppRootPath))
{
return ApplicationManager::BeforeRunStatus::Status_Failure;
}
if (!AssetUtilities::ComputeEngineRoot(m_systemRoot))
{
return ApplicationManager::BeforeRunStatus::Status_Failure;
}
if (!AssetUtilities::UpdateBranchToken())
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Asset Processor was unable to open the bootstrap file and verify/update the branch token. \
Please ensure that the bootstrap.cfg file is present and not locked by any other program.\n");
return ApplicationManager::BeforeRunStatus::Status_Failure;
}
return ApplicationManager::BeforeRunStatus::Status_Success;
}
bool ApplicationManager::Activate()
{
if (!AssetUtilities::ComputeAssetRoot(m_systemRoot))
{
return false;
}
m_gameName = AssetUtilities::ComputeGameName();
if (m_gameName.isEmpty())
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Unable to detect name of current game project. Is bootstrap.cfg appropriately configured?");
return false;
}
// the following controls what registry keys (or on mac or linux what entries in home folder) are used
// so they should not be translated!
qApp->setOrganizationName(GetOrganizationName());
qApp->setOrganizationDomain("amazon.com");
qApp->setApplicationName(GetApplicationName());
return true;
}
QString ApplicationManager::GetOrganizationName() const
{
return "Amazon";
}
QString ApplicationManager::GetApplicationName() const
{
return "Asset Processor";
}
bool ApplicationManager::PostActivate()
{
return true;
}
bool ApplicationManager::NeedRestart() const
{
return m_needRestart;
}
void ApplicationManager::Restart()
{
if (m_needRestart)
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Restart() - already restarting\n");
return;
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "AssetProcessor is restarting.\n");
m_needRestart = true;
m_updateTimer.stop();
QuitRequested();
}
void ApplicationManager::ReadyToQuit(QObject* source)
{
if (!source)
{
return;
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "App Quit Object of type %s indicates it is ready.\n", source->metaObject()->className());
for (int notifyIdx = 0; notifyIdx < m_objectsToNotify.size(); ++notifyIdx)
{
if (m_objectsToNotify[notifyIdx].first == source)
{
// replace it.
m_objectsToNotify[notifyIdx] = QuitPair(m_objectsToNotify[notifyIdx].first, true);
}
}
if (!m_queuedCheckQuit)
{
QTimer::singleShot(0, this, SLOT(CheckQuit()));
m_queuedCheckQuit = true;
}
}
void ApplicationManager::RegisterComponentDescriptor(const AZ::ComponentDescriptor* descriptor)
{
AZ_Assert(descriptor, "descriptor cannot be null");
this->m_frameworkApp.RegisterComponentDescriptor(descriptor);
}
ApplicationManager::RegistryCheckInstructions ApplicationManager::CheckForRegistryProblems(QWidget* /*parentWidget*/, [[maybe_unused]] bool showPopupMessage)
{
#if defined(AZ_PLATFORM_WINDOWS)
// There's a bug that prevents rc.exe from closing properly, making it appear
// that jobs never complete. The issue is that Windows sometimes decides to put
// an exe into a special compatibility mode and tells FreeLibrary calls to stop
// doing anything. Once the registry entry for this is written, it never gets
// removed unless the user goes and does it manually in RegEdit.
// To prevent this from being a problem, we check for that registry key
// and tell the user to remove it.
// Here's a link with the same problem reported: https://social.msdn.microsoft.com/Forums/vstudio/en-US/3abe477b-ba6f-49d2-894f-efd42165e620/why-windows-generates-an-ignorefreelibrary-entry-in-appcompatflagslayers-registry-?forum=windowscompatibility
// Here's a link to someone else with the same problem mentioning the problem registry key: https://software.intel.com/en-us/forums/intel-visual-fortran-compiler-for-windows/topic/606006
QString compatibilityRegistryGroupName = "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers";
QSettings settings(compatibilityRegistryGroupName, QSettings::NativeFormat);
auto keys = settings.childKeys();
for (auto key : keys)
{
if (key.contains("rc.exe", Qt::CaseInsensitive))
{
// Windows will allow us to see that there is an entry, but won't allow us to
// read the entry or to modify it, so we'll have to warn the user instead
// Qt displays the key with the slashes flipped; flip them back because we're on windows
QString windowsFriendlyRegPath = key.replace('/', '\\');
QString warningText = QObject::tr(
"The AssetProcessor will not function correctly with certain registry settings. To correct the problem, please:\n"
"1) Open RegEdit\n"
"2) When Windows asks if you'd like to allow the app to make changes to your device, click \"Yes\"\n"
"3) Open the registry group for the path %0\n"
"4) Delete the key for %1\n"
"5) %2"
).arg(compatibilityRegistryGroupName, windowsFriendlyRegPath);
if (showPopupMessage)
{
return PopupRegistryProblemsMessage(warningText);
}
else
{
warningText = warningText.arg(tr("Restart the Asset Processor"));
QByteArray warningUtf8 = warningText.toUtf8();
AZ_TracePrintf(AssetProcessor::ConsoleChannel, warningUtf8.data());
}
return RegistryCheckInstructions::Exit;
}
}
#endif
return RegistryCheckInstructions::Continue;
}
QDateTime ApplicationDependencyInfo::Timestamp() const
{
return m_timestamp;
}
void ApplicationDependencyInfo::SetTimestamp(const QDateTime& timestamp)
{
m_timestamp = timestamp;
}
QString ApplicationDependencyInfo::FileName() const
{
return m_fileName;
}
void ApplicationDependencyInfo::SetFileName(QString fileName)
{
m_fileName = fileName;
}
@@ -0,0 +1,204 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <type_traits>
#include <QList>
#include <QString>
#include <QObject>
#include <QDir>
#include <QTimer>
#include <QDateTime>
#include "native/assetprocessor.h"
#endif
class FolderWatchCallbackEx;
class QCoreApplication;
namespace AZ
{
class Entity;
}
namespace AssetProcessor
{
class ThreadWorker;
}
class AssetProcessorAZApplication
: public QObject
, public AzToolsFramework::ToolsApplication
{
Q_OBJECT
public:
explicit AssetProcessorAZApplication(int* argc, char*** argv, QObject* parent = nullptr);
~AssetProcessorAZApplication() override = default;
/////////////////////////////////////////////////////////
//// AzFramework::Application overrides
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
void RegisterCoreComponents() override;
void ResolveModulePath(AZ::OSString& modulePath) override;
void SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations) override;
///////////////////////////////////////////////////////////
Q_SIGNALS:
void AssetProcessorStatus(AssetProcessor::AssetProcessorStatusEntry entry);
private:
AZ::ModuleManagerRequests::PreModuleLoadEvent::Handler m_preModuleLoadHandler;
};
struct ApplicationDependencyInfo;
//This global function is required, if we want to use uuid as a key in a QSet
uint qHash(const AZ::Uuid& key, uint seed = 0);
//! This class allows you to register any number of objects to it
//! and when quit is requested, it will send a signal "QuitRequested()" to the registered object.
//! (You must implement this slot in your object!)
//! It will then expect each of those objects to send it the "ReadyToQuit(QObject*)" message when its ready
//! once every object is ready, qApp() will be told to quit.
//! the QObject parameter is the object that was originally registered and serves as the handle.
//! if your registered object is destroyed, it will automatically remove it from the list for you, no need to
//! unregister.
class ApplicationManager
: public QObject
{
Q_OBJECT
public:
//! This enum is used by the BeforeRun method and is useful in deciding whether we can run the application
//! or whether we need to exit the application either because of an error or because we are restarting
enum BeforeRunStatus
{
Status_Success = 0,
Status_Restarting,
Status_Failure,
};
explicit ApplicationManager(int* argc, char*** argv, QObject* parent = 0);
virtual ~ApplicationManager();
//! Prepares all the prerequisite needed for the main application functionality
//! For eg Starts the AZ Framework,Activates logging ,Initialize Qt etc
//! This method can return the following states success,failure and restarting.The latter two will cause the application to exit.
virtual ApplicationManager::BeforeRunStatus BeforeRun();
//! This method actually runs the main functionality of the application ,if BeforeRun method succeeds
virtual bool Run() = 0;
//! Returns a pointer to the QCoreApplication
QCoreApplication* GetQtApplication();
QDir GetSystemRoot() const;
QString GetGameName() const;
void RegisterComponentDescriptor(const AZ::ComponentDescriptor* descriptor);
enum class RegistryCheckInstructions
{
Continue,
Exit,
Restart,
};
RegistryCheckInstructions CheckForRegistryProblems(QWidget* parentWidget, bool showPopupMessage);
virtual bool IsAssetProcessorManagerIdle() const = 0;
Q_SIGNALS:
void AssetProcessorStatusChanged(AssetProcessor::AssetProcessorStatusEntry entry);
public Q_SLOTS:
void ReadyToQuit(QObject* source);
void QuitRequested();
void ObjectDestroyed(QObject* source);
void Restart();
private Q_SLOTS:
void CheckQuit();
void CheckForUpdate();
protected:
//! Deactivate all your class member objects in this method
virtual void Destroy() = 0;
//! Prepares Qt Directories,Install Qt Translator etc
virtual bool Activate();
//! Runs late stage set up code
virtual bool PostActivate();
//! Override this method to create either QApplication or QCoreApplication
virtual void CreateQtApplication() = 0;
QString GetOrganizationName() const;
QString GetApplicationName() const;
void RegisterObjectForQuit(QObject* source, bool insertInFront = false);
bool NeedRestart() const;
void addRunningThread(AssetProcessor::ThreadWorker* thread);
template<class BuilderClass>
void RegisterInternalBuilder(const QString& builderName);
//! Load the Modules (Such as Gems) and have them be reflected.
bool ActivateModules();
void PopulateApplicationDependencies();
bool InitiatedShutdown() const;
bool m_duringStartup = true;
AssetProcessorAZApplication m_frameworkApp;
QCoreApplication* m_qApp = nullptr;
//! Get the list of external builder files for this asset processor
void GetExternalBuilderFileList(QStringList& externalBuilderModules);
virtual void Reflect() = 0;
virtual const char* GetLogBaseName() = 0;
virtual RegistryCheckInstructions PopupRegistryProblemsMessage(QString warningText) = 0;
private:
bool StartAZFramework(QString appRootOverride);
bool ValidateExternalAppRoot(QString appRootPath) const;
QString ParseOptionAppRootArgument();
// QuitPair - Object pointer and "is ready" boolean pair.
typedef QPair<QObject*, bool> QuitPair;
QList<QuitPair> m_objectsToNotify;
bool m_duringShutdown = false;
QList<ApplicationDependencyInfo*> m_appDependencies;
QList<QString> m_filesOfInterest;
QList<AssetProcessor::ThreadWorker*> m_runningThreads;
QTimer m_updateTimer;
bool m_needRestart = false;
bool m_queuedCheckQuit = false;
QDir m_systemRoot;
QString m_gameName;
AZ::Entity* m_entity = nullptr;
};
///This class stores all the information of files that
/// we need to monitor for relaunching assetprocessor
struct ApplicationDependencyInfo
{
QString m_fileName;
QDateTime m_timestamp;
ApplicationDependencyInfo(QString fileName, QDateTime timestamp)
: m_fileName(fileName)
, m_timestamp(timestamp)
{
}
public:
QString FileName() const;
void SetFileName(QString FileName);
QDateTime Timestamp() const;
void SetTimestamp(const QDateTime& Timestamp);
};
@@ -0,0 +1,50 @@
/*
* 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.
*
*/
#ifndef ASSETPROCESSOR_APPLICATIONMANAGERAPI_H
#define ASSETPROCESSOR_APPLICATIONMANAGERAPI_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/parallel/mutex.h>
#pragma once
namespace AssetProcessor
{
//! This class contains notifications broadcast by the Application Manager (Which manages the lifecycle of the application).
//! note, these events will be dispatched sequentially and safely from one specific thread (main UI thread), but may be
//! talking to an object on a different "unsafe" thread and thus appropriate thread safety should be observed by the listener.
class ApplicationManagerNotifications
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
/// Public API:
//! Invoked by the application when its time to shut down
//! Jobs must quit as soon as they can, with 'failed' status.
virtual void ApplicationShutdownRequested() = 0;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/// Bus Configuration
ApplicationManagerNotifications() = default;
~ApplicationManagerNotifications() = default;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // any number of connected listeners
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // no addressing used.
typedef AZStd::recursive_mutex MutexType; // protect bus addition and removal since listeners can disconnect
using Bus = AZ::EBus<ApplicationManagerNotifications>;
//////////////////////////////////////////////////////////////////////////
};
} // namespace AssetProcesor
#endif // ASSETPROCESSOR_APPLICATIONMANAGERAPI_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,251 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <native/FileWatcher/FileWatcher.h>
#include <native/utilities/ApplicationManager.h>
#include <native/utilities/AssetBuilderInfo.h>
#include <native/utilities/BuilderManager.h>
#endif
namespace AzToolsFramework
{
class ProcessWatcher;
class Ticker;
}
namespace AssetProcessor
{
class AssetCatalog;
class AssetProcessorManager;
class AssetRequestHandler;
class AssetScanner;
class AssetServerHandler;
class BuilderConfigurationManager;
class BuilderManager;
class ExternalModuleAssetBuilderInfo;
class FileProcessor;
class FileStateBase;
class FileStateCache;
class InternalAssetBuilderInfo;
class PlatformConfiguration;
class RCController;
class SettingsRegistryBuilder;
}
class ApplicationServer;
class ConnectionManager;
class FolderWatchCallbackEx;
class ControlRequestHandler;
class ApplicationManagerBase
: public ApplicationManager
, public AssetBuilderSDK::AssetBuilderBus::Handler
, public AssetProcessor::AssetBuilderInfoBus::Handler
, public AssetProcessor::AssetBuilderRegistrationBus::Handler
, public AZ::Debug::TraceMessageBus::Handler
, protected AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Handler
, public AssetProcessor::DiskSpaceInfoBus::Handler
, protected AzToolsFramework::SourceControlNotificationBus::Handler
{
Q_OBJECT
public:
explicit ApplicationManagerBase(int* argc, char*** argv, QObject* parent = 0);
virtual ~ApplicationManagerBase();
ApplicationManager::BeforeRunStatus BeforeRun() override;
void Destroy() override;
bool Run() override;
void HandleFileRelocation() const;
bool Activate() override;
bool PostActivate() override;
AssetProcessor::PlatformConfiguration* GetPlatformConfiguration() const;
AssetProcessor::AssetProcessorManager* GetAssetProcessorManager() const;
AssetProcessor::AssetScanner* GetAssetScanner() const;
AssetProcessor::RCController* GetRCController() const;
ConnectionManager* GetConnectionManager() const;
ApplicationServer* GetApplicationServer() const;
int ProcessedAssetCount() const;
int FailedAssetsCount() const;
void ResetProcessedAssetCount();
void ResetFailedAssetCount();
//! AssetBuilderSDK::AssetBuilderBus Interface
void RegisterBuilderInformation(const AssetBuilderSDK::AssetBuilderDesc& builderDesc) override;
void RegisterComponentDescriptor(AZ::ComponentDescriptor* descriptor) override;
void BuilderLog(const AZ::Uuid& builderId, const char* message, ...) override;
void BuilderLogV(const AZ::Uuid& builderId, const char* message, va_list list) override;
bool FindBuilderInformation(const AZ::Uuid& builderGuid, AssetBuilderSDK::AssetBuilderDesc& descriptionOut) override;
//! AssetBuilderSDK::InternalAssetBuilderBus Interface
void UnRegisterBuilderDescriptor(const AZ::Uuid& builderId) override;
//! AssetProcessor::AssetBuilderInfoBus Interface
void GetMatchingBuildersInfo(const AZStd::string& assetPath, AssetProcessor::BuilderInfoList& builderInfoList) override;
void GetAllBuildersInfo(AssetProcessor::BuilderInfoList& builderInfoList) override;
//! TraceMessageBus Interface
bool OnError(const char* window, const char* message) override;
//! DiskSpaceInfoBus::Handler
bool CheckSufficientDiskSpace(const QString& savePath, qint64 requiredSpace, bool shutdownIfInsufficient) override;
//! AzFramework::SourceControlNotificationBus::Handler
void ConnectivityStateChanged(const AzToolsFramework::SourceControlState newState) override;
void RemoveOldTempFolders();
void Rescan();
bool IsAssetProcessorManagerIdle() const;
bool CheckFullIdle();
Q_SIGNALS:
void CheckAssetProcessorManagerIdleState();
void ConnectionStatusMsg(QString message);
void SourceControlReady();
void OnBuildersRegistered();
void AssetProcesserManagerIdleStateChange(bool isIdle);
void FullIdle(bool isIdle);
public Q_SLOTS:
void OnAssetProcessorManagerIdleState(bool isIdle);
protected:
virtual void InitAssetProcessorManager();//Deletion of assetProcessor Manager will be handled by the ThreadController
virtual void InitAssetCatalog();//Deletion of AssetCatalog will be handled when the ThreadController is deleted by the base ApplicationManager
virtual void InitRCController();
virtual void DestroyRCController();
virtual void InitAssetScanner();
virtual void DestroyAssetScanner();
virtual bool InitPlatformConfiguration();
virtual void DestroyPlatformConfiguration();
virtual void InitFileMonitor();
virtual void DestroyFileMonitor();
virtual bool InitBuilderConfiguration();
virtual void InitControlRequestHandler();
virtual void DestroyControlRequestHandler();
virtual bool InitApplicationServer() = 0;
void DestroyApplicationServer();
virtual void InitConnectionManager();
void DestroyConnectionManager();
void InitAssetRequestHandler(AssetProcessor::AssetRequestHandler* assetRequestHandler);
void InitFileStateCache();
void CreateQtApplication() override;
bool InitializeInternalBuilders();
bool InitializeExternalBuilders();
void InitBuilderManager();
void ShutdownBuilderManager();
bool InitAssetDatabase();
void ShutDownAssetDatabase();
void InitAssetServerHandler();
void DestroyAssetServerHandler();
void InitFileProcessor();
void ShutDownFileProcessor();
virtual void InitSourceControl() = 0;
void InitInputThread();
void InputThread();
// Give an opportunity to derived classes to make connections before the application server starts listening
virtual void MakeActivationConnections() {}
virtual bool GetShouldExitOnIdle() const = 0;
virtual void TryScanProductDependencies() {}
virtual void TryHandleFileRelocation() {}
// IMPLEMENTATION OF -------------- AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Listener
bool GetAssetDatabaseLocation(AZStd::string& location) override;
// ------------------------------------------------------------
AssetProcessor::AssetCatalog* GetAssetCatalog() const { return m_assetCatalog; }
static bool WaitForBuilderExit(AzToolsFramework::ProcessWatcher* processWatcher, AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds);
ApplicationServer* m_applicationServer = nullptr;
ConnectionManager* m_connectionManager = nullptr;
// keep track of the critical loading point where we are loading other dlls so the error messages can be better.
bool m_isCurrentlyLoadingGems = false;
public Q_SLOTS:
void OnActiveJobsCountChanged(unsigned int count);
protected Q_SLOTS:
void CheckForIdle();
protected:
int m_processedAssetCount = 0;
int m_failedAssetsCount = 0;
int m_warningCount = 0;
int m_errorCount = 0;
bool m_AssetProcessorManagerIdleState = false;
bool m_sourceControlReady = false;
bool m_fullIdle = false;
AZStd::vector<AZStd::unique_ptr<FolderWatchCallbackEx> > m_folderWatches;
FileWatcher m_fileWatcher;
AZStd::vector<int> m_watchHandles;
AssetProcessor::PlatformConfiguration* m_platformConfiguration = nullptr;
AssetProcessor::AssetProcessorManager* m_assetProcessorManager = nullptr;
AssetProcessor::AssetCatalog* m_assetCatalog = nullptr;
AssetProcessor::AssetScanner* m_assetScanner = nullptr;
AssetProcessor::RCController* m_rcController = nullptr;
AssetProcessor::AssetRequestHandler* m_assetRequestHandler = nullptr;
AssetProcessor::BuilderManager* m_builderManager = nullptr;
AssetProcessor::AssetServerHandler* m_assetServerHandler = nullptr;
ControlRequestHandler* m_controlRequestHandler = nullptr;
AZStd::unique_ptr<AssetProcessor::FileStateBase> m_fileStateCache;
AZStd::unique_ptr<AssetProcessor::FileProcessor> m_fileProcessor;
AZStd::unique_ptr<AssetProcessor::BuilderConfigurationManager> m_builderConfig;
// The internal builders
AZStd::shared_ptr<AssetProcessor::InternalRecognizerBasedBuilder> m_internalBuilder;
AZStd::shared_ptr<AssetProcessor::SettingsRegistryBuilder> m_settingsRegistryBuilder;
// Builder description map based on the builder id
AZStd::unordered_map<AZ::Uuid, AssetBuilderSDK::AssetBuilderDesc> m_builderDescMap;
// Lookup for builder ids based on the name. The builder name must be unique
AZStd::unordered_map<AZStd::string, AZ::Uuid> m_builderNameToId;
// Builder pattern matchers to used to locate the builder descriptors that match a pattern
AZStd::list<AssetUtilities::BuilderFilePatternMatcher> m_matcherBuilderPatterns;
// Collection of all the external module builders
AZStd::list<AssetProcessor::ExternalModuleAssetBuilderInfo*> m_externalAssetBuilders;
AssetProcessor::ExternalModuleAssetBuilderInfo* m_currentExternalAssetBuilder = nullptr;
QAtomicInt m_connectionsAwaitingAssetCatalogSave = 0;
int m_remainingAPMJobs = 0;
bool m_assetProcessorManagerIsReady = false;
unsigned int m_highestConnId = 0;
AzToolsFramework::Ticker* m_ticker = nullptr; // for ticking the tickbus.
QList<QMetaObject::Connection> m_connectionsToRemoveOnShutdown;
QString m_dependencyScanPattern;
QString m_fileDependencyScanPattern;
AZStd::vector<AZStd::string> m_dependencyAddtionalScanFolders;
int m_dependencyScanMaxIteration = AssetProcessor::MissingDependencyScanner::DefaultMaxScanIteration; // The maximum number of times to recurse when scanning a file for missing dependencies.
};
@@ -0,0 +1,53 @@
/*
* 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 "native/utilities/ApplicationServer.h"
#include <QTcpSocket>
const char ApplicationServer::RandomListeningPortOption[] = "randomListeningPort";
ApplicationServer::ApplicationServer(QObject* parent)
: QTcpServer(parent)
{
}
ApplicationServer::~ApplicationServer()
{
ApplicationServerBus::Handler::BusDisconnect();
}
void ApplicationServer::incomingConnection(qintptr socketDescriptor)
{
if (m_isShuttingDown)
{
// deny the connection and return.
QTcpSocket sock;
sock.setSocketDescriptor(socketDescriptor, QAbstractSocket::ConnectedState, QIODevice::ReadWrite);
sock.close();
return;
}
Q_EMIT newIncomingConnection(socketDescriptor);
}
int ApplicationServer::GetServerListeningPort() const
{
return m_serverListeningPort;
}
void ApplicationServer::QuitRequested()
{
// stop accepting messages and close the connection immediately.
pauseAccepting();
close();
Q_EMIT ReadyToQuit(this);
}
@@ -0,0 +1,50 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QTcpServer>
#include <native/utilities/AssetUtilEBusHelper.h>
#endif
/** This Class is responsible for listening and getting new connections
*/
class ApplicationServer
: public QTcpServer,
public ApplicationServerBus::Handler
{
Q_OBJECT
public:
explicit ApplicationServer(QObject* parent = 0);
virtual ~ApplicationServer();
void incomingConnection(qintptr socketDescriptor) override;
virtual bool startListening(unsigned short port = 0) = 0;
//! ApplicationServerBus Handler
int GetServerListeningPort() const override;
static const char RandomListeningPortOption[];
Q_SIGNALS:
void newIncomingConnection(qintptr socketDescriptor);
void ReadyToQuit(QObject* source);
public Q_SLOTS:
void QuitRequested();
protected:
int m_serverListeningPort = 0;
bool m_isShuttingDown = false;
};
@@ -0,0 +1,194 @@
/*
* 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 <native/utilities/AssetBuilderInfo.h>
#include <AzCore/Component/Entity.h>
namespace AssetProcessor
{
#ifdef AZ_PLATFORM_WINDOWS
const char* const s_assetBuilderRelativePath = "AssetBuilder.exe";
#else
const char* const s_assetBuilderRelativePath = "AssetBuilder";
#endif
ExternalModuleAssetBuilderInfo::ExternalModuleAssetBuilderInfo(const QString& modulePath)
: m_builderName(modulePath)
, m_entity(nullptr)
, m_componentDescriptorList()
, m_initializeModuleFunction(nullptr)
, m_moduleRegisterDescriptorsFunction(nullptr)
, m_moduleAddComponentsFunction(nullptr)
, m_uninitializeModuleFunction(nullptr)
, m_library(modulePath)
{
}
const QString& ExternalModuleAssetBuilderInfo::GetName() const
{
return m_builderName;
}
QString ExternalModuleAssetBuilderInfo::GetModuleFullPath() const
{
return m_library.fileName();
}
//! Sanity check for the module's status
bool ExternalModuleAssetBuilderInfo::IsLoaded() const
{
return m_library.isLoaded();
}
void ExternalModuleAssetBuilderInfo::Initialize()
{
AZ_Error(AssetProcessor::ConsoleChannel, IsLoaded(), "External module %s not loaded.", GetName().toUtf8().data());
if (GetAssetBuilderType() == AssetBuilderType::Valid)
{
m_initializeModuleFunction(AZ::Environment::GetInstance());
m_moduleRegisterDescriptorsFunction();
AZStd::string entityName = AZStd::string::format("%s Entity", GetName().toUtf8().data());
m_entity = aznew AZ::Entity(entityName.c_str());
m_moduleAddComponentsFunction(m_entity);
AZ_TracePrintf(AssetProcessor::DebugChannel, "Init Entity %s", GetName().toUtf8().data());
m_entity->Init();
//Activate all the components
m_entity->Activate();
}
}
void ExternalModuleAssetBuilderInfo::UnInitialize()
{
AZ_Error(AssetProcessor::ConsoleChannel, IsLoaded(), "External module %s not loaded.", GetName().toUtf8().data());
AZ_TracePrintf(AssetProcessor::DebugChannel, "Uninitializing builder: %s\n", GetModuleFullPath().toUtf8().data());
if (m_entity)
{
m_entity->Deactivate();
delete m_entity;
m_entity = nullptr;
}
for (AZ::ComponentDescriptor* componentDesc : m_componentDescriptorList)
{
componentDesc->ReleaseDescriptor();
}
m_componentDescriptorList.clear();
for (const AZ::Uuid& builderDescID : m_registeredBuilderDescriptorIDs)
{
AssetBuilderRegistrationBus::Broadcast(&AssetBuilderRegistrationBusTraits::UnRegisterBuilderDescriptor, builderDescID);
}
m_registeredBuilderDescriptorIDs.clear();
m_uninitializeModuleFunction();
if (IsLoaded())
{
m_library.unload();
}
}
AssetBuilderType ExternalModuleAssetBuilderInfo::GetAssetBuilderType()
{
QStringList missingFunctionsList;
ResolveModuleFunction<QFunctionPointer>("IsAssetBuilder", missingFunctionsList);
InitializeModuleFunction initializeModuleAddress = ResolveModuleFunction<InitializeModuleFunction>("InitializeModule", missingFunctionsList);
ModuleRegisterDescriptorsFunction moduleRegisterDescriptorsAddress = ResolveModuleFunction<ModuleRegisterDescriptorsFunction>("ModuleRegisterDescriptors", missingFunctionsList);
ModuleAddComponentsFunction moduleAddComponentsAddress = ResolveModuleFunction<ModuleAddComponentsFunction>("ModuleAddComponents", missingFunctionsList);
UninitializeModuleFunction uninitializeModuleAddress = ResolveModuleFunction<UninitializeModuleFunction>("UninitializeModule", missingFunctionsList);
if (missingFunctionsList.size() == 0)
{
//if we are here then it is a builder
m_initializeModuleFunction = initializeModuleAddress;
m_moduleRegisterDescriptorsFunction = moduleRegisterDescriptorsAddress;
m_moduleAddComponentsFunction = moduleAddComponentsAddress;
m_uninitializeModuleFunction = uninitializeModuleAddress;
return AssetBuilderType::Valid;
}
else if (missingFunctionsList.size() > 0 && missingFunctionsList.contains("IsAssetBuilder"))
{
// This DLL is not a builder and should be ignored.
return AssetBuilderType::None;
}
else
{
// This is supposed to be a builder but is invalid
QString errorMessage = QString("Builder library %1 is missing one or more exported functions: %2").arg(QString(GetName()), missingFunctionsList.join(','));
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "One or more builder functions is missing in the library: %s\n", errorMessage.toUtf8().data());
return AssetBuilderType::Invalid;
}
}
AssetBuilderType ExternalModuleAssetBuilderInfo::Load()
{
if (IsLoaded())
{
// This builder is already loaded - ignore the duplicate
AZ_Warning(AssetProcessor::ConsoleChannel, false, "External module %s already loaded.", GetName().toUtf8().data());
return AssetBuilderType::None;
}
if (!m_library.load())
{
// Invalid builder - unable to load
AZ_TracePrintf(AssetProcessor::DebugChannel, "Unable to load builder : %s\n", GetName().toUtf8().data());
return AssetBuilderType::Invalid;
}
return GetAssetBuilderType();
}
void ExternalModuleAssetBuilderInfo::RegisterBuilderDesc(const AZ::Uuid& builderDescID)
{
if (m_registeredBuilderDescriptorIDs.find(builderDescID) != m_registeredBuilderDescriptorIDs.end())
{
AZ_Warning(AssetBuilderSDK::InfoWindow,
false,
"Builder description id '%s' already registered to external builder module %s",
builderDescID.ToString<AZStd::string>().c_str(),
GetName().toUtf8().data());
return;
}
m_registeredBuilderDescriptorIDs.insert(builderDescID);
}
void ExternalModuleAssetBuilderInfo::RegisterComponentDesc(AZ::ComponentDescriptor* descriptor)
{
m_componentDescriptorList.push_back(descriptor);
}
template<typename T>
T ExternalModuleAssetBuilderInfo::ResolveModuleFunction(const char* functionName, QStringList& missingFunctionsList)
{
T functionAddr = reinterpret_cast<T>(m_library.resolve(functionName));
if (!functionAddr)
{
missingFunctionsList.append(QString(functionName));
}
return functionAddr;
}
}
@@ -0,0 +1,115 @@
/*
* 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.
*
*/
#ifndef UTILITIES_ASSETBUILDERINFO_H
#define UTILITIES_ASSETBUILDERINFO_H
#pragma once
#include <functional>
#include <QVector>
#include <QLibrary>
#include <AzCore/std/base.h>
#include <AzCore/std/containers/set.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <native/assetprocessor.h>
#include <native/utilities/PlatformConfiguration.h>
#include <native/resourcecompiler/RCBuilder.h>
#include <AssetBuilder/AssetBuilderInfo.h>
class FolderWatchCallbackEx;
class QCoreApplication;
namespace AssetProcessor
{
using AssetBuilder::AssetBuilderType;
enum ASSET_BUILDER_TYPE
{
INVALID, VALID, NONE
};
// A string like "AssetBuilder.exe" which names the executable of the asset builder.
extern const char* const s_assetBuilderRelativePath;
//! Class to manage external module builders for the asset processor
class ExternalModuleAssetBuilderInfo
{
public:
ExternalModuleAssetBuilderInfo(const QString& modulePath);
virtual ~ExternalModuleAssetBuilderInfo() = default;
const QString& GetName() const;
QString GetModuleFullPath() const;
//! Perform a load of the external module, this is required before initialize.
AssetBuilderType Load();
//! Sanity check for the module's status
bool IsLoaded() const;
//! Perform the module initialization for the external builder
void Initialize();
//! Perform the necessary process of uninitializing an external builder
void UnInitialize();
//! Check to see if the builder has the required functions defined.
AssetBuilderType GetAssetBuilderType();
ASSET_BUILDER_TYPE IsAssetBuilder();
//! Register a builder descriptor ID to track as part of this builders lifecycle management
void RegisterBuilderDesc(const AZ::Uuid& builderDesc);
//! Register a component descriptor to track as part of this builders lifecycle management
void RegisterComponentDesc(AZ::ComponentDescriptor* descriptor);
protected:
AZStd::set<AZ::Uuid> m_registeredBuilderDescriptorIDs;
typedef void(* InitializeModuleFunction)(AZ::EnvironmentInstance sharedEnvironment);
typedef void(* ModuleRegisterDescriptorsFunction)(void);
typedef void(* ModuleAddComponentsFunction)(AZ::Entity* entity);
typedef void(* UninitializeModuleFunction)(void);
template<typename T>
T ResolveModuleFunction(const char* functionName, QStringList& missingFunctionsList);
InitializeModuleFunction m_initializeModuleFunction;
ModuleRegisterDescriptorsFunction m_moduleRegisterDescriptorsFunction;
ModuleAddComponentsFunction m_moduleAddComponentsFunction;
UninitializeModuleFunction m_uninitializeModuleFunction;
AZStd::vector<AZ::ComponentDescriptor*> m_componentDescriptorList;
AZ::Entity* m_entity = nullptr;
QString m_builderName;
QLibrary m_library;
};
//!This EBUS is used to send information from an internal builder to the AssetProcessor
class AssetBuilderRegistrationBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
virtual ~AssetBuilderRegistrationBusTraits() {}
virtual void UnRegisterBuilderDescriptor(const AZ::Uuid& /*builderId*/) {}
};
typedef AZ::EBus<AssetBuilderRegistrationBusTraits> AssetBuilderRegistrationBus;
} // AssetProcessor
#endif //UTILITIES_ASSETBUILDERINFO_H
@@ -0,0 +1,155 @@
/*
* 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 <native/utilities/AssetServerHandler.h>
#include <native/resourcecompiler/rcjob.h>
#include <AzToolsFramework/Archive/ArchiveAPI.h>
#include <QDir>
namespace AssetProcessor
{
QString ComputeArchiveFilePath(const AssetProcessor::BuilderParams& builderParams)
{
QFileInfo fileInfo(builderParams.m_processJobRequest.m_sourceFile.c_str());
QString assetServerAddress = QDir::toNativeSeparators(AssetUtilities::ServerAddress());
if (!assetServerAddress.isEmpty())
{
QDir assetServerDir(assetServerAddress);
QString archiveFileName = builderParams.GetServerKey() + ".zip";
QString archiveFilePath = QDir(assetServerDir.filePath(fileInfo.path())).filePath(archiveFileName);
return archiveFilePath;
}
return QString();
}
AssetServerHandler::AssetServerHandler()
{
AssetServerBus::Handler::BusConnect();
}
AssetServerHandler::~AssetServerHandler()
{
AssetServerBus::Handler::BusDisconnect();
}
bool AssetServerHandler::IsServerAddressValid()
{
QString address = AssetUtilities::ServerAddress();
bool isValid = !address.isEmpty() && QDir(address).exists();
return isValid;
}
bool AssetServerHandler::RetrieveJobResult(const AssetProcessor::BuilderParams& builderParams)
{
AssetBuilderSDK::JobCancelListener jobCancelListener(builderParams.m_rcJob->GetJobEntry().m_jobRunKey);
AssetUtilities::QuitListener listener;
listener.BusConnect();
QString archiveAbsFilePath = ComputeArchiveFilePath(builderParams);
if (archiveAbsFilePath.isEmpty())
{
AZ_Error(AssetProcessor::DebugChannel, false, "Extracting archive operation failed. Archive Absolute Path is empty. \n");
return false;
}
if (!QFile::exists(archiveAbsFilePath))
{
// file does not exist on the server
AZ_TracePrintf(AssetProcessor::DebugChannel, "Extracting archive operation cancelled. Archive does not exist on server. \n");
return false;
}
if (listener.WasQuitRequested() || jobCancelListener.IsCancelled())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Extracting archive operation cancelled. \n");
return false;
}
AZ_TracePrintf(AssetProcessor::DebugChannel, "Extracting archive for job (%s, %s, %s) with fingerprint (%u).\n",
builderParams.m_rcJob->GetJobEntry().m_pathRelativeToWatchFolder.toUtf8().data(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
builderParams.m_rcJob->GetPlatformInfo().m_identifier.c_str(), builderParams.m_rcJob->GetOriginalFingerprint());
bool success = false;
AzToolsFramework::ArchiveCommands::Bus::BroadcastResult(success, &AzToolsFramework::ArchiveCommands::ExtractArchiveBlocking, archiveAbsFilePath.toUtf8().data(), builderParams.GetTempJobDirectory(), false);
AZ_Error(AssetProcessor::DebugChannel, success, "Extracting archive operation failed.\n");
return success;
}
bool AssetServerHandler::StoreJobResult(const AssetProcessor::BuilderParams& builderParams, AZStd::vector<AZStd::string>& sourceFileList)
{
AssetBuilderSDK::JobCancelListener jobCancelListener(builderParams.m_rcJob->GetJobEntry().m_jobRunKey);
AssetUtilities::QuitListener listener;
listener.BusConnect();
QString archiveAbsFilePath = ComputeArchiveFilePath(builderParams);
if (archiveAbsFilePath.isEmpty())
{
AZ_Error(AssetProcessor::DebugChannel, false, "Creating archive operation failed. Archive Absolute Path is empty. \n");
return false;
}
if (QFile::exists(archiveAbsFilePath))
{
// file already exists on the server
AZ_TracePrintf(AssetProcessor::DebugChannel, "Creating archive operation cancelled. An archive of this asset already exists on server. \n");
return true;
}
if (listener.WasQuitRequested() || jobCancelListener.IsCancelled())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Creating archive operation cancelled. \n");
return false;
}
bool success = false;
AZ_TracePrintf(AssetProcessor::DebugChannel, "Creating archive for job (%s, %s, %s) with fingerprint (%u).\n",
builderParams.m_rcJob->GetJobEntry().m_pathRelativeToWatchFolder.toUtf8().data(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
builderParams.m_rcJob->GetPlatformInfo().m_identifier.c_str(), builderParams.m_rcJob->GetOriginalFingerprint());
AzToolsFramework::ArchiveCommands::Bus::BroadcastResult(success, &AzToolsFramework::ArchiveCommands::CreateArchiveBlocking, archiveAbsFilePath.toUtf8().data(), builderParams.GetTempJobDirectory());
AZ_Error(AssetProcessor::DebugChannel, success, "Creating archive operation failed. \n");
if (success && sourceFileList.size())
{
// Check if any of our output products for this job was a source file which would not be in the temp folder
// If so add it to the archive
AddSourceFilesToArchive(builderParams, archiveAbsFilePath, sourceFileList);
}
return success;
}
bool AssetServerHandler::AddSourceFilesToArchive(const AssetProcessor::BuilderParams& builderParams, const QString& archivePath, AZStd::vector<AZStd::string>& sourceFileList)
{
bool allSuccess{ true };
for (const auto& thisProduct : sourceFileList)
{
QFileInfo sourceFile{ builderParams.m_rcJob->GetJobEntry().GetAbsoluteSourcePath() };
QDir sourceDir{ sourceFile.absoluteDir() };
if (!QFileInfo(sourceDir.absoluteFilePath(thisProduct.c_str())).exists())
{
AZ_Warning(AssetProcessor::DebugChannel, false, "Failed to add %s to %s - source does not exist in expected location (sourceDir %s )", thisProduct.c_str(), archivePath.toUtf8().data(), sourceDir.path().toUtf8().data());
allSuccess = false;
continue;
}
bool success{ false };
AzToolsFramework::ArchiveCommands::Bus::BroadcastResult(success, &AzToolsFramework::ArchiveCommands::AddFileToArchiveBlocking, archivePath.toUtf8().data(), sourceDir.path().toUtf8().data(), thisProduct.c_str());
if (!success)
{
AZ_Warning(AssetProcessor::DebugChannel, false, "Failed to add %s to %s", thisProduct.c_str(), archivePath.toUtf8().data());
allSuccess = false;
}
}
return allSuccess;
}
}// AssetProcessor
@@ -0,0 +1,42 @@
/*
* 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.
*
*/
#pragma once
#include <native/utilities/AssetUtilEBusHelper.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
namespace AssetProcessor
{
//! AssetServerHandler is implementing asset server using network share.
class AssetServerHandler
: public AssetServerBus::Handler
{
public:
AssetServerHandler();
virtual ~AssetServerHandler();
//////////////////////////////////////////////////////////////////////////
// AssetServerBus::Handler overrides
bool IsServerAddressValid();
//! StoreJobResult will store all the files in the the temp folder provided by AP to a zip file on the network drive
//! whose file name will be based on the server key
bool StoreJobResult(const AssetProcessor::BuilderParams& builderParams, AZStd::vector<AZStd::string>& sourceFileList) override;
//! RetrieveJobResult will retrieve the zip file from the network share associated with the server key and unzip it to the temporary directory provided by AP.
bool RetrieveJobResult(const AssetProcessor::BuilderParams& builderParams) override;
protected:
//! Source files intended to be copied into the cache don't go through out temp folder so they need
//! to be added to the Archive in an additional step
bool AddSourceFilesToArchive(const AssetProcessor::BuilderParams& builderParams, const QString& archivePath, AZStd::vector<AZStd::string>& sourceFileList);
//////////////////////////////////////////////////////////////////////////
};
} //namespace AssetProcessor
@@ -0,0 +1,255 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
#include <native/assetprocessor.h>
#include <QByteArray>
namespace AssetProcessor
{
struct BuilderParams;
}
namespace AssetUtilities
{
class QuitListener;
class JobLogTraceListener;
}
//This EBUS broadcasts the platform of the connection the AssetProcessor connected or disconnected with
class AssetProcessorPlaformBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // multi listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; //single bus
virtual ~AssetProcessorPlaformBusTraits() {}
//Informs that the AP got a connection for this platform.
virtual void AssetProcessorPlatformConnected(const AZStd::string platform) {}
//Informs that a connection got disconnected for this platform.
virtual void AssetProcessorPlatformDisconnected(const AZStd::string platform) {}
};
using AssetProcessorPlatformBus = AZ::EBus<AssetProcessorPlaformBusTraits>;
class ApplicationServerBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
virtual ~ApplicationServerBusTraits() {};
//! Returns the port the server is set to listen on
virtual int GetServerListeningPort() const = 0;
};
using ApplicationServerBus = AZ::EBus<ApplicationServerBusTraits>;
namespace AzFramework
{
namespace AssetSystem
{
class BaseAssetProcessorMessage;
}
}
namespace AssetProcessor
{
// This bus sends messages to connected clients/proxies identified by their connection ID. The bus
// is addressed by the connection ID as assigned by the ConnectionManager.
class ConnectionBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
typedef unsigned int BusIdType;
typedef AZStd::recursive_mutex MutexType;
virtual ~ConnectionBusTraits() {}
// Sends an unsolicited message to the connection
virtual size_t Send(unsigned int serial, const AzFramework::AssetSystem::BaseAssetProcessorMessage& message) = 0;
// Sends a raw buffer to the connection
virtual size_t SendRaw(unsigned int type, unsigned int serial, const QByteArray& data) = 0;
// Sends a message to the connection if the platform match
virtual size_t SendPerPlatform(unsigned int serial, const AzFramework::AssetSystem::BaseAssetProcessorMessage& message, const QString& platform) = 0;
// Sends a raw buffer to the connection if the platform match
virtual size_t SendRawPerPlatform(unsigned int type, unsigned int serial, const QByteArray& data, const QString& platform) = 0;
using ResponseCallback = AZStd::function<void(AZ::u32, QByteArray)>;
// Sends a message to the connection which expects a response.
virtual unsigned int SendRequest(const AzFramework::AssetSystem::BaseAssetProcessorMessage& message, const ResponseCallback& callback) = 0;
// Sends a response to the connection
virtual size_t SendResponse(unsigned int serial, const AzFramework::AssetSystem::BaseAssetProcessorMessage& message) = 0;
// Removes a response handler that is no longer needed
virtual void RemoveResponseHandler(unsigned int serial) = 0;
};
using ConnectionBus = AZ::EBus<ConnectionBusTraits>;
class MessageInfoBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // multi listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; //single bus
typedef AZStd::recursive_mutex MutexType;
virtual ~MessageInfoBusTraits() {}
//Show a message window to the user
virtual void NegotiationFailed() {}
// Notifies listeners of a given Asset failing to process
virtual void OnAssetFailed(const AZStd::string& /*sourceFileName*/) {}
// Notifies listener about a general error
virtual void OnErrorMessage([[maybe_unused]] const char* error) {}
};
using MessageInfoBus = AZ::EBus<MessageInfoBusTraits>;
typedef AZStd::vector <AssetBuilderSDK::AssetBuilderDesc> BuilderInfoList;
// This EBUS is used to retrieve asset builder Information
class AssetBuilderInfoBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; // single listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; //single bus
virtual ~AssetBuilderInfoBusTraits() {}
// For a given asset returns a list of all asset builder that are interested in it.
virtual void GetMatchingBuildersInfo(const AZStd::string& assetPath, AssetProcessor::BuilderInfoList& /*builderInfoList*/) = 0;
virtual void GetAllBuildersInfo(AssetProcessor::BuilderInfoList& /*builderInfoList*/) = 0;
};
using AssetBuilderInfoBus = AZ::EBus<AssetBuilderInfoBusTraits>;
// This EBUS is used to broadcast information about the currently processing job
class ProcessingJobInfoBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; // single listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; //single bus
typedef AZStd::recursive_mutex MutexType;
virtual ~ProcessingJobInfoBusTraits() {}
// Will notify other systems a product is about to be updated in the cache. This can mean that
// it will be created, overwritten with new data or deleted. BeginCacheFileUpdate is pared with
// EndCacheFileUpdate.
virtual void BeginCacheFileUpdate(const char* /*productPath*/) {};
// Will notify other systems that a file in the cache has been updated along with status of whether it
// succeeded or failed. EndCacheFileUpdate is paired with BeginCacheFileUpdate.
virtual void EndCacheFileUpdate(const char* /*productPath*/, bool /*queueAgainForDeletion*/) {};
virtual AZ::u32 GetJobFingerprint(const AssetProcessor::JobIndentifier& /*jobIndentifier*/) { return 0; };
};
using ProcessingJobInfoBus = AZ::EBus<ProcessingJobInfoBusTraits>;
// This EBUS is used to issue requests to the AssetCatalog
class AssetRegistryRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; // single listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; //single bus
typedef AZStd::recursive_mutex MutexType;
// This function will either return the registry version of the next registry save or of the current one, if it is in progress
// It will not put another save registry event in the event pump if we are currently in the process of saving the registry
virtual int SaveRegistry() = 0;
// This method checks for cyclic preload dependency for all the currently processed assets.
virtual void ValidatePreLoadDependency() = 0;
};
typedef AZ::EBus<AssetRegistryRequests> AssetRegistryRequestBus;
// This EBUS issues notifications when the catalog begins and finishes saving the asset registry
class AssetRegistryNotifications
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // single listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; //single bus
typedef AZStd::recursive_mutex MutexType;
// The asset catalog has finished saving the registry
virtual void OnRegistrySaveComplete(int /*assetCatalogVersion*/, bool /*allCatalogsSaved*/) {}
};
using AssetRegistryNotificationBus = AZ::EBus<AssetRegistryNotifications>;
// This EBUS is used to check if there is sufficient disk space
class DiskSpaceInfoBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
// Returns true if there is at least `requiredSpace` bytes plus 256kb free disk space at the specified path
// savePath must be a folder path, not a file path
// If shutdownIfInsufficient is true, an error will be displayed and the application will be shutdown
virtual bool CheckSufficientDiskSpace(const QString& /*savePath*/, qint64 /*requiredSpace*/, bool /*shutdownIfInsufficient*/) { return true; }
};
using DiskSpaceInfoBus = AZ::EBus<DiskSpaceInfoBusTraits>;
// This EBUS is used to perform Asset Server related tasks.
class AssetServerBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
static const bool LocklessDispatch = true;
//! This will return true if we were able to verify the server address as being valid, otherwise return false.
virtual bool IsServerAddressValid() = 0;
//! StoreJobResult should store all the files in the temp folder provided by the builderParams to the server
//! As well as any outputProducts which are outside the temp folder intended to be copied directly to the
//! Cache without going through the temp folder
//! It should associate those files with the server key provided by the builderParams because
//! it will be send the same server key to retrieve these files by the client.
//! This will return true if it was able to save all the relevant job data to the server, otherwise return false.
virtual bool StoreJobResult(const AssetProcessor::BuilderParams& builderParams, AZStd::vector<AZStd::string>& sourceFileList) = 0;
//! RetrieveJobResult should retrieve all the files associated with the server key provided in the builderParams
//! and put them in the temporary directory provided by the builderParam.
//! This will return true if it was able to retrieve all the relevant job data from the server, otherwise return false.
virtual bool RetrieveJobResult(const AssetProcessor::BuilderParams& builderParams) = 0;
};
using AssetServerBus = AZ::EBus<AssetServerBusTraits>;
} // namespace AssetProcessor
@@ -0,0 +1,195 @@
/*
* 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 "BatchApplicationManager.h"
#include <native/resourcecompiler/rccontroller.h>
#include <native/AssetManager/assetScanner.h>
#include <native/utilities/BatchApplicationServer.h>
#include <AzToolsFramework/UI/Logging/LogLine.h>
#include <QCoreApplication>
// in batch mode, we are going to show the log files of up to N failures.
// in order to not spam the logs, we limit this - its possible that something fundamental is broken and EVERY asset is failing
// and we don't want to thus write gigabytes of logs out.
const int s_MaximumFailuresToReport = 10;
#if defined(AZ_PLATFORM_WINDOWS)
namespace BatchApplicationManagerPrivate
{
BatchApplicationManager* g_appManager = nullptr;
BOOL WINAPI CtrlHandlerRoutine(DWORD dwCtrlType)
{
(void)dwCtrlType;
AZ_Printf("AssetProcessor", "Asset Processor Batch Processing Interrupted. Quitting.\n");
QMetaObject::invokeMethod(g_appManager, "QuitRequested", Qt::QueuedConnection);
return TRUE;
}
}
#endif //#if defined(AZ_PLATFORM_WINDOWS)
BatchApplicationManager::BatchApplicationManager(int* argc, char*** argv, QObject* parent)
: ApplicationManagerBase(argc, argv, parent)
{
AssetProcessor::MessageInfoBus::Handler::BusConnect();
}
BatchApplicationManager::~BatchApplicationManager()
{
AssetProcessor::MessageInfoBus::Handler::BusDisconnect();
}
void BatchApplicationManager::Destroy()
{
#if defined(AZ_PLATFORM_WINDOWS)
SetConsoleCtrlHandler((PHANDLER_ROUTINE)BatchApplicationManagerPrivate::CtrlHandlerRoutine, FALSE);
BatchApplicationManagerPrivate::g_appManager = nullptr;
#endif //#if defined(AZ_PLATFORM_WINDOWS)
ApplicationManagerBase::Destroy();
}
bool BatchApplicationManager::Activate()
{
#if defined(AZ_PLATFORM_WINDOWS)
BatchApplicationManagerPrivate::g_appManager = this;
SetConsoleCtrlHandler((PHANDLER_ROUTINE)BatchApplicationManagerPrivate::CtrlHandlerRoutine, TRUE);
#endif //defined(AZ_PLATFORM_WINDOWS)
return ApplicationManagerBase::Activate();
}
void BatchApplicationManager::OnErrorMessage([[maybe_unused]] const char* error)
{
AZ_Error("AssetProcessor", false, "%s", error);
}
void BatchApplicationManager::Reflect()
{
}
const char* BatchApplicationManager::GetLogBaseName()
{
return "AP_Batch";
}
ApplicationManager::RegistryCheckInstructions BatchApplicationManager::PopupRegistryProblemsMessage(QString warningText)
{
return RegistryCheckInstructions::Exit;
}
void BatchApplicationManager::InitSourceControl()
{
bool enableSourceControl = false;
const AzFramework::CommandLine* commandLine = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
if (commandLine->HasSwitch("enablescm"))
{
enableSourceControl = true;
}
if (enableSourceControl)
{
AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, true);
}
else
{
Q_EMIT SourceControlReady();
}
}
void BatchApplicationManager::MakeActivationConnections()
{
QObject::connect(m_rcController, &AssetProcessor::RCController::FileCompiled,
m_assetProcessorManager, [this](AssetProcessor::JobEntry entry, AssetBuilderSDK::ProcessJobResponse /*response*/)
{
m_processedAssetCount++;
AssetProcessor::JobDiagnosticInfo info{};
AssetProcessor::JobDiagnosticRequestBus::BroadcastResult(info, &AssetProcessor::JobDiagnosticRequestBus::Events::GetDiagnosticInfo, entry.m_jobRunKey);
m_warningCount += info.m_warningCount;
m_errorCount += info.m_errorCount;
});
QObject::connect(m_rcController, &AssetProcessor::RCController::FileFailed,
m_assetProcessorManager, [this](AssetProcessor::JobEntry entry)
{
m_failedAssetsCount++;
AssetProcessor::JobDiagnosticInfo info{};
AssetProcessor::JobDiagnosticRequestBus::BroadcastResult(info, &AssetProcessor::JobDiagnosticRequestBus::Events::GetDiagnosticInfo, entry.m_jobRunKey);
m_warningCount += info.m_warningCount;
m_errorCount += info.m_errorCount;
using AssetJobLogRequest = AzToolsFramework::AssetSystem::AssetJobLogRequest;
using AssetJobLogResponse = AzToolsFramework::AssetSystem::AssetJobLogResponse;
if (m_failedAssetsCount < s_MaximumFailuresToReport) // if we're in the situation where many assets are failing we need to stop spamming after a few
{
AssetJobLogRequest request;
AssetJobLogResponse response;
request.m_jobRunKey = entry.m_jobRunKey;
QMetaObject::invokeMethod(GetAssetProcessorManager(), "ProcessGetAssetJobLogRequest", Qt::DirectConnection, Q_ARG(const AssetJobLogRequest&, request), Q_ARG(AssetJobLogResponse&, response));
if (response.m_isSuccess)
{
// write the log to console!
AzToolsFramework::Logging::LogLine::ParseLog(response.m_jobLog.c_str(), response.m_jobLog.size(),
[](AzToolsFramework::Logging::LogLine& target)
{
// We're going to output *everything* because when a non-obvious error occurs, even mundane info output can be helpful for diagnosing the cause of the error
AZStd::string logString = target.ToString();
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "JOB LOG: %s", logString.c_str());
});
}
}
else if (m_failedAssetsCount == s_MaximumFailuresToReport)
{
// notify the user that we're done here, and will not be notifying any more.
AZ_Printf(AssetProcessor::ConsoleChannel, "%s\n", QCoreApplication::translate("Batch Mode", "Too Many Compile Errors - not printing out full logs for remaining errors").toUtf8().constData());
}
});
m_connectionsToRemoveOnShutdown << QObject::connect(m_assetScanner, &AssetProcessor::AssetScanner::AssetScanningStatusChanged, this,
[this](AssetProcessor::AssetScanningStatus status)
{
if ((status == AssetProcessor::AssetScanningStatus::Completed) || (status == AssetProcessor::AssetScanningStatus::Stopped))
{
AZ_Printf(AssetProcessor::ConsoleChannel, QCoreApplication::translate("Batch Mode", "Analyzing scanned files for changes...\n").toUtf8().constData());
CheckForIdle();
}
});
}
void BatchApplicationManager::TryScanProductDependencies()
{
if (!m_dependencyScanPattern.isEmpty())
{
m_assetProcessorManager->ScanForMissingProductDependencies(m_dependencyScanPattern, m_fileDependencyScanPattern, m_dependencyAddtionalScanFolders, m_dependencyScanMaxIteration);
m_dependencyScanPattern.clear();
}
}
void BatchApplicationManager::TryHandleFileRelocation()
{
HandleFileRelocation();
}
bool BatchApplicationManager::InitApplicationServer()
{
m_applicationServer = new BatchApplicationServer();
return true;
}
@@ -0,0 +1,55 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include "native/utilities/ApplicationManagerBase.h"
#endif
namespace AssetProcessor
{
extern const char ExcludeMetaDataFiles[];
}
class BatchApplicationManager
: public ApplicationManagerBase
, public AssetProcessor::MessageInfoBus::Handler
{
Q_OBJECT
public:
explicit BatchApplicationManager(int* argc, char*** argv, QObject* parent = 0);
virtual ~BatchApplicationManager();
void Destroy() override;
bool Activate() override;
////////////////////////////////////////////////////
///MessageInfoBus::Listener interface///////////////
void OnErrorMessage(const char* error) override;
///////////////////////////////////////////////////
bool InitApplicationServer() override;
private:
void Reflect() override;
const char* GetLogBaseName() override;
RegistryCheckInstructions PopupRegistryProblemsMessage(QString warningText) override;
void InitSourceControl() override;
void MakeActivationConnections() override;
bool GetShouldExitOnIdle() const override { return true; }
void TryScanProductDependencies() override;
void TryHandleFileRelocation() override;
};
@@ -0,0 +1,66 @@
/*
* 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 <native/utilities/BatchApplicationServer.h>
#include "native/utilities/assetUtils.h"
BatchApplicationServer::BatchApplicationServer(QObject* parent)
: ApplicationServer(parent)
{
}
BatchApplicationServer::~BatchApplicationServer()
{
}
bool BatchApplicationServer::startListening(unsigned short port)
{
if (!isListening())
{
if (port == 0)
{
quint16 listeningPort = AssetUtilities::ReadListeningPortFromSettingsRegistry();
m_serverListeningPort = static_cast<int>(listeningPort);
// In batch mode, make sure we use a different port from the GUI
++m_serverListeningPort;
}
else
{
// override the port
m_serverListeningPort = port;
}
// Since we're starting up builders ourselves and informing them of the port chosen, we can scan for a free port
while (!listen(QHostAddress::Any, m_serverListeningPort))
{
auto error = serverError();
if (error == QAbstractSocket::AddressInUseError)
{
++m_serverListeningPort;
}
else
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to start Asset Processor server. Error: %s", errorString().toStdString().c_str());
return false;
}
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Listening Port: %d\n", m_serverListeningPort);
ApplicationServerBus::Handler::BusConnect();
AZ_TracePrintf(AssetProcessor::DebugChannel, "Asset Processor server listening on port %d\n", m_serverListeningPort);
}
return true;
}
@@ -0,0 +1,29 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <native/utilities/ApplicationServer.h>
#endif
/** This Class is responsible for listening and getting new connections
*/
class BatchApplicationServer
: public ApplicationServer
{
Q_OBJECT
public:
explicit BatchApplicationServer(QObject* parent = 0);
~BatchApplicationServer() override;
bool startListening(unsigned short port = 0) override;
};
@@ -0,0 +1,45 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
namespace AssetProcessor
{
class BuilderConfigurationRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
using MutexType = AZStd::recursive_mutex;
virtual ~BuilderConfigurationRequests() = default;
//! Load configuration data from a specific BuilderConfig.ini file
virtual bool LoadConfiguration(const AZStd::string& /*configFile*/) { return false; }
//! Update a job descriptor given the configuration data which has been loaded
virtual bool UpdateJobDescriptor(const AZStd::string& /*jobKey*/, AssetBuilderSDK::JobDescriptor& /*jobDesc*/) { return false; }
//! Update a builder desc given configuration data
virtual bool UpdateBuilderDescriptor(const AZStd::string& /*builderName*/, AssetBuilderSDK::AssetBuilderDesc& /*jobDesc*/) { return false; }
};
using BuilderConfigurationRequestBus = AZ::EBus<BuilderConfigurationRequests>;
} // namespace AssetProcessor
@@ -0,0 +1,190 @@
/*
* 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 "BuilderConfigurationManager.h"
#include <QFile>
#include <QSettings>
namespace AssetProcessor
{
BuilderConfigurationManager::BuilderConfigurationManager()
{
BuilderConfigurationRequestBus::Handler::BusConnect();
}
BuilderConfigurationManager::~BuilderConfigurationManager()
{
BuilderConfigurationRequestBus::Handler::BusDisconnect();
}
bool BuilderConfigurationManager::LoadConfiguration(const AZStd::string& configFile)
{
if (!QFile::exists(configFile.c_str()))
{
AZ_Warning("BuilderConfiguration", false, "Couldn't load builder configuration file at %s", configFile.c_str());
return false;
}
QSettings loader(configFile.c_str(), QSettings::IniFormat);
QStringList groups = loader.childGroups();
for (QString group : groups)
{
const char JobGroupKey[] = "Job ";
if (group.startsWith(JobGroupKey, Qt::CaseInsensitive))
{
loader.beginGroup(group);
AZStd::string jobName = group.mid(static_cast<int>(strlen(JobGroupKey))).toStdString().c_str(); // Job name is after "job "
ParamMap thisMap;
for (const auto& thisKey : loader.allKeys())
{
thisMap[thisKey.toUtf8().data()] = loader.value(thisKey);
}
m_jobSettings[jobName] = thisMap;
loader.endGroup();
}
const char BuilderGroupKey[] = "Builder ";
if (group.startsWith(BuilderGroupKey, Qt::CaseInsensitive))
{
loader.beginGroup(group);
AZStd::string builderName = group.mid(static_cast<int>(strlen(BuilderGroupKey))).toStdString().c_str(); // Builder name is after "Builder"
ParamMap thisMap;
for (const auto& thisKey : loader.allKeys())
{
thisMap[AZStd::string(thisKey.toUtf8().data())] = QVariant(loader.value(thisKey));
}
m_builderSettings[builderName] = thisMap;
loader.endGroup();
}
}
m_loaded = true;
return true;
}
bool BuilderConfigurationManager::UpdateJobDescriptor(const AZStd::string& jobKey, AssetBuilderSDK::JobDescriptor& jobDesc)
{
auto jobEntry = m_jobSettings.find(jobKey);
if (jobEntry == m_jobSettings.end())
{
return false;
}
const auto& paramMap = jobEntry->second;
auto thisParam = paramMap.find("fingerprint");
if (thisParam != paramMap.end())
{
jobDesc.m_additionalFingerprintInfo = thisParam->second.toString().toStdString().c_str();
}
thisParam = paramMap.find("checkServer");
if (thisParam != paramMap.end())
{
jobDesc.m_checkServer = thisParam->second.toBool();
}
thisParam = paramMap.find("critical");
if (thisParam != paramMap.end())
{
jobDesc.m_critical = thisParam->second.toBool();;
}
thisParam = paramMap.find("priority");
if (thisParam != paramMap.end())
{
jobDesc.m_priority = thisParam->second.toInt();
}
thisParam = paramMap.find("checkExclusiveLock");
if (thisParam != paramMap.end())
{
jobDesc.m_checkExclusiveLock = thisParam->second.toBool();
}
thisParam = paramMap.find("params");
if (thisParam != paramMap.end())
{
QString patternString = thisParam->second.type() == QVariant::StringList ? thisParam->second.toStringList().join(",") : thisParam->second.toString();
if (patternString.length())
{
jobDesc.m_jobParameters.clear();
QStringList paramList = patternString.split(",");
for (const auto& stringParam : paramList)
{
QStringList paramVals = stringParam.split("=");
jobDesc.m_jobParameters[AZ_CRC(paramVals[0].toUtf8().data())] = paramVals.size() > 1 ? paramVals[1].toUtf8().data() : "";
}
}
}
return true;
}
bool BuilderConfigurationManager::UpdateBuilderDescriptor(const AZStd::string& builderName, AssetBuilderSDK::AssetBuilderDesc& builderDesc)
{
auto builderEntry = m_builderSettings.find(builderName);
if (builderEntry == m_builderSettings.end())
{
return false;
}
const auto& paramMap = builderEntry->second;
auto paramEntry = paramMap.find("fingerprint");
if (paramEntry != paramMap.end())
{
builderDesc.m_analysisFingerprint = paramEntry->second.toString().toStdString().c_str();
}
paramEntry = paramMap.find("version");
if (paramEntry != paramMap.end())
{
builderDesc.m_version = paramEntry->second.toInt();
}
paramEntry = paramMap.find("flags");
if (paramEntry != paramMap.end())
{
builderDesc.m_flags = static_cast<AZ::u8>(paramEntry->second.toInt());
}
paramEntry = builderEntry->second.find("patterns");
if (paramEntry != paramMap.end())
{
QString patternString = paramEntry->second.type() == QVariant::StringList ? paramEntry->second.toStringList().join(",") : paramEntry->second.toString();
if (patternString.length())
{
builderDesc.m_patterns.clear();
QStringList paramList = patternString.split(",");
for (const auto& thisParam : paramList)
{
AssetBuilderSDK::AssetBuilderPattern thisPattern;
QStringList paramVals = thisParam.split("=");
thisPattern.m_pattern = paramVals[0].toUtf8().data();
if (paramVals.length() > 1 && (paramVals[1] == "1" || paramVals[1].compare("regex", Qt::CaseInsensitive)))
{
thisPattern.m_type = AssetBuilderSDK::AssetBuilderPattern::Regex;
}
else
{
thisPattern.m_type = AssetBuilderSDK::AssetBuilderPattern::Wildcard;
}
builderDesc.m_patterns.emplace_back(AZStd::move(thisPattern));
}
}
}
return true;
}
} // namespace AssetProcessor
@@ -0,0 +1,50 @@
/*
* 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.
*
*/
#pragma once
#include <native/utilities/BuilderConfigurationBus.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <QVariant>
namespace AssetProcessor
{
const char BuilderConfigFile[] = "BuilderConfig.ini";
class BuilderConfigurationManager :
public BuilderConfigurationRequestBus::Handler
{
public:
using ParamMap = AZStd::unordered_map<AZStd::string, QVariant>;
using ConfigMap = AZStd::unordered_map <AZStd::string, ParamMap>;
BuilderConfigurationManager();
~BuilderConfigurationManager();
//BuilderConfigurationRequestBus
bool LoadConfiguration(const AZStd::string& configFile) override;
bool UpdateJobDescriptor(const AZStd::string& jobKey, AssetBuilderSDK::JobDescriptor& jobDesc) override;
bool UpdateBuilderDescriptor(const AZStd::string& builderName, AssetBuilderSDK::AssetBuilderDesc& jobDesc) override;
bool IsLoaded() const { return m_loaded; }
private:
ConfigMap m_builderSettings;
ConfigMap m_jobSettings;
bool m_loaded{ false };
};
} // namespace AssetProcessor
@@ -0,0 +1,550 @@
/*
* 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 "BuilderManager.h"
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <native/connection/connectionManager.h>
#include <native/connection/connection.h>
#include <native/utilities/AssetBuilderInfo.h>
#include <QCoreApplication>
namespace AssetProcessor
{
//! Amount of time in milliseconds to wait between checking the status of the AssetBuilder process and pumping the stdout/err pipes
static const int s_MaximumSleepTimeMS = 10;
//! Time in milliseconds to wait after each message pump cycle
static const int s_IdleBuilderPumpingDelayMS = 100;
//! Amount of time in seconds to wait for a builder to start up and connect
// sometimes, builders take a long time to start because of things like virus scanners scanning each
// builder DLL, so we give them a large margin.
static const int s_StartupConnectionWaitTimeS = 120;
static const int s_MillisecondsInASecond = 1000;
static const char* s_buildersFolderName = "Builders";
bool Builder::IsConnected() const
{
return m_connectionId > 0;
}
bool Builder::WaitForConnection()
{
if (m_connectionId == 0)
{
bool result = false;
QElapsedTimer ticker;
ticker.start();
while (!result)
{
result = m_connectionEvent.try_acquire_for(AZStd::chrono::milliseconds(s_MaximumSleepTimeMS));
PumpCommunicator();
if (ticker.elapsed() > s_StartupConnectionWaitTimeS * s_MillisecondsInASecond
|| m_quitListener.WasQuitRequested()
|| !IsRunning())
{
break;
}
}
PumpCommunicator();
FlushCommunicator();
if (result)
{
return true;
}
AZ::u32 exitCode;
if (m_quitListener.WasQuitRequested())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Aborting waiting for builder, quit requested\n");
}
else if (!IsRunning(&exitCode))
{
AZ_Error("Builder", false, "AssetBuilder terminated during start up with exit code %d", exitCode);
}
else
{
AZ_Error("Builder", false, "AssetBuilder failed to connect within %d seconds", s_StartupConnectionWaitTimeS);
}
return false;
}
return true;
}
void Builder::SetConnection(AZ::u32 connId)
{
m_connectionId = connId;
m_connectionEvent.release();
}
AZ::u32 Builder::GetConnectionId() const
{
return m_connectionId;
}
AZ::Uuid Builder::GetUuid() const
{
return m_uuid;
}
AZStd::string Builder::UuidString() const
{
return m_uuid.ToString<AZStd::string>(false, false);
}
void Builder::PumpCommunicator() const
{
if (m_tracePrinter)
{
m_tracePrinter->Pump();
}
}
void Builder::FlushCommunicator() const
{
if (m_tracePrinter)
{
// flush both STDOUT and STDERR
m_tracePrinter->WriteCurrentString(true);
m_tracePrinter->WriteCurrentString(false);
}
}
void Builder::TerminateProcess(AZ::u32 exitCode) const
{
if (m_processWatcher)
{
m_processWatcher->TerminateProcess(exitCode);
}
}
bool Builder::Start()
{
// Get the app root to locate the builders
AZStd::string appRootString;
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRootString, &AzFramework::ApplicationRequests::GetAppRoot);
// Get the current BinXXX folder based on the current running AP
QString applicationDir = QCoreApplication::instance()->applicationDirPath();
// Construct the Builders subfolder path
AZStd::string buildersFolder;
AzFramework::StringFunc::Path::Join(applicationDir.toUtf8().constData(), s_buildersFolderName, buildersFolder);
// Construct the full exe for the builder.exe
const AZStd::string fullExePathString = QDir(applicationDir).absoluteFilePath(AssetProcessor::s_assetBuilderRelativePath).toUtf8().constData();
if (m_quitListener.WasQuitRequested())
{
return false;
}
const AZStd::string params = BuildParams("resident", buildersFolder.c_str(), UuidString(), "", "");
m_processWatcher = LaunchProcess(fullExePathString.c_str(), params);
if (!m_processWatcher)
{
return false;
}
m_tracePrinter = AZStd::make_unique<CommunicatorTracePrinter>(m_processWatcher->GetCommunicator(), "AssetBuilder");
return WaitForConnection();
}
bool Builder::IsValid() const
{
return m_connectionId != 0 && IsRunning();
}
bool Builder::IsRunning(AZ::u32* exitCode) const
{
return !m_processWatcher || (m_processWatcher && m_processWatcher->IsProcessRunning(exitCode));
}
AZStd::string Builder::BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile) const
{
QDir projectCacheRoot;
AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot);
QString gameName = AssetUtilities::ComputeGameName();
AZStd::string appRootString;
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRootString, &AzFramework::ApplicationRequests::GetAppRoot);
QDir appRoot(QString(appRootString.c_str()));
QString gameRoot = appRoot.absoluteFilePath(gameName);
int portNumber = 0;
ApplicationServerBus::BroadcastResult(portNumber, &ApplicationServerBus::Events::GetServerListeningPort);
AZStd::string params;
#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
params = AZStd::string::format(R"(-task=%s -id="%s" -gamename="%s" -gamecache="%s" -gameroot="%s" -port %d)",
task, builderGuid.c_str(), gameName.toUtf8().constData(), projectCacheRoot.absolutePath().toUtf8().constData(), gameRoot.toUtf8().constData(), portNumber);
#else
params = AZStd::string::format(R"(-task=%s -id="%s" -gamename="\"%s\"" -gamecache="\"%s\"" -gameroot="\"%s\"" -port %d)",
task, builderGuid.c_str(), gameName.toUtf8().constData(), projectCacheRoot.absolutePath().toUtf8().constData(), gameRoot.toUtf8().constData(), portNumber);
#endif // !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
if (moduleFilePath && moduleFilePath[0])
{
#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
params.append(AZStd::string::format(R"( -module="%s")", moduleFilePath).c_str());
#else
params.append(AZStd::string::format(R"( -module="\"%s\"")", moduleFilePath).c_str());
#endif // !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
}
if (!jobDescriptionFile.empty() && !jobResponseFile.empty())
{
#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
params = AZStd::string::format(R"(%s -input="%s" -output="%s")", params.c_str(), jobDescriptionFile.c_str(), jobResponseFile.c_str());
#else
params = AZStd::string::format(R"(%s -input="\"%s\"" -output="\"%s\"")", params.c_str(), jobDescriptionFile.c_str(), jobResponseFile.c_str());
#endif // !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
}
return params;
}
AZStd::unique_ptr<AzToolsFramework::ProcessWatcher> Builder::LaunchProcess(const char* fullExePath, const AZStd::string& params) const
{
AzToolsFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_processExecutableString = fullExePath;
processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" %s", fullExePath, params.c_str());
processLaunchInfo.m_showWindow = false;
processLaunchInfo.m_processPriority = AzToolsFramework::ProcessPriority::PROCESSPRIORITY_IDLE;
AZ_TracePrintf(AssetProcessor::DebugChannel, "Executing AssetBuilder with parameters: %s\n", processLaunchInfo.m_commandlineParameters.c_str());
auto processWatcher = AZStd::unique_ptr<AzToolsFramework::ProcessWatcher>(AzToolsFramework::ProcessWatcher::LaunchProcess(processLaunchInfo, AzToolsFramework::COMMUNICATOR_TYPE_STDINOUT));
AZ_Error(AssetProcessor::ConsoleChannel, processWatcher, "Failed to start %s", fullExePath);
return processWatcher;
}
BuilderRunJobOutcome Builder::WaitForBuilderResponse(AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds, AZStd::binary_semaphore* waitEvent) const
{
AZ::u32 exitCode = 0;
bool finishedOK = false;
QElapsedTimer ticker;
ticker.start();
while (!finishedOK)
{
finishedOK = waitEvent->try_acquire_for(AZStd::chrono::milliseconds(s_MaximumSleepTimeMS));
PumpCommunicator();
if (!IsValid() || ticker.elapsed() > processTimeoutLimitInSeconds * s_MillisecondsInASecond || (jobCancelListener && jobCancelListener->IsCancelled()))
{
break;
}
}
PumpCommunicator();
FlushCommunicator();
if (finishedOK)
{
return BuilderRunJobOutcome::Ok;
}
else if (!IsConnected())
{
AZ_Error("Builder", false, "Lost connection to asset builder");
return BuilderRunJobOutcome::LostConnection;
}
else if (!IsRunning(&exitCode))
{
// these are written to the debug channel because other messages are given for when asset builders die
// that are more appropriate
AZ_Error("Builder", false, "AssetBuilder terminated with exit code %d", exitCode);
return BuilderRunJobOutcome::ProcessTerminated;
}
else if (jobCancelListener && jobCancelListener->IsCancelled())
{
AZ_Error("Builder", false, "Job request was cancelled");
TerminateProcess(AZ::u32(-1)); // Terminate the builder. Even if it isn't deadlocked, we can't put it back in the pool while it's busy.
return BuilderRunJobOutcome::JobCancelled;
}
else
{
AZ_Error("Builder", false, "AssetBuilder failed to respond within %d seconds", processTimeoutLimitInSeconds);
TerminateProcess(AZ::u32(-1)); // Terminate the builder. Even if it isn't deadlocked, we can't put it back in the pool while it's busy.
return BuilderRunJobOutcome::ResponseFailure;
}
}
//////////////////////////////////////////////////////////////////////////
BuilderRef::BuilderRef(const AZStd::shared_ptr<Builder>& builder)
: m_builder(builder)
{
if (m_builder)
{
m_builder->m_busy = true;
}
}
BuilderRef::BuilderRef(BuilderRef&& rhs)
: m_builder(AZStd::move(rhs.m_builder))
{
}
BuilderRef& BuilderRef::operator=(BuilderRef&& rhs)
{
m_builder = AZStd::move(rhs.m_builder);
return *this;
}
BuilderRef::~BuilderRef()
{
if (m_builder)
{
AZ_Warning("BuilderRef", m_builder->m_busy, "Builder reference is valid but is already set to not busy");
m_builder->m_busy = false;
m_builder = nullptr;
}
}
const Builder* BuilderRef::operator->() const
{
return m_builder.get();
}
BuilderRef::operator bool() const
{
return m_builder != nullptr;
}
//////////////////////////////////////////////////////////////////////////
BuilderManager::BuilderManager(ConnectionManager* connectionManager)
{
using namespace AZStd::placeholders;
connectionManager->RegisterService(AssetBuilderSDK::BuilderHelloRequest::MessageType(), AZStd::bind(&BuilderManager::IncomingBuilderPing, this, _1, _2, _3, _4, _5));
// Setup a background thread to pump the idle builders so they don't get blocked trying to output to stdout/err
m_pollingThread = AZStd::thread([this]()
{
while (!m_quitListener.WasQuitRequested())
{
PumpIdleBuilders();
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(s_IdleBuilderPumpingDelayMS));
}
});
m_quitListener.BusConnect();
BusConnect();
}
BuilderManager::~BuilderManager()
{
BusDisconnect();
m_quitListener.BusDisconnect();
m_quitListener.ApplicationShutdownRequested();
if (m_pollingThread.joinable())
{
m_pollingThread.join();
}
}
void BuilderManager::ConnectionLost(AZ::u32 connId)
{
AZ_Assert(connId > 0, "ConnectionId was 0");
AZStd::lock_guard<AZStd::mutex> lock(m_buildersMutex);
for (auto itr = m_builders.begin(); itr != m_builders.end(); ++itr)
{
auto& builder = itr->second;
if (builder->GetConnectionId() == connId)
{
AZ_TracePrintf("BuilderManager", "Lost connection to builder %s\n", builder->UuidString().c_str());
builder->m_connectionId = 0;
m_builders.erase(itr);
break;
}
}
}
void BuilderManager::IncomingBuilderPing(AZ::u32 connId, AZ::u32 /*type*/, AZ::u32 serial, QByteArray payload, QString platform)
{
AssetBuilderSDK::BuilderHelloRequest requestPing;
AssetBuilderSDK::BuilderHelloResponse responsePing;
if (!AZ::Utils::LoadObjectFromBufferInPlace(payload.data(), payload.length(), requestPing))
{
AZ_Error("BuilderManager", false,
"Failed to deserialize BuilderHelloRequest.\n"
"Your builder(s) may need recompilation to function correctly as this kind of failure usually indicates that "
"there is a disparity between the version of asset processor running and the version of builder dll files present in the "
"'builders' subfolder.");
}
else
{
AZStd::lock_guard<AZStd::mutex> lock(m_buildersMutex);
AZStd::shared_ptr<Builder> builder;
auto itr = m_builders.find(requestPing.m_uuid);
if (itr != m_builders.end())
{
builder = itr->second;
}
else if (m_allowUnmanagedBuilderConnections)
{
AZ_TracePrintf("BuilderManager", "External builder connection accepted\n");
builder = AddNewBuilder();
}
else
{
AZ_Warning("BuilderManager", false, "Received request ping from builder but could not match uuid %s", requestPing.m_uuid.ToString<AZStd::string>().c_str());
}
if (builder)
{
if (builder->IsConnected())
{
AZ_Error("BuilderManager", false, "Builder %s is already connected and should not be sending another ping. Something has gone wrong. There may be multiple builders with the same UUID", builder->UuidString().c_str());
}
else
{
AZ_TracePrintf("BuilderManager", "Builder %s connected, connId: %d\n", builder->UuidString().c_str(), connId);
builder->SetConnection(connId);
responsePing.m_accepted = true;
responsePing.m_uuid = builder->GetUuid();
}
}
}
AssetProcessor::ConnectionBus::Event(connId, &AssetProcessor::ConnectionBusTraits::SendResponse, serial, responsePing);
}
AZStd::shared_ptr<Builder> BuilderManager::AddNewBuilder()
{
AZ::Uuid builderUuid;
// Make sure that we don't already have a builder with the same UUID. If we do, try generating another one
constexpr int MaxRetryCount = 10;
int retriesRemaining = MaxRetryCount;
do
{
builderUuid = AZ::Uuid::CreateRandom();
--retriesRemaining;
} while (m_builders.find(builderUuid) != m_builders.end() && retriesRemaining > 0);
if(m_builders.find(builderUuid) != m_builders.end())
{
AZ_Error("BuilderManager", false, "Failed to generate a unique id for new builder after %d attempts. All attempted random ids were already taken.", MaxRetryCount);
return {};
}
auto builder = AZStd::make_shared<Builder>(m_quitListener, builderUuid);
m_builders.insert({ builder->GetUuid(), builder });
return builder;
}
BuilderRef BuilderManager::GetBuilder()
{
AZStd::shared_ptr<Builder> newBuilder;
BuilderRef builderRef;
{
AZStd::unique_lock<AZStd::mutex> lock(m_buildersMutex);
for (auto itr = m_builders.begin(); itr != m_builders.end(); )
{
auto& builder = itr->second;
if (!builder->m_busy)
{
builder->PumpCommunicator();
if (builder->IsValid())
{
return BuilderRef(builder);
}
else
{
itr = m_builders.erase(itr);
}
}
else
{
++itr;
}
}
AZ_TracePrintf("BuilderManager", "Starting new builder for job request\n");
// None found, start up a new one
newBuilder = AddNewBuilder();
// Grab a reference so no one else can take it while we're outside the lock
builderRef = BuilderRef(newBuilder);
}
if (!newBuilder->Start())
{
AZ_Error("BuilderManager", false, "Builder failed to start");
AZStd::unique_lock<AZStd::mutex> lock(m_buildersMutex);
builderRef = {}; // Release after the lock to make sure no one grabs it before we can delete it
m_builders.erase(newBuilder->GetUuid());
}
else
{
AZ_TracePrintf("BuilderManager", "Builder started successfully\n");
}
return builderRef;
}
void BuilderManager::PumpIdleBuilders()
{
AZStd::lock_guard<AZStd::mutex> lock(m_buildersMutex);
for (auto pair : m_builders)
{
auto builder = pair.second;
if (!builder->m_busy)
{
builder->PumpCommunicator();
}
}
}
} // namespace AssetProcessor
@@ -0,0 +1,203 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/string/string.h>
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzToolsFramework/Process/ProcessWatcher.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <QString>
#include <QByteArray>
#include <native/utilities/CommunicatorTracePrinter.h>
#include <native/utilities/assetUtils.h>
#include <QDir> // used in the inl file.
class ConnectionManager;
namespace AssetProcessor
{
struct BuilderRef;
//! Indicates if job request files should be created on success. Can be useful for debugging
static const bool s_createRequestFileForSuccessfulJob = false;
//! This EBUS is used to request a free builder from the builder manager pool
class BuilderManagerBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
using MutexType = AZStd::recursive_mutex;
virtual ~BuilderManagerBusTraits() = default;
//! Returns a builder for doing work
virtual BuilderRef GetBuilder() = 0;
};
using BuilderManagerBus = AZ::EBus<BuilderManagerBusTraits>;
enum class BuilderRunJobOutcome
{
Ok,
LostConnection,
ProcessTerminated,
JobCancelled,
ResponseFailure,
FailedToDecodeResponse,
FailedToWriteDebugRequest
};
//! Wrapper for managing a single builder process and sending job requests to it
class Builder
{
friend class BuilderManager;
friend struct BuilderRef;
public:
Builder(const AssetUtilities::QuitListener& quitListener, AZ::Uuid uuid)
: m_uuid(uuid),
m_quitListener(quitListener)
{}
~Builder() = default;
// Disable copy and move (can't move a semaphore)
AZ_DISABLE_COPY_MOVE(Builder);
//! Returns true if the builder has a valid connection id and, if there is a process associated, the process is running
bool IsValid() const;
//! Returns true if the builder has a process watcher and the process is running OR does not have a process watcher. Returns false otherwise
bool IsRunning(AZ::u32* exitCode = nullptr) const;
//! Returns true if the builder exe has established a connection
bool IsConnected() const;
//! Blocks waiting for the builder to establish a connection
bool WaitForConnection();
AZ::u32 GetConnectionId() const;
AZ::Uuid GetUuid() const;
AZStd::string UuidString() const;
void PumpCommunicator() const;
void FlushCommunicator() const;
void TerminateProcess(AZ::u32 exitCode) const;
//! Sends the job over to the builder and blocks until the response is received or the builder crashes/times out
template<typename TNetRequest, typename TNetResponse, typename TRequest, typename TResponse>
BuilderRunJobOutcome RunJob(const TRequest& request, TResponse& response, AZ::u32 processTimeoutLimitInSeconds, const AZStd::string& task, const AZStd::string& modulePath, AssetBuilderSDK::JobCancelListener* jobCancelListener = nullptr, AZStd::string tempFolderPath = AZStd::string()) const;
private:
//! Starts the builder process and waits for it to connect
bool Start();
//! Sets the connection id and signals that the builder has connected
void SetConnection(AZ::u32 connId);
AZStd::string BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile) const;
AZStd::unique_ptr<AzToolsFramework::ProcessWatcher> LaunchProcess(const char* fullExePath, const AZStd::string& params) const;
//! Waits for the builder exe to send the job response and pumps stdout/err
BuilderRunJobOutcome WaitForBuilderResponse(AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds, AZStd::binary_semaphore* waitEvent) const;
//! Writes the request out to disk for debug purposes and logs info on how to manually run the asset builder
template<typename TRequest>
bool DebugWriteRequestFile(QString tempFolderPath, const TRequest& request, const AZStd::string& task, const AZStd::string& modulePath) const;
const AZ::Uuid m_uuid;
//! Indicates if the builder is currently in use
bool m_busy = false;
AZStd::atomic<AZ::u32> m_connectionId = 0;
//! Signals the exe has successfully established a connection
AZStd::binary_semaphore m_connectionEvent;
//! Optional process watcher
AZStd::unique_ptr<AzToolsFramework::ProcessWatcher> m_processWatcher = nullptr;
//! Optional communicator, only available if we have a process watcher
AZStd::unique_ptr<CommunicatorTracePrinter> m_tracePrinter = nullptr;
const AssetUtilities::QuitListener& m_quitListener;
};
//! Scoped reference to a builder. Destructor returns the builder to the free builders pool
struct BuilderRef
{
BuilderRef() = default;
explicit BuilderRef(const AZStd::shared_ptr<Builder>& builder);
~BuilderRef();
// Disable copy
BuilderRef(const BuilderRef&) = delete;
BuilderRef& operator=(const BuilderRef&) = delete;
// Allow move
BuilderRef(BuilderRef&&);
BuilderRef& operator=(BuilderRef&&);
const Builder* operator->() const;
explicit operator bool() const;
private:
AZStd::shared_ptr<Builder> m_builder = nullptr;
};
//! Manages the builder pool
class BuilderManager
: public BuilderManagerBus::Handler
{
public:
explicit BuilderManager(ConnectionManager* connectionManager);
~BuilderManager();
// Disable copy
AZ_DISABLE_COPY_MOVE(BuilderManager);
void ConnectionLost(AZ::u32 connId);
//BuilderManagerBus
BuilderRef GetBuilder() override;
private:
//! Makes a new builder, adds it to the pool, and returns a shared pointer to it
AZStd::shared_ptr<Builder> AddNewBuilder();
//! Handles incoming builder connections
void IncomingBuilderPing(AZ::u32 connId, AZ::u32 type, AZ::u32 serial, QByteArray payload, QString platform);
void PumpIdleBuilders();
AZStd::mutex m_buildersMutex;
//! Map of builders, keyed by the builder's unique ID. Must be locked before accessing
AZStd::unordered_map<AZ::Uuid, AZStd::shared_ptr<Builder>> m_builders;
//! Indicates if we allow builders to connect that we haven't started up ourselves. Useful for debugging
bool m_allowUnmanagedBuilderConnections = false;
//! Responsible for going through all the idle builders and pumping their communicators so they don't stall
AZStd::thread m_pollingThread;
AssetUtilities::QuitListener m_quitListener;
};
} // namespace AssetProcessor
#include "native/utilities/BuilderManager.inl"
@@ -0,0 +1,96 @@
/*
* 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.
*
*/
#pragma once
namespace AssetProcessor
{
//! Sends the job over to the builder and blocks until the response is received or the builder crashes/times out
template<typename TNetRequest, typename TNetResponse, typename TRequest, typename TResponse>
BuilderRunJobOutcome Builder::RunJob(const TRequest& request, TResponse& response, AZ::u32 processTimeoutLimitInSeconds, const AZStd::string& task, const AZStd::string& modulePath, AssetBuilderSDK::JobCancelListener* jobCancelListener /*= nullptr*/, AZStd::string tempFolderPath /*= AZStd::string()*/) const
{
TNetRequest netRequest;
TNetResponse netResponse;
netRequest.m_request = request;
AZ::u32 type;
QByteArray data;
AZStd::binary_semaphore wait;
unsigned int serial;
AssetProcessor::ConnectionBus::EventResult(serial, m_connectionId, &AssetProcessor::ConnectionBusTraits::SendRequest, netRequest, [&](AZ::u32 msgType, QByteArray msgData)
{
type = msgType;
data = msgData;
wait.release();
});
BuilderRunJobOutcome result = WaitForBuilderResponse(jobCancelListener, processTimeoutLimitInSeconds, &wait);
if (result != BuilderRunJobOutcome::Ok)
{
// Clear out the response handler so it doesn't get triggered after the variables go out of scope (also to clean up the memory)
AssetProcessor::ConnectionBus::Event(m_connectionId, &AssetProcessor::ConnectionBusTraits::RemoveResponseHandler, serial);
return result;
}
AZ_Assert(type == netRequest.GetMessageType(), "Response type does not match");
if (!AZ::Utils::LoadObjectFromBufferInPlace(data.data(), data.length(), netResponse))
{
AZ_Error("Builder", false, "Failed to deserialize processJobs response");
return BuilderRunJobOutcome::FailedToDecodeResponse;
}
if (!netResponse.m_response.Succeeded() || s_createRequestFileForSuccessfulJob)
{
// we write the request out to disk for failure or debugging
if (!DebugWriteRequestFile(tempFolderPath.c_str(), request, task, modulePath))
{
return BuilderRunJobOutcome::FailedToWriteDebugRequest;
}
}
response = AZStd::move(netResponse.m_response);
return result;
}
template<typename TRequest>
bool Builder::DebugWriteRequestFile(QString tempFolderPath, const TRequest& request, const AZStd::string& task, const AZStd::string& modulePath) const
{
if (tempFolderPath.isEmpty())
{
if (!AssetUtilities::CreateTempWorkspace(tempFolderPath))
{
AZ_Error("Builder", false, "Failed to create temporary workspace to execute builder task");
return false;
}
}
const QDir tempFolder = QDir(tempFolderPath);
const AZStd::string jobRequestFile = tempFolder.filePath("request.xml").toStdString().c_str();
const AZStd::string jobResponseFile = tempFolder.filePath("response.xml").toStdString().c_str();
if (!AZ::Utils::SaveObjectToFile(jobRequestFile, AZ::DataStream::ST_XML, &request))
{
AZ_Error("Builder", false, "Failed to save request to file: %s", jobRequestFile.c_str());
return false;
}
auto params = BuildParams(task.c_str(), modulePath.c_str(), "", jobRequestFile, jobResponseFile);
AZ_TracePrintf(AssetProcessor::DebugChannel, "Job request written to %s\n", jobRequestFile.c_str());
AZ_TracePrintf(AssetProcessor::DebugChannel, "To re-run this request manually, run AssetBuilder with the following parameters:\n");
AZ_TracePrintf(AssetProcessor::DebugChannel, "%s\n", params.c_str());
return true;
}
} // namespace AssetProcessor
@@ -0,0 +1,196 @@
/*
* 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 "native/utilities/ByteArrayStream.h"
namespace AssetProcessor
{
using namespace AZ::IO;
ByteArrayStream::ByteArrayStream()
{
m_usingOwnArray = true;
m_activeArray = &m_ownArray;
m_currentPos = 0;
m_readOnly = false;
}
ByteArrayStream::ByteArrayStream(QByteArray* other)
{
m_activeArray = other;
m_usingOwnArray = false;
m_currentPos = m_activeArray->size();
m_readOnly = false;
}
ByteArrayStream::ByteArrayStream(const char* data, unsigned int length)
{
m_activeArray = &m_ownArray;
m_ownArray.setRawData(data, length);
m_usingOwnArray = true;
m_currentPos = 0;
m_readOnly = true;
}
void ByteArrayStream::Reserve(int amount)
{
if (m_readOnly)
{
return;
}
m_activeArray->reserve(amount);
}
void ByteArrayStream::Seek(OffsetType bytes, SeekMode mode)
{
SizeType finalPosition = GenericStream::ComputeSeekPosition(bytes, mode);
AZ_Assert(finalPosition < INT_MAX, "Overflow of SizeType to int in ByteArrayStream.");
AZ_Assert(finalPosition >= 0, "underflow in seek in ByteArrayStream");
AZ_Assert(finalPosition <= m_activeArray->size(), "You cant seek beyond end of file");
// safety clamp!
finalPosition = AZ::GetClamp<SizeType>(finalPosition, 0, static_cast<SizeType>(m_activeArray->size()));
m_currentPos = static_cast<int>(finalPosition);
}
SizeType ByteArrayStream::Read(SizeType bytes, void* oBuffer)
{
// eg
// xxxxx <-- data, size() 5
// ^ <-- pos, currently 2
// we have 3 bytes available .. 5 - 2.
int actualAvailableBytes = m_activeArray->size() - m_currentPos;
if (actualAvailableBytes <= 0)
{
return static_cast<SizeType>(0);
}
const char* data = m_activeArray->constData();
data += m_currentPos;
AZ_Assert(bytes < std::numeric_limits<int>::max(), "Overflow in ByteArrayStream::Read.");
// safety cast.
bytes = AZ::GetMin(static_cast<SizeType>(std::numeric_limits<int>::max()), bytes);
int bytesToRead = AZ::GetMin<int>(static_cast<int>(bytes), actualAvailableBytes);
memcpy(oBuffer, data, bytesToRead);
m_currentPos += bytesToRead;
return bytesToRead;
}
SizeType ByteArrayStream::PrepareForWrite(SizeType bytes)
{
// how much bigger does our array have to grow?
// example
// oooooooo <---- capacity = 8
// xxxxx <--- size = 5
// ^ <--- currentpos = 2 (3rd byte)
// if we're asking for a 10 byte write the final picture will be
// xxyyyyyyyyyy <--- size() = 12.
if (m_readOnly)
{
return 0;
}
SizeType intMaxSize = std::numeric_limits<int>::max();
SizeType finalSize = bytes + static_cast<SizeType>(m_currentPos);
AZ_Assert(finalSize < intMaxSize, "Overflow in ByteArrayStream::Write");
if (finalSize > intMaxSize)
{
SizeType delta = finalSize - intMaxSize;
finalSize -= delta;
bytes -= delta;
}
int intSize = static_cast<int>(finalSize);
if (intSize > m_activeArray->capacity())
{
// grow the array, but let's be smart about it.
// assume there'll be another write the same size pretty soon.
// we'd like to grow it by about a quarter of its current size
// thus if we're making one LARGE write, it grows 0
int growthAmount = intSize / 4;
// don't allow overflow here either.
if (static_cast<SizeType>(growthAmount) + static_cast<SizeType>(intSize) >= intMaxSize)
{
growthAmount = 0;
}
m_activeArray->reserve(intSize + growthAmount);
}
if (intSize > m_activeArray->size())
{
m_activeArray->resize(intSize);
}
return bytes;
}
SizeType ByteArrayStream::Write(SizeType bytes, const void* iBuffer)
{
bytes = PrepareForWrite(bytes);
if (bytes > 0)
{
char* data = m_activeArray->data();
data += m_currentPos;
memcpy(data, iBuffer, bytes);
m_currentPos += static_cast<int>(bytes);
}
return bytes;
}
SizeType ByteArrayStream::WriteFromStream(SizeType bytes, AZ::IO::GenericStream* inputStream)
{
AZ_Assert(inputStream, "Cannot copy from a null input stream.");
AZ_Assert(inputStream != this, "Can't write and read from the same stream.");
bytes = PrepareForWrite(bytes);
if (bytes > 0)
{
char* data = m_activeArray->data();
data += m_currentPos;
bytes = inputStream->Read(bytes, data);
m_currentPos += static_cast<int>(bytes);
}
return bytes;
}
SizeType ByteArrayStream::GetCurPos() const
{
return static_cast<SizeType>(m_currentPos);
}
SizeType ByteArrayStream::GetLength() const
{
return static_cast<SizeType>(m_activeArray->size());
}
QByteArray ByteArrayStream::GetArray() const
{
if (m_usingOwnArray)
{
return m_ownArray;
}
return *m_activeArray;
}
}
@@ -0,0 +1,90 @@
/*
* 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.
*
*/
#ifndef ASSETBUILDER_BYTEARRAYSTREAM
#define ASSETBUILDER_BYTEARRAYSTREAM
#include <AzCore/IO/GenericStreams.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
#include <QByteArray>
namespace AssetProcessor
{
//! Wrap a QByteArray (which has an int-interface) in a GenericStream.
class ByteArrayStream
: public AZ::IO::GenericStream
{
public:
ByteArrayStream();
ByteArrayStream(QByteArray* other); // attach to external
ByteArrayStream(const char* data, unsigned int length); // const attach to read-only buffer
bool IsOpen() const override
{
return true;
}
bool CanSeek() const override
{
return true;
}
virtual bool CanRead() const override { return true; }
virtual bool CanWrite() const override { return true; }
void Seek(AZ::IO::OffsetType bytes, SeekMode mode) override;
AZ::IO::SizeType Read(AZ::IO::SizeType bytes, void* oBuffer) override;
AZ::IO::SizeType Write(AZ::IO::SizeType bytes, const void* iBuffer) override;
AZ::IO::SizeType WriteFromStream(AZ::IO::SizeType bytes, AZ::IO::GenericStream *inputStream) override;
AZ::IO::SizeType GetCurPos() const override;
AZ::IO::SizeType GetLength() const override;
QByteArray GetArray() const; // bytearrays are copy-on-write so retrieving it is akin to retreiving a refcounted object, its cheap to 'copy'
void Reserve(int amount); // for performance.
private:
AZ::IO::SizeType PrepareForWrite(AZ::IO::SizeType bytes);
QByteArray* m_activeArray;
QByteArray m_ownArray; // used when not constructed around an attached array
bool m_usingOwnArray = true; // if false, its been attached
int m_currentPos = 0; // the byte array underlying has only ints :(
bool m_readOnly = false;
};
// Pack any serializable type into a QByteArray
// note that this is not a specialization of the AZFramework version of this function
// because C++ does not support partial specialization of function templates, only classes.
template <class Message>
bool PackMessage(const Message& message, QByteArray& buffer)
{
ByteArrayStream byteStream(&buffer);
return AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, &message, message.RTTI_GetType());
}
// Unpack any serializable type from a QByteArray
// note that this is not a specialization of the AZFramework version of this function
// because C++ does not support partial specialization of function templates, only classes.
template <class Message>
bool UnpackMessage(const QByteArray& buffer, Message& message)
{
ByteArrayStream byteStream(buffer.constData(), buffer.size());
// we expect network messages to be pristine - so if there's any error, don't allow it!
// also do not allow it to load assets just becuase they're in fields
AZ::ObjectStream::FilterDescriptor filterToUse(&AZ::Data::AssetFilterNoAssetLoading, AZ::ObjectStream::FILTERFLAG_STRICT);
return AZ::Utils::LoadObjectFromStreamInPlace(byteStream, message, nullptr, filterToUse);
}
}
#endif // ASSETBUILDER_BYTEARRAYSTREAM
@@ -0,0 +1,90 @@
/*
* 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 "CommunicatorTracePrinter.h"
CommunicatorTracePrinter::CommunicatorTracePrinter(AzToolsFramework::ProcessCommunicator* communicator, const char* window) :
m_communicator(communicator),
m_window(window)
{
m_stringBeingConcatenated.reserve(1024);
}
CommunicatorTracePrinter::~CommunicatorTracePrinter()
{
// flush stdout
WriteCurrentString(false);
// flush stderr
WriteCurrentString(true);
}
void CommunicatorTracePrinter::Pump()
{
if (m_communicator->IsValid())
{
// Don't call readOutput unless there is output or else it will block...
while (m_communicator->PeekOutput())
{
AZ::u32 readSize = m_communicator->ReadOutput(m_streamBuffer, AZ_ARRAY_SIZE(m_streamBuffer));
ParseDataBuffer(readSize, false);
}
while (m_communicator->PeekError())
{
AZ::u32 readSize = m_communicator->ReadError(m_streamBuffer, AZ_ARRAY_SIZE(m_streamBuffer));
ParseDataBuffer(readSize, true);
}
}
}
void CommunicatorTracePrinter::ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr)
{
if (readSize > AZ_ARRAY_SIZE(m_streamBuffer))
{
AZ_ErrorOnce("ERROR", false, "Programmer bug: Read size is overflowing in traceprintf communicator.");
return;
}
// we cannot write the string to the same buffer, as stdError and stdOut are different streams and could
// have different cutting points as buffers empty.
AZStd::string& bufferToUse = isFromStdErr ? m_errorStringBeingConcatenated : m_stringBeingConcatenated;
for (size_t pos = 0; pos < readSize; ++pos)
{
if ((m_streamBuffer[pos] == '\n') || (m_streamBuffer[pos] == '\r'))
{
WriteCurrentString(isFromStdErr);
}
else
{
bufferToUse.push_back(m_streamBuffer[pos]);
}
}
}
void CommunicatorTracePrinter::WriteCurrentString(bool isFromStdErr)
{
AZStd::string& bufferToUse = isFromStdErr ? m_errorStringBeingConcatenated : m_stringBeingConcatenated;
if (!bufferToUse.empty())
{
if (isFromStdErr)
{
AZ_Error(m_window.c_str(), false, "%s", bufferToUse.c_str());
}
else
{
AZ_TracePrintf(m_window.c_str(), "%s", bufferToUse.c_str());
}
bufferToUse.clear();
}
}
@@ -0,0 +1,39 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/Process/ProcessCommunicator.h>
//! CommunicatorTracePrinter listens to stderr and stdout of a running process and writes its output to the AZ_Trace system
//! Importantly, it does not do any blocking operations.
class CommunicatorTracePrinter
{
public:
CommunicatorTracePrinter(AzToolsFramework::ProcessCommunicator* communicator, const char* window);
~CommunicatorTracePrinter();
// call this periodically to drain the buffers and write them.
void Pump();
// drains the buffer into the string thats being built, then traces the string when it hits a newline.
void ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr);
void WriteCurrentString(bool isFromStdError);
private:
AZStd::string m_window;
AzToolsFramework::ProcessCommunicator* m_communicator;
char m_streamBuffer[128];
AZStd::string m_stringBeingConcatenated;
AZStd::string m_errorStringBeingConcatenated;
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,118 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include "native/utilities/ApplicationManagerBase.h"
#include "native/utilities/AssetUtilEBusHelper.h"
#include "native/FileWatcher/FileWatcher.h"
#include <QMap>
#include <QAtomicInt>
#include <QFileSystemWatcher>
#include <AzCore/UserSettings/UserSettingsProvider.h>
#include <native/ui/MainWindow.h>
#include <QSystemTrayIcon>
#endif
class ConnectionManager;
class IniConfiguration;
class ApplicationServer;
class FileServer;
class ShaderCompilerManager;
class ShaderCompilerModel;
namespace AssetProcessor
{
class AssetRequestHandler;
}
//! This class is the Application manager for the GUI Mode
class GUIApplicationManager
: public ApplicationManagerBase
, public AssetProcessor::MessageInfoBus::Handler
{
Q_OBJECT
public:
explicit GUIApplicationManager(int* argc, char*** argv, QObject* parent = 0);
virtual ~GUIApplicationManager();
ApplicationManager::BeforeRunStatus BeforeRun() override;
IniConfiguration* GetIniConfiguration() const;
FileServer* GetFileServer() const;
ShaderCompilerManager* GetShaderCompilerManager() const;
ShaderCompilerModel* GetShaderCompilerModel() const;
bool Run() override;
////////////////////////////////////////////////////
///MessageInfoBus::Listener interface///////////////
void NegotiationFailed() override;
void OnAssetFailed(const AZStd::string& sourceFileName) override;
void OnErrorMessage(const char* error) override;
///////////////////////////////////////////////////
//! TraceMessageBus::Handler
bool OnError(const char* window, const char* message) override;
bool OnAssert(const char* message) override;
private:
bool Activate() override;
bool PostActivate() override;
void CreateQtApplication() override;
bool InitApplicationServer() override;
void InitConnectionManager() override;
void InitIniConfiguration();
void DestroyIniConfiguration();
void InitFileServer();
void DestroyFileServer();
void InitShaderCompilerManager();
void DestroyShaderCompilerManager();
void InitShaderCompilerModel();
void DestroyShaderCompilerModel();
void Destroy() override;
Q_SIGNALS:
void ShowWindow();
protected Q_SLOTS:
void FileChanged(QString path);
void DirectoryChanged(QString path);
void ShowMessageBox(QString title, QString msg, bool isCritical);
void ShowTrayIconMessage(QString msg);
void ShowTrayIconErrorMessage(QString msg);
private:
bool Restart();
void Reflect() override;
const char* GetLogBaseName() override;
ApplicationManager::RegistryCheckInstructions PopupRegistryProblemsMessage(QString warningText) override;
void InitSourceControl() override;
bool GetShouldExitOnIdle() const override;
IniConfiguration* m_iniConfiguration = nullptr;
FileServer* m_fileServer = nullptr;
ShaderCompilerManager* m_shaderCompilerManager = nullptr;
ShaderCompilerModel* m_shaderCompilerModel = nullptr;
QFileSystemWatcher m_qtFileWatcher;
AZ::UserSettingsProvider m_localUserSettings;
bool m_messageBoxIsVisible = false;
bool m_startedSuccessfully = true;
QPointer<QSystemTrayIcon> m_trayIcon;
QPointer<MainWindow> m_mainWindow;
AZ::SettingsRegistryInterface::NotifyEventHandler m_bootstrapGameFolderChangedHandler;
AZStd::chrono::system_clock::time_point m_timeWhenLastWarningWasShown;
};
@@ -0,0 +1,57 @@
/*
* 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 <native/utilities/GUIApplicationServer.h>
#include <AzFramework/API/ApplicationAPI.h>
#include "native/utilities/assetUtils.h"
GUIApplicationServer::GUIApplicationServer(QObject* parent)
: ApplicationServer(parent)
{
}
GUIApplicationServer::~GUIApplicationServer()
{
}
bool GUIApplicationServer::startListening(unsigned short port)
{
if (!isListening())
{
const AzFramework::CommandLine* commandLine = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
bool randomPort = commandLine && commandLine->HasSwitch(ApplicationServer::RandomListeningPortOption);
if (port == 0 && !randomPort)
{
quint16 listeningPort = AssetUtilities::ReadListeningPortFromSettingsRegistry();
m_serverListeningPort = static_cast<int>(listeningPort);
}
else
{
// override the port
m_serverListeningPort = port;
}
if (!listen(QHostAddress::Any, aznumeric_cast<quint16>(m_serverListeningPort)))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Cannot start Asset Processor server - another instance of the Asset Processor may already be running on port number %d. If you'd like to run multiple Asset Processors on different branches at the same time, please edit bootstrap.cfg and assign different remote_port values to each branch instance.\n", m_serverListeningPort);
return false;
}
m_serverListeningPort = serverPort();
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Listening Port: %d\n", m_serverListeningPort);
ApplicationServerBus::Handler::BusConnect();
AZ_TracePrintf(AssetProcessor::DebugChannel, "Asset Processor server listening on port %d\n", m_serverListeningPort);
}
return true;
}
@@ -0,0 +1,30 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <native/utilities/ApplicationServer.h>
#endif
/** This Class is responsible for listening and getting new connections
*/
class GUIApplicationServer
: public ApplicationServer
{
Q_OBJECT
public:
explicit GUIApplicationServer(QObject* parent = 0);
~GUIApplicationServer() override;
bool startListening(unsigned short port = 0) override;
};
@@ -0,0 +1,75 @@
/*
* 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 "native/utilities/IniConfiguration.h"
#include "native/utilities/assetUtils.h"
namespace
{
// singleton Pattern
IniConfiguration* s_iniConfigurationSingleton = nullptr;
}
IniConfiguration::IniConfiguration(QObject* pParent)
: QObject(pParent)
, m_listeningPort(0)
{
AZ_Assert(s_iniConfigurationSingleton == nullptr, "Duplicate singleton installation detected.");
s_iniConfigurationSingleton = this;
}
IniConfiguration::~IniConfiguration()
{
AZ_Assert(s_iniConfigurationSingleton == this, "There should always be a single singleton!");
s_iniConfigurationSingleton = nullptr;
}
const IniConfiguration* IniConfiguration::Get()
{
return s_iniConfigurationSingleton;
}
void IniConfiguration::parseCommandLine(QStringList args)
{
for (QString arg : args)
{
if (arg.startsWith("--port="))
{
bool converted = false;
quint16 port = arg.replace("--port=", "").toUShort(&converted);
m_listeningPort = converted ? port : m_listeningPort;
}
}
}
void IniConfiguration::readINIConfigFile(QDir dir)
{
m_userConfigFilePath = dir.filePath("AssetProcessorConfiguration.ini");
// if AssetProcessorProxyInformation.ini file exists then delete it
// we used to store proxy info in this file
if (QFile::exists(m_userConfigFilePath))
{
QFile::remove(m_userConfigFilePath);
}
m_listeningPort = AssetUtilities::ReadListeningPortFromSettingsRegistry();
}
quint16 IniConfiguration::listeningPort() const
{
return m_listeningPort;
}
void IniConfiguration::SetListeningPort(quint16 port)
{
m_listeningPort = port;
}
@@ -0,0 +1,44 @@
/*
* 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.
*
*/
#ifndef INICONFIGURATION_H
#define INICONFIGURATION_H
#if !defined(Q_MOC_RUN)
#include <QDir>
#include <QString>
#include <QCoreApplication>
#endif
/** Reads the bootstrap file for listening port
*/
class IniConfiguration
: public QObject
{
Q_OBJECT
public:
explicit IniConfiguration(QObject* pParent = nullptr);
virtual ~IniConfiguration();
// Singleton pattern:
static const IniConfiguration* Get();
void parseCommandLine(QStringList cmdLine = QCoreApplication::arguments());
void readINIConfigFile(QDir dir = qApp->applicationDirPath());
quint16 listeningPort() const;
void SetListeningPort(quint16 port);
private:
quint16 m_listeningPort;
QString m_userConfigFilePath;
};
#endif // INICONFIGURATION_H
@@ -0,0 +1,70 @@
/*
* 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 "JobDiagnosticTracker.h"
namespace AssetProcessor
{
bool JobDiagnosticInfo::operator==(const JobDiagnosticInfo& rhs) const
{
return m_errorCount == rhs.m_errorCount
&& m_warningCount == rhs.m_warningCount;
}
bool JobDiagnosticInfo::operator!=(const JobDiagnosticInfo& rhs) const
{
return !operator==(rhs);
}
//////////////////////////////////////////////////////////////////////////
JobDiagnosticTracker::JobDiagnosticTracker()
{
BusConnect();
}
JobDiagnosticTracker::~JobDiagnosticTracker()
{
BusDisconnect();
}
JobDiagnosticInfo AssetProcessor::JobDiagnosticTracker::GetDiagnosticInfo(AZ::u64 jobRunKey) const
{
auto jobIter = m_jobInfo.find(jobRunKey);
if(jobIter != m_jobInfo.end())
{
return jobIter->second;
}
return {};
}
void JobDiagnosticTracker::RecordDiagnosticInfo(AZ::u64 jobRunKey, JobDiagnosticInfo info)
{
if (info != JobDiagnosticInfo{})
{
// Only store non-empty entries
m_jobInfo[jobRunKey] = info;
}
}
WarningLevel JobDiagnosticTracker::GetWarningLevel() const
{
return m_warningLevel;
}
void JobDiagnosticTracker::SetWarningLevel(WarningLevel level)
{
m_warningLevel = level;
}
}
@@ -0,0 +1,72 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <native/resourcecompiler/RCCommon.h>
namespace AssetProcessor
{
struct JobDiagnosticInfo
{
JobDiagnosticInfo() = default;
JobDiagnosticInfo(AZ::u32 warningCount, AZ::u32 errorCount)
: m_warningCount(warningCount), m_errorCount(errorCount)
{}
bool operator==(const JobDiagnosticInfo& rhs) const;
bool operator!=(const JobDiagnosticInfo& rhs) const;
AZ::u32 m_warningCount = 0;
AZ::u32 m_errorCount = 0;
};
enum class WarningLevel : AZ::u8
{
Default = 0,
FatalErrors,
FatalErrorsAndWarnings
};
class JobDiagnosticRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
virtual JobDiagnosticInfo GetDiagnosticInfo(AZ::u64 jobRunKey) const = 0;
virtual void RecordDiagnosticInfo(AZ::u64 jobRunKey, JobDiagnosticInfo info) = 0;
virtual WarningLevel GetWarningLevel() const = 0;
virtual void SetWarningLevel(WarningLevel level) = 0;
};
using JobDiagnosticRequestBus = AZ::EBus<JobDiagnosticRequests>;
class JobDiagnosticTracker
: public JobDiagnosticRequestBus::Handler
{
public:
JobDiagnosticTracker();
~JobDiagnosticTracker();
JobDiagnosticInfo GetDiagnosticInfo(AZ::u64 jobRunKey) const override;
void RecordDiagnosticInfo(AZ::u64 jobRunKey, JobDiagnosticInfo info) override;
WarningLevel GetWarningLevel() const override;
void SetWarningLevel(WarningLevel level) override;
WarningLevel m_warningLevel = WarningLevel::Default;
AZStd::unordered_map<AZ::u64, JobDiagnosticInfo> m_jobInfo;
};
} // namespace AssetProcessor
@@ -0,0 +1,253 @@
/*
* 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 "LineByLineDependencyScanner.h"
#include "assetprocessor.h"
#include "PotentialDependencies.h"
namespace AssetProcessor
{
class RegexComplexityAssertAbsorber
: public AZ::Debug::TraceMessageBus::Handler
{
public:
RegexComplexityAssertAbsorber()
{
AZ::Debug::TraceMessageBus::Handler::BusConnect();
}
virtual ~RegexComplexityAssertAbsorber()
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
}
bool OnPreAssert(const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) override
{
// Ignore the regex complexity assert, there's no reason for the asset processor to crash when running a complex regex.
const char* complexityError = AZStd::Internal::RegexError(AZStd::regex_constants::error_complexity);
if (strncmp(message, complexityError, strlen(complexityError)) == 0)
{
return true;
}
else
{
return false;
}
}
};
LineByLineDependencyScanner::SearchResult GlobalSearch(const AZStd::string& scanString, int maxScanIteration, const AZStd::regex& regex, AZStd::function<void(const AZStd::smatch&)> callback)
{
AZStd::smatch result;
AZStd::string::const_iterator searchStart = scanString.begin();
bool useScanTimeout = maxScanIteration > 0;
// Some binary files can cause the regex system to emit an assert that they are too complex to scan.
// There's no harm in this case failing here, so ignore that assert if it occurs.
RegexComplexityAssertAbsorber assertAbsorber;
while (AZStd::regex_search(searchStart, scanString.end(), result, regex) && (!useScanTimeout || maxScanIteration > 0))
{
callback(result);
searchStart = result[0].second;
--maxScanIteration;
}
if (useScanTimeout && maxScanIteration == 0)
{
return LineByLineDependencyScanner::SearchResult::ScanLimitHit;
}
return LineByLineDependencyScanner::SearchResult::Completed;
}
LineByLineDependencyScanner::SearchResult LineByLineDependencyScanner::ScanStringForMissingDependencies(
const AZStd::string& scanString,
int maxScanIteration,
const AZStd::regex& subIdRegex,
const AZStd::regex& uuidRegex,
const AZStd::regex& pathRegex,
PotentialDependencies& potentialDependencies)
{
SearchResult assetIdSearchResult = GlobalSearch(scanString, maxScanIteration, subIdRegex, [this, &potentialDependencies](const AZStd::smatch& assetIdMatchResult)
{
AZ::Uuid uuid(assetIdMatchResult[1].str().c_str());
AZ::u32 subId = AZStd::stoi(assetIdMatchResult[3].str());
AZ::Data::AssetId assetId(uuid, subId);
AZStd::string assetIdAsInFile(AZStd::string::format("%s%s%s",
assetIdMatchResult[1].str().c_str(),
assetIdMatchResult[2].str().c_str(),
assetIdMatchResult[3].str().c_str()));
// If one asset ID appears multiple times, only report it once to avoid too much repetitive output.
PotentialDependencyMetaData dependencyMetaData(assetIdAsInFile, shared_from_this());
potentialDependencies.m_assetIds[assetId] = dependencyMetaData;
});
SearchResult uuidSearchResult = GlobalSearch(scanString, maxScanIteration, uuidRegex, [this, &potentialDependencies, &subIdRegex](const AZStd::smatch& uuidMatchResult)
{
AZStd::string uuidStr = uuidMatchResult[1].str();
AZ::Uuid uuid(uuidStr.c_str());
// If one UUID appears multiple times, only report it once to avoid too much repetitive output.
AZStd::smatch assetIdResult;
const AZStd::string original{ uuidMatchResult.m_original };
if (AZStd::regex_search(original.begin(), original.end(), assetIdResult, subIdRegex)) // check to see if this UUID is part of it a full asset id (which would be caught above)
{
if (assetIdResult.position(0) > uuidMatchResult.position(0))
{
PotentialDependencyMetaData dependencyMetaData(uuidStr, shared_from_this());
potentialDependencies.m_uuids[uuid] = dependencyMetaData;
}
}
else
{
PotentialDependencyMetaData dependencyMetaData(uuidStr, shared_from_this());
potentialDependencies.m_uuids[uuid] = dependencyMetaData;
}
});
// We'll first break up the input string into blocks that *could* contain a path. This is a faster and simpler regex test
// For each block, we'll do a quick string check to see if it contains a path separator or a file extension (.)
// Only if we find one will we do the more expensive path regex check
SearchResult pathSearchResult = GlobalSearch(scanString, maxScanIteration, AZStd::regex(R"~(([^:*?<>|" ]+))~"), [this, &maxScanIteration, &potentialDependencies, &pathRegex](const AZStd::smatch& matchResult)
{
AZStd::string stringSection = matchResult[1].str();
if(stringSection.find('\\') != AZStd::string::npos || stringSection.find('/') != AZStd::string::npos || stringSection.find('.') != AZStd::string::npos)
{
return GlobalSearch(stringSection, maxScanIteration, pathRegex, [this, &potentialDependencies](const AZStd::smatch& pathMatchResult)
{
AZStd::string potentialPath = pathMatchResult[1].str();
PotentialDependencyMetaData dependencyMetaData(potentialPath, shared_from_this());
potentialDependencies.m_paths.insert(dependencyMetaData);
});
}
return SearchResult::Completed;
});
// If any scan did not complete, return that result.
// There should only be one warning per file.
if (assetIdSearchResult != SearchResult::Completed)
{
return assetIdSearchResult;
}
if (uuidSearchResult != SearchResult::Completed)
{
return uuidSearchResult;
}
if (pathSearchResult != SearchResult::Completed)
{
return pathSearchResult;
}
return SearchResult::Completed;
}
// A UUID is groups of hexadecimal digits, that may or may not be separated every 8, 4, 4, 4, 12 characters by a dash.
AZStd::string GetUUIDRegex()
{
const char validUUIDVals[] = R"([\da-fA-F])";
AZStd::string uuidSearchString = AZStd::string::format("\\b(%s{8}-?%s{4}-?%s{4}-?%s{4}-?%s{12})",
validUUIDVals,
validUUIDVals,
validUUIDVals,
validUUIDVals,
validUUIDVals);
return uuidSearchString;
}
bool LineByLineDependencyScanner::ScanFileForPotentialDependencies(
AZ::IO::GenericStream& fileStream,
PotentialDependencies& potentialDependencies,
int maxScanIteration)
{
// An empty file will have no missing dependencies.
AZ::IO::SizeType length = fileStream.GetLength();
if (length == 0)
{
return true;
}
AZStd::vector<char> charBuffer;
charBuffer.resize_no_construct(length + 1);
fileStream.Read(length, charBuffer.data());
charBuffer.back() = 0;
// Search the file line by line. This won't catch cases where a missing
// dependency uses data from multiple lines, but the regexes in use here also wouldn't catch that.
AZStd::vector<AZStd::string> fileLines;
AzFramework::StringFunc::Tokenize(charBuffer.data(), fileLines, "\r\n");
AZStd::string uuidRegexStr(GetUUIDRegex());
AZStd::regex uuidRegex(AZStd::string::format("%s(\\b)", uuidRegexStr.c_str()));
// The sub ID may be immediately after the UUID, or there may be a character separating, like }.
// There is a colon or dash character that separates the sub ID from the asset ID.
// The sub ID may or may not be wrapped in braces of some kind, like [5] or {4}.
// This will match things like:
// {A4844298-8495-4E2A-B587-C6E8ED9552AB}:5
// aaaaaaaa84954E2AB587C6E8ED9552AB-[5]
AZStd::regex subIdRegex(AZStd::string::format(R"(%s(.?[-:][\{\(\[]?)(\d+))", uuidRegexStr.c_str()));
// Don't use a greedy search, a given line may have multiple start/end quotes, find the smallest
// thing that looks like a path. This search won't find things that look like paths without file extensions.
AZStd::regex pathRegex(R"(([\w\\/-]*?\.[\w\d\.-]*))");
int currentLineIndex = 1; // Most file editing software starts at line 1, not 0.
for (const AZStd::string& line : fileLines)
{
SearchResult searchResult = ScanStringForMissingDependencies(
line,
maxScanIteration,
subIdRegex,
uuidRegex,
pathRegex,
potentialDependencies);
switch (searchResult)
{
case SearchResult::ScanLimitHit:
// This doesn't print the actual line in question out because it's likely a line complex enough to hit this limit isn't going to be print friendly.
AZ_Printf(AssetProcessor::ConsoleChannel,
"\tFile will only be partially scanned, line %d matched more than the scan limit allows. To perform a more complete and lengthy scan, use the '--dependencyScanMaxIteration' setting.\n",
currentLineIndex);
break;
default:
break;
}
++currentLineIndex;
}
return true;
}
bool LineByLineDependencyScanner::DoesScannerMatchFileData(AZ::IO::GenericStream& /*fileStream*/)
{
// This scanner can handle any file.
return true;
}
bool LineByLineDependencyScanner::DoesScannerMatchFileExtension(const AZStd::string& /*fullPath*/)
{
// This scanner can handle any file.
return true;
}
AZStd::string LineByLineDependencyScanner::GetVersion() const
{
return "1.0.0";
}
AZStd::string LineByLineDependencyScanner::GetName() const
{
return "Line by line scanner";
}
AZ::Crc32 LineByLineDependencyScanner::GetScannerCRC() const
{
return AZ::Crc32(GetName().c_str());
}
}
@@ -0,0 +1,48 @@
/*
* 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.
*
*/
#pragma once
#include "SpecializedDependencyScanner.h"
#include <AzCore/std/string/regex.h>
namespace AssetProcessor
{
/// Scans a given file stream for anything that looks like a path, asset ID, or UUID.
class LineByLineDependencyScanner : public SpecializedDependencyScanner
{
public:
bool ScanFileForPotentialDependencies(AZ::IO::GenericStream& fileStream, PotentialDependencies& potentialDependencies, int maxScanIteration) override;
bool DoesScannerMatchFileData(AZ::IO::GenericStream& fileStream) override;
bool DoesScannerMatchFileExtension(const AZStd::string& fullPath) override;
AZStd::string GetVersion() const override;
AZStd::string GetName() const override;
AZ::Crc32 GetScannerCRC() const override;
enum class SearchResult
{
Completed,
ScanLimitHit,
};
protected:
SearchResult ScanStringForMissingDependencies(
const AZStd::string& scanString,
int maxScanIteration,
const AZStd::regex& subIdRegex,
const AZStd::regex& uuidRegex,
const AZStd::regex& pathRegex,
PotentialDependencies& potentialDependencies);
};
}
@@ -0,0 +1,90 @@
/*
* 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 "LogPanel.h"
#include <native/utilities/ThreadHelper.h>
namespace AssetProcessor
{
LogPanel::LogPanel(QWidget* parent)
: AzToolsFramework::LogPanel::StyledTracePrintFLogPanel(parent)
{
}
QWidget* LogPanel::CreateTab(const AzToolsFramework::LogPanel::TabSettings& settings)
{
LogTab* logTab = aznew LogTab(settings, this);
logTab->AddInitialLogMessage();
return logTab;
}
LogTab::LogTab(const AzToolsFramework::LogPanel::TabSettings& settings, QWidget* parent)
: AzToolsFramework::LogPanel::StyledTracePrintFLogTab(settings, parent)
{
}
void LogTab::AddInitialLogMessage()
{
LogTraceMessage(AzToolsFramework::Logging::LogLine::TYPE_MESSAGE, "AssetProcessor", "Started recording logs. To check previous logs please navigate to the logs folder.", true);
}
bool LogTab::OnAssert(const char* message)
{
if (AssetProcessor::GetThreadLocalJobId())
{
return false; // we are in a job thread
}
return AzToolsFramework::LogPanel::StyledTracePrintFLogTab::OnAssert(message);
}
bool LogTab::OnException(const char* message)
{
if (AssetProcessor::GetThreadLocalJobId())
{
return false; // we are in a job thread
}
return AzToolsFramework::LogPanel::StyledTracePrintFLogTab::OnException(message);
}
bool LogTab::OnPrintf(const char* window, const char* message)
{
if (AssetProcessor::GetThreadLocalJobId())
{
return false; // we are in a job thread
}
return AzToolsFramework::LogPanel::StyledTracePrintFLogTab::OnPrintf(window, message);
}
bool LogTab::OnError(const char* window, const char* message)
{
if (AssetProcessor::GetThreadLocalJobId())
{
return false; // we are in a job thread
}
return AzToolsFramework::LogPanel::StyledTracePrintFLogTab::OnError(window, message);
}
bool LogTab::OnWarning(const char* window, const char* message)
{
if (AssetProcessor::GetThreadLocalJobId())
{
return false; // we are in a job thread
}
return AzToolsFramework::LogPanel::StyledTracePrintFLogTab::OnWarning(window, message);
}
}
@@ -0,0 +1,46 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/UI/Logging/StyledTracePrintFLogPanel.h>
namespace AssetProcessor
{
//! LogPanel - an implementation of TracePrintFLogPanel which shows recent traceprintfs
//! CreateTabs will create a new instance of LogTab
class LogPanel : public AzToolsFramework::LogPanel::StyledTracePrintFLogPanel
{
public:
explicit LogPanel(QWidget* parent = nullptr);
~LogPanel() override = default;
protected:
QWidget* CreateTab(const AzToolsFramework::LogPanel::TabSettings& settings) override;
};
//! LogTab - a Log View listening on AZ Traceprintfs and puts them in a ring buffer
//! It also filters traceprintfs based on the thread local job id
class LogTab : public AzToolsFramework::LogPanel::StyledTracePrintFLogTab
{
public:
AZ_CLASS_ALLOCATOR(LogTab, AZ::SystemAllocator, 0);
explicit LogTab(const AzToolsFramework::LogPanel::TabSettings& settings, QWidget* parent = nullptr);
~LogTab() override = default;
void AddInitialLogMessage();
//////////////////////////////////////////////////////////////////////////
// TraceMessagesBus
bool OnAssert(const char* message) override;
bool OnException(const char* message) override;
bool OnError(const char* window, const char* message) override;
bool OnWarning(const char* window, const char* message) override;
bool OnPrintf(const char* window, const char* message) override;
//////////////////////////////////////////////////////////////////////////
};
}
@@ -0,0 +1,810 @@
/*
* 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 "MissingDependencyScanner.h"
AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option")
AZ_POP_DISABLE_WARNING
#include "LineByLineDependencyScanner.h"
#include "PotentialDependencies.h"
#include "native/AssetDatabase/AssetDatabase.h"
#include "native/assetprocessor.h"
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/FileTag/FileTagBus.h>
#include <AzCore/std/string/wildcard.h>
#include <AzCore/XML/rapidxml.h>
#include <AzFramework/FileTag/FileTag.h>
#include <AzFramework/IO/LocalFileIO.h>
namespace AssetProcessor
{
const char* EngineFolder = "Engine";
AZStd::string GetXMLDependenciesFile(const AZStd::string& fullPath, const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AZStd::string& tokenName)
{
AZStd::string xmlDependenciesFileFullPath;
tokenName = EngineFolder;
for (const AzToolsFramework::AssetUtils::GemInfo& gemElement : gemInfoList)
{
if (AzFramework::StringFunc::StartsWith(fullPath.c_str(), gemElement.m_absoluteFilePath.c_str()) || AzFramework::StringFunc::Equal(gemElement.m_absoluteFilePath.c_str(), fullPath.c_str()))
{
AZStd::string fileName = AZStd::string::format("%s_Dependencies.xml", gemElement.m_gemName.c_str());
AzFramework::StringFunc::Path::ConstructFull(gemElement.m_absoluteFilePath.c_str(), AzToolsFramework::AssetUtils::GemInfo::GetGemAssetFolder().c_str(), fileName.c_str() , "xml", xmlDependenciesFileFullPath);
if (AZ::IO::FileIOBase::GetInstance()->Exists(xmlDependenciesFileFullPath.c_str()))
{
tokenName = gemElement.m_gemName;
return xmlDependenciesFileFullPath;
}
}
}
// if we are here than either the %gemName%_Dependencies.xml file does not exists or the user inputted path is not inside a gems folder,
// in both the cases we will return the engine dependencies file
const char* devRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devroot@");
AzFramework::StringFunc::Path::ConstructFull(devRoot, EngineFolder, "Engine_Dependencies.xml", "xml", xmlDependenciesFileFullPath);
return xmlDependenciesFileFullPath;
}
const int MissingDependencyScanner::DefaultMaxScanIteration = 800;
class MissingDependency
{
public:
MissingDependency(const AZ::Data::AssetId& assetId, const PotentialDependencyMetaData& metaData) :
m_assetId(assetId),
m_metaData(metaData)
{
}
// Allows MissingDependency to be in a sorted container, which stabilizes log output.
bool operator<(const MissingDependency& rhs) const
{
return m_assetId < rhs.m_assetId;
}
AZ::Data::AssetId m_assetId;
PotentialDependencyMetaData m_metaData;
};
MissingDependencyScanner::MissingDependencyScanner()
{
m_defaultScanner = AZStd::make_shared<LineByLineDependencyScanner>();
ApplicationManagerNotifications::Bus::Handler::BusConnect();
MissingDependencyScannerRequestBus::Handler::BusConnect();
}
MissingDependencyScanner::~MissingDependencyScanner()
{
MissingDependencyScannerRequestBus::Handler::BusDisconnect();
ApplicationManagerNotifications::Bus::Handler::BusDisconnect();
}
void MissingDependencyScanner::ApplicationShutdownRequested()
{
// Do not add any new functions to the SystemTickBus queue
m_shutdownRequested = true;
// Finish up previously queued work
AZ::SystemTickBus::ExecuteQueuedEvents();
}
void MissingDependencyScanner::ScanFile(const AZStd::string& fullPath, int maxScanIteration, AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection, const AZStd::string& dependencyTokenName, bool queueDbCommandsOnMainThread, scanFileCallback callback)
{
AZ::s64 productPK = -1;
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencies = {};
ScanFile(fullPath, maxScanIteration, productPK, dependencies, databaseConnection, dependencyTokenName, ScannerMatchType::ExtensionOnlyFirstMatch, nullptr, queueDbCommandsOnMainThread, callback);
}
void MissingDependencyScanner::ScanFile(
const AZStd::string& fullPath,
int maxScanIteration,
AZ::s64 productPK,
const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencies,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
bool queueDbCommandsOnMainThread,
scanFileCallback callback)
{
ScanFile(fullPath, maxScanIteration, productPK, dependencies, databaseConnection, "", ScannerMatchType::ExtensionOnlyFirstMatch, nullptr, queueDbCommandsOnMainThread, callback);
}
void MissingDependencyScanner::ScanFile(
const AZStd::string& fullPath,
int maxScanIteration,
AZ::s64 productPK,
const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencies,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
AZStd::string dependencyTokenName,
ScannerMatchType matchType,
AZ::Crc32* forceScanner,
bool queueDbCommandsOnMainThread,
scanFileCallback callback)
{
using namespace AzFramework::FileTag;
AZ_Printf(AssetProcessor::ConsoleChannel, "Scanning for missing dependencies:\t%s\n", fullPath.c_str());
AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
if (productPK != -1)
{
AZStd::vector<AZStd::vector<AZStd::string>> excludedTagsList = {
{
FileTags[static_cast<unsigned int>(FileTagsIndex::EditorOnly)]
},
{
FileTags[static_cast<unsigned int>(FileTagsIndex::Shader)]
} };
databaseConnection->GetSourceByProductID(productPK, sourceEntry);
for (const AZStd::vector<AZStd::string>& tags : excludedTagsList)
{
bool shouldIgnore = false;
QueryFileTagsEventBus::EventResult(shouldIgnore, FileTagType::Exclude,
&QueryFileTagsEventBus::Events::Match, fullPath.c_str(), tags);
if (shouldIgnore)
{
// Record that this file was ignored in the database, so the asset tab can display this information.
AZStd::string ignoredByTagText("File matches EditorOnly or Shader tag, ignoring for missing dependencies search.");
AZ_Printf(AssetProcessor::ConsoleChannel, "\t%s\n", ignoredByTagText.c_str());
SetDependencyScanResultStatus(
ignoredByTagText,
productPK,
sourceEntry.m_analysisFingerprint,
databaseConnection,
queueDbCommandsOnMainThread,
callback);
return;
}
}
}
else
{
// if we are here than it implies that this file is not an asset
AZStd::vector<AZStd::string> tags{
FileTags[static_cast<unsigned int>(FileTagsIndex::Ignore)],
FileTags[static_cast<unsigned int>(FileTagsIndex::ProductDependency)] };
bool shouldIgnore = false;
QueryFileTagsEventBus::EventResult(shouldIgnore, FileTagType::Exclude, &QueryFileTagsEventBus::Events::Match, fullPath.c_str(), tags);
if (shouldIgnore)
{
AZ_Printf(AssetProcessor::ConsoleChannel, "File ( %s ) will be skipped by the missing dependency scanner.\n", fullPath.c_str());
return;
}
}
AZ::IO::FileIOStream fileStream;
if (!fileStream.Open(fullPath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "File at path %s could not be opened.", fullPath.c_str());
// Record that this file was ignored in the database, so the asset tab can display this information.
SetDependencyScanResultStatus(
"The file could not be opened.",
productPK,
sourceEntry.m_analysisFingerprint,
databaseConnection,
queueDbCommandsOnMainThread,
callback);
return;
}
PotentialDependencies potentialDependencies;
bool scanSuccessful = RunScan(fullPath, maxScanIteration, fileStream, potentialDependencies, matchType, forceScanner);
fileStream.Close();
if (!scanSuccessful)
{
// RunScan will report an error on what caused the scan to fail.
SetDependencyScanResultStatus(
"An error occured, see log for details.",
productPK,
sourceEntry.m_analysisFingerprint,
databaseConnection,
queueDbCommandsOnMainThread,
callback);
return;
}
MissingDependencies missingDependencies;
PopulateMissingDependencies(productPK, databaseConnection, dependencies, missingDependencies, potentialDependencies);
if (queueDbCommandsOnMainThread && !m_shutdownRequested)
{
AZ::SystemTickBus::QueueFunction([=]()
{
ReportMissingDependencies(productPK, databaseConnection, dependencyTokenName, missingDependencies, callback);
});
}
else
{
ReportMissingDependencies(productPK, databaseConnection, dependencyTokenName, missingDependencies, callback);
}
}
void MissingDependencyScanner::SetDependencyScanResultStatus(
AZStd::string status,
AZ::s64 productPK,
const AZStd::string& analysisFingerprint,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
bool queueDbCommandsOnMainThread,
scanFileCallback callback)
{
QDateTime currentTime = QDateTime::currentDateTime();
auto finalizeMissingDependency = [=]() {
AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry missingDependencyEntry(
productPK,
/*Scanner*/ "",
/*Scanner Version*/ "",
analysisFingerprint,
AZ::Uuid::CreateNull(),
/*Product ID*/ 0,
status,
currentTime.toString().toUtf8().constData(),
currentTime.toSecsSinceEpoch());
databaseConnection->SetMissingProductDependency(missingDependencyEntry);
callback(missingDependencyEntry.m_missingDependencyString);
};
if (queueDbCommandsOnMainThread && !m_shutdownRequested)
{
AZ::SystemTickBus::QueueFunction(finalizeMissingDependency);
}
else
{
finalizeMissingDependency();
}
}
void MissingDependencyScanner::RegisterSpecializedScanner(AZStd::shared_ptr<SpecializedDependencyScanner> scanner)
{
m_specializedScanners.insert(AZStd::pair<AZ::Crc32, AZStd::shared_ptr<SpecializedDependencyScanner>>(scanner->GetScannerCRC(), scanner));
}
bool MissingDependencyScanner::RunScan(
const AZStd::string& fullPath,
int maxScanIteration,
AZ::IO::GenericStream& fileStream,
PotentialDependencies& potentialDependencies,
ScannerMatchType matchType,
AZ::Crc32* forceScanner)
{
// If a scanner is given to specifically use, then use that scanner and only that scanner.
if (forceScanner)
{
AZ_Printf(
AssetProcessor::ConsoleChannel,
"\tForcing scanner with CRC %d\n",
*forceScanner);
DependencyScannerMap::iterator scannerToUse = m_specializedScanners.find(*forceScanner);
if (scannerToUse != m_specializedScanners.end())
{
scannerToUse->second->ScanFileForPotentialDependencies(fileStream, potentialDependencies, maxScanIteration);
return true;
}
else
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Attempted to force dependency scan using CRC %d, which is not registered.",
*forceScanner);
return false;
}
}
// Check if a specialized scanner should be used, based on the given scanner matching type rule.
for (const AZStd::pair<AZ::Crc32, AZStd::shared_ptr<SpecializedDependencyScanner>>&
scanner : m_specializedScanners)
{
switch (matchType)
{
case ScannerMatchType::ExtensionOnlyFirstMatch:
if (scanner.second->DoesScannerMatchFileExtension(fullPath))
{
return scanner.second->ScanFileForPotentialDependencies(fileStream, potentialDependencies, maxScanIteration);
}
break;
case ScannerMatchType::FileContentsFirstMatch:
if (scanner.second->DoesScannerMatchFileData(fileStream))
{
return scanner.second->ScanFileForPotentialDependencies(fileStream, potentialDependencies, maxScanIteration);
}
break;
case ScannerMatchType::Deep:
// A deep scan has every matching scanner scan the file, and uses the default scan.
if (scanner.second->DoesScannerMatchFileData(fileStream))
{
scanner.second->ScanFileForPotentialDependencies(fileStream, potentialDependencies, maxScanIteration);
}
break;
default:
AZ_Error(AssetProcessor::ConsoleChannel, false, "Scan match type %d is not available.", matchType);
break;
};
}
// No specialized scanner was found (or a deep scan is being performed), so use the default scanner.
return m_defaultScanner->ScanFileForPotentialDependencies(fileStream, potentialDependencies, maxScanIteration);
}
void MissingDependencyScanner::PopulateMissingDependencies(
AZ::s64 productPK,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencies,
MissingDependencies& missingDependencies,
const PotentialDependencies& potentialDependencies)
{
// If a file references itself, don't report it.
AzToolsFramework::AssetDatabase::SourceDatabaseEntry fileWithPotentialMissingDependencies;
databaseConnection->GetSourceByProductID(productPK, fileWithPotentialMissingDependencies);
AZStd::map<AZ::Uuid, PotentialDependencyMetaData> uuids(potentialDependencies.m_uuids);
AZStd::map<AZ::Data::AssetId, PotentialDependencyMetaData> assetIds(potentialDependencies.m_assetIds);
// Check if any products exist for the given job, and those products have a sub ID that matches
// the expected sub ID.
AzToolsFramework::AssetDatabase::ProductDatabaseEntry productWithPotentialMissingDependencies;
databaseConnection->GetProductByProductID(productPK, productWithPotentialMissingDependencies);
QString scannedProductPath( productWithPotentialMissingDependencies.m_productName.c_str() );
auto lastSeparatorIndex = scannedProductPath.lastIndexOf(AZ_CORRECT_DATABASE_SEPARATOR_STRING);
scannedProductPath = scannedProductPath.remove(lastSeparatorIndex + 1, scannedProductPath.length());
// Check the existing product dependency list for the file that is being scanned, remove
// any potential UUIDs that match dependencies already being emitted.
for (const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry&
existingDependency : dependencies)
{
AZStd::map<AZ::Uuid, PotentialDependencyMetaData>::iterator matchingDependency =
uuids.find(existingDependency.m_dependencySourceGuid);
if (matchingDependency != uuids.end())
{
uuids.erase(matchingDependency);
}
}
// Remove all UUIDs that don't match an asset in the database.
for (AZStd::map<AZ::Uuid, PotentialDependencyMetaData>::iterator uuidIter = uuids.begin();
uuidIter != uuids.end();
++uuidIter)
{
if (fileWithPotentialMissingDependencies.m_sourceGuid == uuidIter->first)
{
// This product references itself, or the source it comes from. Don't report it as a missing dependency.
continue;
}
AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
if (!databaseConnection->GetSourceBySourceGuid(uuidIter->first, sourceEntry))
{
// The UUID isn't in the asset database, don't add it to the list of missing dependencies.
continue;
}
AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer jobs;
if (!databaseConnection->GetJobsBySourceID(sourceEntry.m_sourceID, jobs))
{
// No jobs existed for that source asset, so there are no products for this asset.
// With no products, there is no way there can be a missing product dependency.
continue;
}
// The dependency only referenced the source UUID, so add all products as missing dependencies.
for (const AzToolsFramework::AssetDatabase::JobDatabaseEntry& job : jobs)
{
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
if (!databaseConnection->GetProductsByJobID(job.m_jobID, products))
{
continue;
}
for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
{
// This match was for a UUID with no product ID, so add all products as missing dependencies.
MissingDependency missingDependency(
AZ::Data::AssetId(uuidIter->first, product.m_subID),
uuidIter->second);
missingDependencies.insert(missingDependency);
}
}
}
// Validate the asset ID list, removing anything that is already a dependency, or does not exist in the asset database.
for (AZStd::map<AZ::Data::AssetId, PotentialDependencyMetaData>::iterator assetIdIter = assetIds.begin();
assetIdIter != assetIds.end();
++assetIdIter)
{
bool foundUUID = false;
// Strip out all existing, matching dependencies
for (const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry&
existingDependency : dependencies)
{
if (existingDependency.m_dependencySourceGuid == assetIdIter->first.m_guid &&
existingDependency.m_dependencySubID == assetIdIter->first.m_subId)
{
foundUUID = true;
break;
}
}
// There is already a dependency with this UUID, so it's not a missing dependency.
if (foundUUID)
{
continue;
}
AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
if (!databaseConnection->GetSourceBySourceGuid(assetIdIter->first.m_guid, sourceEntry))
{
// The UUID isn't in the asset database. Don't report it as a missing dependency
// because UUIDs are used for tracking many things that are not assets.
continue;
}
AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer jobs;
if (!databaseConnection->GetJobsBySourceID(sourceEntry.m_sourceID, jobs))
{
// No jobs existed for that source asset, so there are no products for this asset.
// With no products, there is no way there can be a missing product dependency.
continue;
}
bool isProductOfFileWithPotentialMissingDependencies = fileWithPotentialMissingDependencies.m_sourceGuid == assetIdIter->first.m_guid;
bool foundMatchingProduct = false;
for (const AzToolsFramework::AssetDatabase::JobDatabaseEntry& job : jobs)
{
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
if (!databaseConnection->GetProductsByJobID(job.m_jobID, products))
{
continue;
}
for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
{
if (product.m_subID == assetIdIter->first.m_subId)
{
// This product references itself. Don't report it as a missing dependency.
// If the product references a different product of the same source and that isn't
// a dependency, then do report that.
// We have to check against more than the productPK to catch identical products across multiple
// platforms.
if (productPK == product.m_productID ||
(isProductOfFileWithPotentialMissingDependencies && productWithPotentialMissingDependencies.m_subID == product.m_subID))
{
continue;
}
MissingDependency missingDependency(
assetIdIter->first,
assetIdIter->second);
missingDependencies.insert(missingDependency);
foundMatchingProduct = true;
break;
}
}
if (foundMatchingProduct)
{
break;
}
}
}
for (const PotentialDependencyMetaData& path : potentialDependencies.m_paths)
{
AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer searchSources;
QString searchName(path.m_sourceString.c_str());
// The paths in the file may have had slashes in either direction, or double slashes.
searchName.replace(AZ_WRONG_DATABASE_SEPARATOR_STRING, AZ_CORRECT_DATABASE_SEPARATOR_STRING);
searchName.replace(AZ_DOUBLE_CORRECT_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR_STRING);
if (databaseConnection->GetSourcesBySourceName(searchName, searchSources))
{
// A source matched the path, look up products and add them as resolved path dependencies.
for (const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& source : searchSources)
{
if (fileWithPotentialMissingDependencies.m_sourceGuid == source.m_sourceGuid)
{
// This product references itself, or the source it comes from. Don't report it as a missing dependency.
continue;
}
bool dependencyExistsForSource = false;
for (const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry&
existingDependency : dependencies)
{
if (existingDependency.m_dependencySourceGuid == source.m_sourceGuid)
{
dependencyExistsForSource = true;
break;
}
}
if (dependencyExistsForSource)
{
continue;
}
AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer jobs;
if (!databaseConnection->GetJobsBySourceID(source.m_sourceID, jobs))
{
// No jobs exist for this source, which means there is no matching product dependency.
continue;
}
for (const AzToolsFramework::AssetDatabase::JobDatabaseEntry& job : jobs)
{
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
if (!databaseConnection->GetProductsByJobID(job.m_jobID, products))
{
// No products, no product dependencies.
continue;
}
for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
{
MissingDependency missingDependency(
AZ::Data::AssetId(source.m_sourceGuid, product.m_subID),
path);
missingDependencies.insert(missingDependency);
}
}
}
}
else
{
// Product paths in the asset database include the platform and additional pathing information that
// makes this check more complex than the source path check.
// Examples:
// pc/usersettings.xml
// pc/ProjectName/file.xml
// Taking all results from this EndsWith check can lead to an over-emission of potential missing dependencies.
// For example, if a file has a comment like "Something about .dds files", then EndsWith would return
// every single dds file in the database.
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
if (!databaseConnection->GetProductsLikeProductName(searchName, AssetDatabaseConnection::LikeType::EndsWith, products))
{
continue;
}
for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
{
if (productPK == product.m_productID)
{
// Don't report if a file has a reference to itself.
continue;
}
// Cull the platform from the product path to perform a more confident comparison against the given path.
QString culledProductPath = QString(product.m_productName.c_str());
// If this appears to be a valid path to a product relative to the product being checked
if (culledProductPath.compare(scannedProductPath.append(searchName), Qt::CaseInsensitive) != 0)
{
culledProductPath = culledProductPath.remove(0, culledProductPath.indexOf(AZ_CORRECT_DATABASE_SEPARATOR_STRING) + 1);
// This first check will catch paths that include the project name, as well as references to assets that include a scan folder in the path.
if (culledProductPath.compare(searchName, Qt::CaseInsensitive) != 0)
{
int nextFolderIndex = culledProductPath.indexOf(AZ_CORRECT_DATABASE_SEPARATOR_STRING);
if (nextFolderIndex == -1)
{
continue;
}
// Perform a second check with the scan folder removed. Many asset references are relevant to scan folder roots.
// For example, a material may have a relative path reference to a texture as "textures/SomeTexture.dds".
// This relative path resolves in many systems based on scan folder root, so if this file is in "platform/project/textures/SomeTexture.dds",
// this check is intended to find that reference.
culledProductPath = culledProductPath.remove(0, nextFolderIndex + 1);
if (culledProductPath.compare(searchName, Qt::CaseInsensitive) != 0)
{
continue;
}
}
}
AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer productSources;
if (!databaseConnection->GetSourcesByProductName(QString(product.m_productName.c_str()), productSources))
{
AZ_Error(
AssetProcessor::ConsoleChannel,
false,
"Product %s does not have a matching source. Your database may be corrupted.", product.m_productName.c_str());
continue;
}
for (const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& source : productSources)
{
bool dependencyExistsForProduct = false;
for (const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry&
existingDependency : dependencies)
{
if (existingDependency.m_dependencySourceGuid == source.m_sourceGuid &&
existingDependency.m_dependencySubID == product.m_subID)
{
dependencyExistsForProduct = true;
break;
}
}
if (!dependencyExistsForProduct)
{
AZ::Data::AssetId assetId(source.m_sourceGuid, product.m_subID);
MissingDependency missingDependency(
AZ::Data::AssetId(source.m_sourceGuid, product.m_subID),
path);
missingDependencies.insert(missingDependency);
}
}
}
}
}
}
void MissingDependencyScanner::ReportMissingDependencies(
AZ::s64 productPK,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
const AZStd::string& dependencyTokenName,
const MissingDependencies& missingDependencies,
scanFileCallback callback)
{
using namespace AzFramework::FileTag;
AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
databaseConnection->GetSourceByProductID(productPK, sourceEntry);
AZStd::vector<AZStd::string> tags{
FileTags[static_cast<unsigned int>(FileTagsIndex::Ignore)],
FileTags[static_cast<unsigned int>(FileTagsIndex::ProductDependency)] };
QDateTime currentTime = QDateTime::currentDateTime();
// If there were no missing dependencies, add a row to the table so we know it was scanned.
if (productPK != -1 && missingDependencies.empty())
{
SetDependencyScanResultStatus(
"No missing dependencies found",
productPK,
sourceEntry.m_analysisFingerprint,
databaseConnection,
/*queueDbCommandsOnMainThread*/ false, // ReportMissingDependencies was already queued to run on the main thread.
callback);
return;
}
for (const MissingDependency& missingDependency : missingDependencies)
{
bool shouldIgnore = false;
QueryFileTagsEventBus::EventResult(shouldIgnore, FileTagType::Exclude,
&QueryFileTagsEventBus::Events::Match, missingDependency.m_metaData.m_sourceString.c_str(), tags);
if (!shouldIgnore && !dependencyTokenName.empty())
{
// if one of the rules in the xml dependency file match then skip the missing dependency
auto rulesFound = m_dependenciesRulesMap.find(dependencyTokenName);
if (rulesFound != m_dependenciesRulesMap.end())
{
for (const auto& rule : rulesFound->second)
{
if (AZStd::wildcard_match(rule, missingDependency.m_metaData.m_sourceString))
{
shouldIgnore = true;
break;
}
}
}
}
if (!shouldIgnore)
{
AZStd::string assetIdStr = missingDependency.m_assetId.ToString<AZStd::string>();
AZ_Printf(
AssetProcessor::ConsoleChannel,
"\t\tMissing dependency: String \"%s\" matches asset: %s\n",
missingDependency.m_metaData.m_sourceString.c_str(),
assetIdStr.c_str());
if (productPK != -1)
{
AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry missingDependencyEntry(
productPK,
missingDependency.m_metaData.m_scanner->GetName(),
missingDependency.m_metaData.m_scanner->GetVersion(),
sourceEntry.m_analysisFingerprint,
missingDependency.m_assetId.m_guid,
missingDependency.m_assetId.m_subId,
missingDependency.m_metaData.m_sourceString,
currentTime.toString().toUtf8().constData(),
currentTime.toSecsSinceEpoch());
databaseConnection->SetMissingProductDependency(missingDependencyEntry);
}
callback(missingDependency.m_metaData.m_sourceString);
}
}
}
bool MissingDependencyScanner::PopulateRulesForScanFolder(const AZStd::string& scanFolderPath, const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AZStd::string& dependencyTokenName)
{
AZStd::string xmlDependenciesFullFilePath = GetXMLDependenciesFile(scanFolderPath, gemInfoList, dependencyTokenName);
if (xmlDependenciesFullFilePath.empty())
{
AZ_Printf(AssetProcessor::ConsoleChannel, "Unable to find xml dependency file for the directory scan %s\n", scanFolderPath.c_str());
}
auto found = m_dependenciesRulesMap.find(dependencyTokenName);
if (found != m_dependenciesRulesMap.end())
{
// this imply that we have already parsed this file and populated the rules,
// therefore we can exit early.
return true;
}
if (!AZ::IO::FileIOBase::GetInstance()->Exists(xmlDependenciesFullFilePath.c_str()))
{
AZ_Printf(AssetProcessor::ConsoleChannel, "Unable to find xml dependency file (%s). \n", xmlDependenciesFullFilePath.c_str());
return false;
}
AZ::IO::FileIOStream fileStream;
AZStd::vector<AZStd::string> dependenciesRuleList;
if (fileStream.Open(xmlDependenciesFullFilePath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary))
{
if (!fileStream.CanRead())
{
return false;
}
AZ::IO::SizeType length = fileStream.GetLength();
if (length == 0)
{
return false;
}
AZStd::vector<char> charBuffer;
charBuffer.resize_no_construct(length + 1);
fileStream.Read(length, charBuffer.data());
charBuffer.back() = 0;
AZ::rapidxml::xml_document<char> xmlDoc;
xmlDoc.parse<AZ::rapidxml::parse_no_data_nodes>(charBuffer.data());
auto engineDependenciesNode = xmlDoc.first_node("EngineDependencies");
if (!engineDependenciesNode)
{
return false;
}
auto dependencyNode = engineDependenciesNode->first_node("Dependency");
while (dependencyNode)
{
auto pathAttr = dependencyNode->first_attribute("path");
if (pathAttr)
{
dependenciesRuleList.emplace_back(AZStd::string(pathAttr->value()));
}
dependencyNode = dependencyNode->next_sibling();
}
m_dependenciesRulesMap[dependencyTokenName] = dependenciesRuleList;
return true;
}
return false;
}
}
@@ -0,0 +1,174 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/containers/set.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/regex.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
#include <AzToolsFramework/Asset/AssetUtils.h>
#include <native/utilities/ApplicationManagerAPI.h>
namespace AZ
{
namespace IO
{
class GenericStream;
}
}
namespace AssetProcessor
{
class AssetDatabaseConnection;
class LineByLineDependencyScanner;
class MissingDependency;
class PotentialDependencies;
class SpecializedDependencyScanner;
typedef AZStd::set<MissingDependency> MissingDependencies;
enum class ScannerMatchType
{
// The scanner to run only matches based on the file extension, such as a "json" scanner will only
// scan files with the .json extension.
ExtensionOnlyFirstMatch,
// The scanners open each file and inspect the contents to see if they look like the format.
// The first scanner found that matches the file will be used.
// Example: If a file named "Medium.difficulty" is in XML format, the XML scanner will catch this and scan it.
FileContentsFirstMatch,
// All scanners that can scan the given file are used to scan it. Time consuming but thorough.
Deep
};
class MissingDependencyScannerRequests
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// Bus configuration
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
using MutexType = AZStd::mutex;
/**
* Scans the given file for a missing dependency. Note that the database connection is
* not the AzToolsFramework version, it is the AssetProcessor version. This command needs
* write access to the database.
*/
using scanFileCallback = AZStd::function<void(AZStd::string relativeDependencyFilePath)>;
virtual void ScanFile(
const AZStd::string& fullPath,
int maxScanIteration,
AZ::s64 productPK,
const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencies,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
bool queueDbCommandsOnMainThread,
scanFileCallback callback) = 0;
};
using MissingDependencyScannerRequestBus = AZ::EBus<MissingDependencyScannerRequests>;
class MissingDependencyScanner
: public MissingDependencyScannerRequestBus::Handler
, AssetProcessor::ApplicationManagerNotifications::Bus::Handler
{
public:
MissingDependencyScanner();
~MissingDependencyScanner() override;
// ApplicationManagerNotifications::Bus::Handler
void ApplicationShutdownRequested() override;
//! Scans the file at the fullPath for anything that looks like a missing dependency.
//! Reporting is handled internally, no results are returned.
//! Anything that matches a result in the given dependency list will not be reported as a missing dependency.
//! The databaseConnection is used to query the database to transform relative paths into source or
//! product assets that match those paths, as well as looking up products for UUIDs found in files.
//! The matchtype is used to determine how to scan the given file, see the ScannerMatchType enum for more information.
//! A specific scanner can be forced to be used, this will supercede the match type.
void ScanFile(
const AZStd::string& fullPath,
int maxScanIteration,
AZ::s64 productPK,
const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencies,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
bool queueDbCommandsOnMainThread,
scanFileCallback callback) override;
void ScanFile(const AZStd::string& fullPath,
int maxScanIteration,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
const AZStd::string& dependencyTokenName,
bool queueDbCommandsOnMainThread,
scanFileCallback callback);
void ScanFile(
const AZStd::string& fullPath,
int maxScanIteration,
AZ::s64 productPK,
const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencies,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
AZStd::string dependencyTokenName,
ScannerMatchType matchType,
AZ::Crc32* forceScanner,
bool queueDbCommandsOnMainThread,
scanFileCallback callback);
static const int DefaultMaxScanIteration;
void RegisterSpecializedScanner(AZStd::shared_ptr<SpecializedDependencyScanner> scanner);
bool PopulateRulesForScanFolder(const AZStd::string& scanFolderPath, const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AZStd::string& dependencyTokenName);
protected:
bool RunScan(
const AZStd::string& fullPath,
int maxScanIteration,
AZ::IO::GenericStream& fileStream,
PotentialDependencies& potentialDependencies,
ScannerMatchType matchType,
AZ::Crc32* forceScanner);
void PopulateMissingDependencies(
AZ::s64 productPK,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencies,
MissingDependencies& missingDependencies,
const PotentialDependencies& potentialDependencies);
void ReportMissingDependencies(
AZ::s64 productPK,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
const AZStd::string& dependencyTokenName,
const MissingDependencies& missingDependencies,
scanFileCallback callback);
void SetDependencyScanResultStatus(
AZStd::string status,
AZ::s64 productPK,
const AZStd::string& analysisFingerprint,
AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
bool queueDbCommandsOnMainThread,
scanFileCallback callback);
typedef AZStd::unordered_map<AZ::Crc32, AZStd::shared_ptr<SpecializedDependencyScanner>> DependencyScannerMap;
DependencyScannerMap m_specializedScanners;
AZStd::shared_ptr<LineByLineDependencyScanner> m_defaultScanner;
AZStd::unordered_map<AZStd::string, AZStd::vector<AZStd::string>> m_dependenciesRulesMap;
AZStd::atomic_bool m_shutdownRequested = false;
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,288 @@
/*
* 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.
*
*/
#ifndef PLATFORMCONFIGURATION_H
#define PLATFORMCONFIGURATION_H
#if !defined(Q_MOC_RUN)
#include <QList>
#include <QString>
#include <QObject>
#include <QHash>
#include <QRegExp>
#include <QPair>
#include <QVector>
#include <QSet>
#include <AzCore/std/string/string.h>
#include <native/utilities/assetUtils.h>
#include <native/AssetManager/assetScanFolderInfo.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzToolsFramework/Asset/AssetUtils.h>
#endif
class QSettings;
namespace AssetProcessor
{
class PlatformConfiguration;
class ScanFolderInfo;
extern const char AssetConfigPlatformDir[];
extern const char AssetProcessorPlatformConfigFileName[];
//! Information for a given recognizer, on a specific platform
//! essentially a plain data holder, but with helper funcs
class AssetPlatformSpec
{
public:
QString m_extraRCParams;
};
//! The data about a particular recognizer, including all platform specs.
//! essentially a plain data holder, but with helper funcs
struct AssetRecognizer
{
AssetRecognizer() = default;
AssetRecognizer(const QString& name, bool testLockSource, int priority,
bool critical, bool supportsCreateJobs, AssetBuilderSDK::FilePatternMatcher patternMatcher,
const QString& version, const AZ::Data::AssetType& productAssetType, bool outputProductDependencies, bool checkServer = false)
: m_name(name)
, m_testLockSource(testLockSource)
, m_priority(priority)
, m_isCritical(critical)
, m_supportsCreateJobs(supportsCreateJobs)
, m_patternMatcher(patternMatcher)
, m_version(version)
, m_productAssetType(productAssetType) // if specified, it allows you to assign a UUID for the type of products directly.
, m_outputProductDependencies(outputProductDependencies)
, m_checkServer(checkServer)
{}
QString m_name;
AssetBuilderSDK::FilePatternMatcher m_patternMatcher;
QString m_version = QString();
// the QString is the Platform Identifier ("pc")
// the AssetPlatformSpec is the details for processing that asset on that platform.
QHash<QString, AssetPlatformSpec> m_platformSpecs;
// an optional parameter which is a UUID of types to assign to the output asset(s)
// if you don't specify one, then a heuristic will be used
AZ::Uuid m_productAssetType = AZ::Uuid::CreateNull();
int m_priority = 0; // used in order to sort these jobs vs other jobs when no other priority is applied (such as platform connected)
bool m_testLockSource = false;
bool m_isCritical = false;
bool m_checkServer = false;
bool m_supportsCreateJobs = false; // used to indicate a recognizer that can respond to a createJobs request
bool m_outputProductDependencies = false;
};
//! Dictionary of Asset Recognizers based on name
typedef QHash<QString, AssetRecognizer> RecognizerContainer;
typedef QList<const AssetRecognizer*> RecognizerPointerContainer;
//! The structure holds information about a particular exclude recognizer
struct ExcludeAssetRecognizer
{
QString m_name;
AssetBuilderSDK::FilePatternMatcher m_patternMatcher;
};
typedef QHash<QString, ExcludeAssetRecognizer> ExcludeRecognizerContainer;
//! Interface to get constant references to asset and exclude recognizers
struct RecognizerConfiguration
{
virtual const RecognizerContainer& GetAssetRecognizerContainer() const = 0;
virtual const ExcludeRecognizerContainer& GetExcludeAssetRecognizerContainer() const = 0;
};
/** Reads the platform ini configuration file to determine
* platforms for which assets needs to be build
*/
class PlatformConfiguration
: public QObject
, public RecognizerConfiguration
{
Q_OBJECT
public:
typedef QPair<QRegExp, QString> RCSpec;
typedef QVector<RCSpec> RCSpecList;
public:
explicit PlatformConfiguration(QObject* pParent = nullptr);
virtual ~PlatformConfiguration() = default;
/** Use this function to parse the set of config files and the gem file to set up the platform config.
* This should be about the only function that is required to be called in order to end up with
* a full configuration.
* Note that order of the config files is relevant - later files override settings in
* files that are earlier.
**/
bool InitializeFromConfigFiles(QString absoluteSystemRoot, QString absoluteAssetRoot, QString gameName, bool addPlatformConfigs = true, bool addGemsConfigs = true);
QString PlatformName(unsigned int platformCrc) const;
QString RendererName(unsigned int rendererCrc) const;
const AZStd::vector<AssetBuilderSDK::PlatformInfo>& GetEnabledPlatforms() const;
const AssetBuilderSDK::PlatformInfo* const GetPlatformByIdentifier(const char* identifier) const;
//! Add AssetProcessor config files from platform specific folders
bool AddPlatformConfigFilePaths(QStringList& configList);
int MetaDataFileTypesCount() const { return m_metaDataFileTypes.count(); }
// Metadata file types are (meta file extension, original file extension - or blank if its tacked on the end instead of replacing).
// so for example if its
// blah.tif + blah.tif.metadata, then its ("metadata", "")
// but if its blah.tif + blah.metadata (rplacing tif, data is lost) then its ("metadata", "tif")
QPair<QString, QString> GetMetaDataFileTypeAt(int pos) const;
// Metadata extensions can also be a real file, to create a dependency on file types if a specific file changes
// so for example, a Metadata file type pair ("Animations/SkeletonList.xml", "i_caf")
// would cause all i_caf files to be re-evaluated when Animations/SkeletonList.xml is modified.
bool IsMetaDataTypeRealFile(QString relativeName) const;
void EnablePlatform(const AssetBuilderSDK::PlatformInfo& platform, bool enable = true);
//! Gets the minumum jobs specified in the configuration file
int GetMinJobs() const;
int GetMaxJobs() const;
//! Return how many scan folders there are
int GetScanFolderCount() const;
//! Return the gems info list
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> GetGemsInformation() const;
//! Retrieve the scan folder at a given index.
AssetProcessor::ScanFolderInfo& GetScanFolderAt(int index);
//! Manually add a scan folder. Also used for testing.
void AddScanFolder(const AssetProcessor::ScanFolderInfo& source, bool isUnitTesting = false);
//! Manually add a recognizer. Used for testing.
void AddRecognizer(const AssetRecognizer& source);
//! Manually remove a recognizer. Used for testing.
void RemoveRecognizer(QString name);
//! Manually add an exclude recognizer. Used for testing.
void AddExcludeRecognizer(const ExcludeAssetRecognizer& recogniser);
//! Manually remove an exclude recognizer. Used for testing.
void RemoveExcludeRecognizer(QString name);
//! Manually add a metadata type. Used for testing.
//! The originalextension, if specified, means this metafile type REPLACES the given extension
//! If not specified (blank) it means that the metafile extension is added onto the end instead and does
//! not remove the original file extension
void AddMetaDataType(const QString& type, const QString& originalExtension);
// ------------------- utility functions --------------------
///! Checks to see whether the input file is an excluded file
bool IsFileExcluded(QString fileName) const;
//! Given a file name, return a container that contains all matching recognizers
//!
//! Returns false if there were no matches, otherwise returns true
bool GetMatchingRecognizers(QString fileName, RecognizerPointerContainer& output) const;
//! given a fileName (as a relative and which scan folder it was found in)
//! Return either an empty string, or the canonical path to a file which overrides it
//! because of folder priority.
//! Note that scanFolderName is only used to exit quickly
//! If its found in any scan folder before it arrives at scanFolderName it will be considered a hit
QString GetOverridingFile(QString relativeName, QString scanFolderName) const;
//! given a relative name, loop over folders and resolve it to a full path with the first existing match.
QString FindFirstMatchingFile(QString relativeName) const;
//! given a relative name with wildcard characters (* allowed) find a set of matching files or optionally folders
QStringList FindWildcardMatches(const QString& sourceFolder, QString relativeName, bool includeFolders = false, bool recursiveSearch = true) const;
//! given a fileName (as a full path), return the database source name which includes the output prefix.
//!
//! for example
//! c:/dev/mygame/textures/texture1.tga
//! ----> [textures/texture1.tga] found under [c:/dev/mygame]
//! c:/dev/engine/models/box01.mdl
//! ----> [models/box01.mdl] found under[c:/dev/engine]
//! note that this does return a database source path by default, which includes the output prefix of the scan folder if present
//! You can override this by setting includeOutputPrefix = false;
bool ConvertToRelativePath(QString fullFileName, QString& databaseSourceName, QString& scanFolderName, bool includeOutputPrefix = true) const;
static bool ConvertToRelativePath(const QString& fullFileName, const ScanFolderInfo* scanFolderInfo, QString& databaseSourceName, bool includeOutputPrefix = true);
//! given a full file name (assumed already fed through the normalization funciton), return the first matching scan folder
const AssetProcessor::ScanFolderInfo* GetScanFolderForFile(const QString& fullFileName) const;
//! Given a scan folder path, get its complete info
const AssetProcessor::ScanFolderInfo* GetScanFolderByPath(const QString& scanFolderPath) const;
const RecognizerContainer& GetAssetRecognizerContainer() const override;
const ExcludeRecognizerContainer& GetExcludeAssetRecognizerContainer() const override;
/** returns true if the config is valid.
* configs are considered invalid if critical information is missing.
* for example, if no recognizers are given, or no platforms are enabled.
* They can also be considered invalid if a critical parse error occurred during load.
*/
bool IsValid() const;
/** If IsValid is false, this will contain the full error string to show to the user.
* Note that IsValid will automatically write this error string to stderror as part of checking
* So this function is there for those wishing to use a GUI.
*/
const AZStd::string& GetError() const;
void PopulatePlatformsForScanFolder(AZStd::vector<AssetBuilderSDK::PlatformInfo>& platformsList, QStringList includeTagsList = QStringList(), QStringList excludeTagsList = QStringList());
protected:
// call this first, to populate the list of platform informations
void ReadPlatformInfosFromConfigFile(QString fileSource);
// call this next, in order to find out what platforms are enabled
void PopulateEnabledPlatforms(QStringList configFiles);
// finaly, call this, in order to delete the platforminfos for non-enabled platforms
void FinalizeEnabledPlatforms();
// iterate over all the gems and add their folders to the "scan folders" list as appropriate.
void AddGemScanFolders(const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList);
void ReadEnabledPlatformsFromConfigFile(QString fileSource);
bool ReadRecognizersFromConfigFile(QString fileSource, bool skipScanFolders = false, QStringList scanFolderPatterns = QStringList() );
void ReadMetaDataFromConfigFile(QString fileSource);
private:
AZStd::vector<AssetBuilderSDK::PlatformInfo> m_enabledPlatforms;
RecognizerContainer m_assetRecognizers;
ExcludeRecognizerContainer m_excludeAssetRecognizers;
AZStd::vector<AssetProcessor::ScanFolderInfo> m_scanFolders;
QList<QPair<QString, QString> > m_metaDataFileTypes;
QSet<QString> m_metaDataRealFiles;
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> m_gemInfoList;
int m_minJobs = 1;
int m_maxJobs = 3;
// used only during file read, keeps the total running list of all the enabled platforms from all config files and command lines
QStringList m_tempEnabledPlatforms;
bool ReadRecognizerFromConfig(AssetRecognizer& target, QSettings& loader); // assumes the group is already selected
///! if non-empty, fatalError contains the error that occurred during read.
///! it will be printed out to the log when
mutable AZStd::string m_fatalError;
};
} // end namespace AssetProcessor
#endif // PLATFORMCONFIGURATION_H
@@ -0,0 +1,69 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
namespace AZ
{
namespace Data
{
struct AssetId;
}
}
namespace AssetProcessor
{
class SpecializedDependencyScanner;
/// Tracks additional information about a potential dependency, such as
/// what string in the file is associated with the dependency, and what scanner.
class PotentialDependencyMetaData
{
public:
PotentialDependencyMetaData() { }
PotentialDependencyMetaData(
AZStd::string sourceString,
AZStd::shared_ptr<SpecializedDependencyScanner> scanner) :
m_sourceString(sourceString),
m_scanner(scanner)
{
}
// Needed to store these in a sorted container, which is used to guarantee
// logs show up in the same order for every sacn.
bool operator<(const PotentialDependencyMetaData& rhs) const
{
return m_sourceString < rhs.m_sourceString;
}
/// The portion of the scanned file that matches the missing dependency.
AZStd::string m_sourceString;
/// Which scanner found this dependency.
AZStd::shared_ptr<SpecializedDependencyScanner> m_scanner;
};
/// Stores the collections of potential product dependencies found in a file.
class PotentialDependencies
{
public:
AZStd::set<PotentialDependencyMetaData> m_paths;
// Using a map instead of a multimap to avoid polluting the results with the
// same missing dependency. If a file references the same potential dependency
// more than once, then only one result will be available.
AZStd::map<AZ::Uuid, PotentialDependencyMetaData> m_uuids;
AZStd::map<AZ::Data::AssetId, PotentialDependencyMetaData> m_assetIds;
};
}
@@ -0,0 +1,43 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Math/Crc.h>
#include <AzCore/std/smart_ptr/enable_shared_from_this.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
namespace IO
{
class GenericStream;
}
}
namespace AssetProcessor
{
class PotentialDependencies;
class SpecializedDependencyScanner : public AZStd::enable_shared_from_this<SpecializedDependencyScanner>
{
public:
virtual ~SpecializedDependencyScanner() {}
virtual bool ScanFileForPotentialDependencies(AZ::IO::GenericStream& fileStream, PotentialDependencies& potentialDependencies, int maxScanIteration) = 0;
virtual bool DoesScannerMatchFileData(AZ::IO::GenericStream& fileStream) = 0;
virtual bool DoesScannerMatchFileExtension(const AZStd::string& fullPath) = 0;
virtual AZStd::string GetVersion() const = 0;
virtual AZStd::string GetName() const = 0;
virtual AZ::Crc32 GetScannerCRC() const = 0;
};
}
@@ -0,0 +1,27 @@
/*
* 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 "ThreadHelper.h"
static AZ_THREAD_LOCAL AZ::s64 s_currentJobId = 0;
namespace AssetProcessor
{
AZ::s64 GetThreadLocalJobId()
{
return s_currentJobId;
}
void SetThreadLocalJobId(AZ::s64 jobId)
{
s_currentJobId = jobId;
}
}
@@ -0,0 +1,143 @@
/*
* 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.
*
*/
#ifndef THREADHELPER_H
#define THREADHELPER_H
#if !defined(Q_MOC_RUN)
#include <QObject>
#include <QMutex>
#include <QMetaObject>
#include <QWaitCondition>
#include <functional>
#include <QThread>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/functional.h>
#endif
// the Thread Helper exists to make it very easy to create a Qt object
// inside a thread, in such a way that the entire construction of the object
// occurs inside the thread.
// we do this by allowing you to specify a factory function that creates your object
// and we arrange to call it on the newly created thread, so that from the very moment
// your object exists, its already on its thread. This is important because Qt objects
// set their thread ownership on create, and if your objects have sub-objects or child objects
// that are members, its important that they too are on the same thread.
// to use this system, use a thread Controller object and call initialize.
// initialize on the Thread Controller automatically blocks until your object is created on the
// target thread and returns your new object, allowing you to then connect signals and slots.
// to clean up, just call destroy().
namespace AssetProcessor
{
AZ::s64 GetThreadLocalJobId();
void SetThreadLocalJobId(AZ::s64 jobId);
class ThreadWorker
: public QObject
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(ThreadWorker, AZ::SystemAllocator, 0)
explicit ThreadWorker(QObject* parent = 0)
: QObject(parent)
{
m_runningThread = new QThread();
}
virtual ~ThreadWorker(){}
//! Destroy function not only stops the running thread
//! but also deletes the ThreadWorker object
void Destroy()
{
// We are calling deletelater() before quiting the thread
// because deletelater will only schedule the object for deletion
// if the thread is running
QThread* threadptr = m_runningThread;
deleteLater();
threadptr->quit();
threadptr->wait();
delete threadptr;
}
Q_SIGNALS:
public Q_SLOTS:
void RunInThread()
{
create();
}
protected:
virtual void create() = 0;
QMutex m_waitConditionMutex;
QWaitCondition m_waitCondition;
QThread* m_runningThread;
};
//! This class helps in creating an instance of the templated
//! QObject class in a new thread.
//! Intialize is a blocking call and than will only return with a pointer to the
//! new object only after the object is created in the new thread.
//! Please note that each instance of this class has to be dynamically allocated.
template<typename T>
class ThreadController
: public ThreadWorker
{
public:
AZ_CLASS_ALLOCATOR(ThreadController<T>, AZ::SystemAllocator, 0)
typedef AZStd::function<T* ()> FactoryFunctionType;
ThreadController()
: ThreadWorker()
{
m_runningThread->setObjectName(T::staticMetaObject.className());
m_runningThread->start();
this->moveToThread(m_runningThread);
}
virtual ~ThreadController()
{
}
T* initialize(FactoryFunctionType callback = nullptr)
{
m_function = callback;
m_waitConditionMutex.lock();
QMetaObject::invokeMethod(this, "RunInThread", Qt::QueuedConnection);
m_waitCondition.wait(&m_waitConditionMutex);
m_waitConditionMutex.unlock();
return m_instance;
}
virtual void create() override
{
if (m_function)
{
m_instance = m_function();
}
m_waitCondition.wakeOne();
}
private:
T* m_instance;
FactoryFunctionType m_function;
};
}
#endif // THREADHELPER_H
@@ -0,0 +1,208 @@
/*
* 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 "native/utilities/UnitTestShaderCompilerServer.h"
#include "native/assetprocessor.h"
#include <QTcpServer>
#include <QTcpSocket>
UnitTestShaderCompilerServer::UnitTestShaderCompilerServer(QObject* parent)
: QObject(parent)
, m_incomingPayload("This is a test string")
, m_outgoingPayload("Test string validated")
, m_serverAddress(QString())
, m_serverPort(0)
, m_server(nullptr)
, m_socket(nullptr)
, m_isPayloadSizeKnown(false)
, m_totalBytesReadInPayload(0)
, m_bytesRemainingInPayload(0)
, m_bytesRemainingInPayloadSize(0)
, m_totalBytesReadInPayloadSize(0)
, m_payloadSize(0)
{
}
UnitTestShaderCompilerServer::~UnitTestShaderCompilerServer()
{
if (m_server != nullptr)
{
m_server->deleteLater();
}
}
void UnitTestShaderCompilerServer::Init(QString serverAddress, int serverPort)
{
m_serverAddress = serverAddress;
m_serverPort = serverPort;
m_server = new QTcpServer(this);
connect(m_server, SIGNAL(newConnection()), this, SLOT(newConnection()));
startServer();
}
void UnitTestShaderCompilerServer::startServer()
{
if (!m_server->isListening())
{
if (!m_server->listen(QHostAddress(m_serverAddress), m_serverPort))
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Server %s could not start.\n", m_serverAddress.toUtf8().data());
emit errorMessage("Server could not start ");
}
}
}
void UnitTestShaderCompilerServer::closeSocket()
{
m_socket->close();
}
void UnitTestShaderCompilerServer::newConnection()
{
m_socket = m_server->nextPendingConnection();
if (m_serverStatus == BadServer_DisconnectAfterConnect)
{
closeSocket();
return;
}
connect(m_socket, SIGNAL(readyRead()), this, SLOT(incomingMessage()));
connect(m_socket, SIGNAL(disconnected()), m_socket, SLOT(deleteLater()));
m_isPayloadSizeKnown = false;
m_totalBytesReadInPayload = 0;
m_bytesRemainingInPayload = 0;
m_totalBytesReadInPayloadSize = 0;
m_bytesRemainingInPayloadSize = 0;
m_payloadSize = 0;
m_payload.clear();
}
void UnitTestShaderCompilerServer::incomingMessage()
{
if (m_serverStatus == BadServer_DisconnectAfterConnect || m_socket->bytesAvailable() == 0)
{
return;
}
if (!m_isPayloadSizeKnown)
{
//reading payload size
m_bytesRemainingInPayloadSize = static_cast<qint64>(sizeof(qint64)) - m_totalBytesReadInPayloadSize;
qint64 bytesAvailable = m_socket->bytesAvailable();
qint64 bytesToread = qMin(bytesAvailable, m_bytesRemainingInPayloadSize);
qint64 bytesRead = m_socket->read(reinterpret_cast<char*>(&m_payloadSize) + m_totalBytesReadInPayloadSize, bytesToread);
if (bytesRead < 0)
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Connection Lost : Cannot read from socket.\n");
emit errorMessage("Connection Lost:Cannot read from socket");
return;
}
m_totalBytesReadInPayloadSize += bytesRead;
if (m_totalBytesReadInPayloadSize == static_cast<qint64>(sizeof(qint64)))
{
m_isPayloadSizeKnown = true;
m_payload.resize(aznumeric_cast<int>(m_payloadSize));
}
if (m_socket->bytesAvailable() > 0)
{
QMetaObject::invokeMethod(this, "incomingMessage", Qt::QueuedConnection);
return;
}
}
else
{
// payload size is known,read the payload
m_bytesRemainingInPayload = m_payloadSize - m_totalBytesReadInPayload;
qint64 bytesAvailable = m_socket->bytesAvailable();
qint64 bytesToread = qMin(bytesAvailable, m_bytesRemainingInPayload);
qint64 bytesRead = m_socket->read(m_payload.data() + m_totalBytesReadInPayload, bytesToread);
if (bytesRead < 0)
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Connection Lost:Cannot read from socket.\n");
emit errorMessage("Connection Lost:Cannot read from socket");
return;
}
m_totalBytesReadInPayload += bytesRead;
if (m_socket->bytesAvailable() > 0)
{
QMetaObject::invokeMethod(this, "incomingMessage", Qt::QueuedConnection);
return;
}
if (m_totalBytesReadInPayload != m_payloadSize)
{
return;
}
if (m_serverStatus == BadServer_ReadsPayloadAndDisconnect)
{
closeSocket();
return;
}
//we have the complete payload here
//compare it with the expected payload
if (QString::compare(QString(m_payload), m_incomingPayload) == 0)
{
QByteArray payload;
qint64 messageSize;
constructPayload(payload);
messageSize = static_cast<qint64>(payload.size());
if (m_serverStatus == BadServer_SendsIncompletePayload)
{
messageSize -= 5;
}
qint64 bytesWritten = 0;
while (bytesWritten != messageSize)
{
qint64 currentWrite = m_socket->write(payload.data() + bytesWritten, messageSize - bytesWritten);
if (currentWrite < 0)
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Connection Lost:Cannot write to socket.\n");
emit errorMessage("Connection Lost:Cannot write to socket");
return;
}
bytesWritten += currentWrite;
}
}
else
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Server Payload is corrupt.\n");
emit errorMessage("Server Payload is corrupt");
return;
}
}
}
void UnitTestShaderCompilerServer::setServerStatus(const ServerStatus& serverStatus)
{
m_serverStatus = serverStatus;
}
void UnitTestShaderCompilerServer::constructPayload(QByteArray& payload)
{
//construct test response payload
quint8 status = 1;
unsigned int outgoingTextLength = static_cast<unsigned int>(m_outgoingPayload.size());
payload.resize(outgoingTextLength + sizeof(unsigned int) + sizeof(quint8));
memcpy(payload.data(), reinterpret_cast<char*>(&outgoingTextLength), sizeof(unsigned int));
memcpy(payload.data() + sizeof(unsigned int), reinterpret_cast<char*>(&status), sizeof(quint8));
memcpy(payload.data() + sizeof(unsigned int) + sizeof(quint8), m_outgoingPayload.toStdString().c_str(), outgoingTextLength);
}
@@ -0,0 +1,73 @@
/*
* 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.
*
*/
#ifndef UNITTESTSHADERCOMPILERSERVER_H
#define UNITTESTSHADERCOMPILERSERVER_H
#if !defined(Q_MOC_RUN)
#include <QString>
#include <QByteArray>
#include <QObject>
#endif
class QTcpServer;
class QTcpSocket;
/** This Class will be used by the UnitTest class for testing
* the Shader Compiler Manager Class.Basically we change the value
* of the server status variable and see whether we are getting the
* expected response from the shader compiler.
*/
class UnitTestShaderCompilerServer
: public QObject
{
Q_OBJECT
public:
explicit UnitTestShaderCompilerServer(QObject* parent = 0);
virtual ~UnitTestShaderCompilerServer();
enum ServerStatus
{
GoodServer,
BadServer_SendsIncompletePayload,
BadServer_ReadsPayloadAndDisconnect,
BadServer_DisconnectAfterConnect,
};
void constructPayload(QByteArray& payload);
void Init(QString serverAddress, int serverPort);
void closeSocket();
void startServer();
void setServerStatus(const ServerStatus& serverStatus);
signals:
void errorMessage(QString error);
public slots:
void newConnection();
void incomingMessage();
private:
QTcpSocket* m_socket;
QTcpServer* m_server;
ServerStatus m_serverStatus;
QString m_serverAddress;
int m_serverPort;
QString m_incomingPayload;
QString m_outgoingPayload;
bool m_isPayloadSizeKnown;
qint64 m_payloadSize;
qint64 m_bytesRemainingInPayloadSize;
qint64 m_totalBytesReadInPayloadSize;
QByteArray m_payload;
qint64 m_bytesRemainingInPayload;
qint64 m_totalBytesReadInPayload;
};
#endif //UNITTESTSHADERCOMPILERSERVER_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,335 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/PlatformIncl.h>
#include <cstdlib> // for size_t
#include <QString>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzFramework/Logging/LogFile.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include "native/assetprocessor.h"
#include "native/utilities/AssetUtilEBusHelper.h"
#include "native/utilities/ApplicationManagerAPI.h"
#include <AzToolsFramework/Asset/AssetProcessorMessages.h>
namespace AzToolsFramework
{
namespace AssetSystem
{
struct JobInfo;
}
namespace Logging
{
class LogLine;
}
}
class QStringList;
class QDir;
namespace AssetProcessor
{
class PlatformConfiguration;
struct AssetRecognizer;
class JobEntry;
class AssetDatabaseConnection;
struct BuilderParams;
}
namespace AssetUtilities
{
inline constexpr char GameFolderOverrideParameter[] = "gamefolder";
//! Set precision fingerprint timestamps will be truncated to avoid mismatches across systems/packaging with different file timestamp precisions
//! Timestamps default to milliseconds. A value of 1 will keep the default millisecond precision. A value of 1000 will reduce the precision to seconds
void SetTruncateFingerprintTimestamp(int precision);
//! Sets an override for using file hashing. If override is true, the value of enable will be used instead of the settings file
void SetUseFileHashOverride(bool override, bool enable);
//! Compute the root asset folder by scanning for marker files such as root.ini
//! By Default, queries the EngineRootFolder value from within the SettingsRegistry
bool ComputeAssetRoot(QDir& root, const QDir* assetRootOverride = nullptr);
//! Get the engine root folder by looking up the EngineRootFolder key from the Settings Registry
bool ComputeEngineRoot(QDir& root, const QDir* engineRootOverride = nullptr);
//! Reset the asset root to not be cached anymore. Generally only useful for tests
void ResetAssetRoot();
//! Reset the game name to not be cached anymore. Generally only useful for tests
void ResetGameName();
//! Copy all files from the source directory to the destination directory, returns true if successfull, else return false
bool CopyDirectory(QDir source, QDir destination);
//! makes the file writable
//! return true if operation is successful, otherwise return false
bool MakeFileWritable(QString filename);
//! Check to see if we can Lock the file
bool CheckCanLock(QString filename);
//! Updates the branch token in the bootstrap file
bool UpdateBranchToken();
//! Checks to see if the asset processor is running in server mode
bool InServerMode();
//! Checks the args for the server parameter, returns true if found otherwise false.
bool CheckServerMode();
//! Reads the server address from the config file.
QString ServerAddress();
bool ShouldUseFileHashing();
//! Determine the name of the current game - for example, SamplesProject
//! Can be overridden by passing in a non-empty gameNameOverride
//! The override will persist if the GameName wasn't set previously or
//! force=true is supplied
QString ComputeGameName(QString gameNameOverride = QString(), bool force = false);
//! Reads the white list directly from the bootstrap file
QString ReadWhitelistFromSettingsRegistry(QString initialFolder = QString());
//! Reads the white list directly from the bootstrap file
QString ReadRemoteIpFromSettingsRegistry(QString initialFolder = QString());
//! Writes the white list directly to the bootstrap file
bool WriteWhitelistToBootstrap(QStringList whiteList);
//! Writes the remote ip directly to the bootstrap file
bool WriteRemoteIpToBootstrap(QString remoteIp);
//! Reads the game name directly from the bootstrap file
QString ReadGameNameFromSettingsRegistry(QString initialFolder = QString());
//! Reads the listening port from the bootstrap file
//! By default the listening port is 45643
quint16 ReadListeningPortFromSettingsRegistry(QString initialFolder = QString());
//! Reads platforms from command line
QStringList ReadPlatformsFromCommandLine();
//! Copies the sourceFile to the outputFile,returns true if the copy operation succeeds otherwise return false
//! This function will try deleting the outputFile first,if it exists, before doing the copy operation
bool CopyFileWithTimeout(QString sourceFile, QString outputFile, unsigned int waitTimeinSeconds = 0);
//! Moves the sourceFile to the outputFile,returns true if the move operation succeeds otherwise return false
//! This function will try deleting the outputFile first,if it exists, before doing the move operation
bool MoveFileWithTimeout(QString sourceFile, QString outputFile, unsigned int waitTimeinSeconds = 0);
//! Create directory with retries, returns true if the create operation succeeds otherwise return false
bool CreateDirectoryWithTimeout(QDir dir, unsigned int waitTimeinSeconds = 0);
//! Normalize and removes any alias from the path
QString NormalizeAndRemoveAlias(QString path);
//! Determine the Job Description for a job, for now it is the name of the recognizer
QString ComputeJobDescription(const AssetProcessor::AssetRecognizer* recognizer);
//! Compute the root of the cache for the current project.
//! This is generally the "cache" folder, subfolder gamedir.
bool ComputeProjectCacheRoot(QDir& projectCacheRoot);
//! Compute the folder that will be used for fence files.
bool ComputeFenceDirectory(QDir& fenceDir);
//! Converts all slashes to forward slashes, removes double slashes,
//! replaces all indirections such as '.' or '..' as appropriate.
//! On windows, the drive letter (if present) is converted to uppercase.
//! Besides that, all case is preserved.
QString NormalizeFilePath(const QString& filePath);
void NormalizeFilePaths(QStringList& filePaths);
//! given a directory name, normalize it the same way as the above file path normalizer
//! does not convert into absolute path - do that yourself before calling this if you want that
QString NormalizeDirectoryPath(const QString& directoryPath);
// UUID generation defaults to lowercase SHA1 of the source name, this does normalization and such
AZ::Uuid CreateSafeSourceUUIDFromName(const char* sourceName, bool caseInsensitive = true);
//! Compute a CRC given a null-terminated string
//! @param[in] priorCRC If supplied, continues an existing CRC by feeding it more data
unsigned int ComputeCRC32(const char* inString, unsigned int priorCRC = 0xFFFFFFFF);
//! Compute a CRC given data and a size
//! @param[in] priorCRC If supplied, continues an existing CRC by feeding it more data
unsigned int ComputeCRC32(const char* data, size_t dataSize, unsigned int priorCRC = 0xFFFFFFFF);
//! Compute a CRC given data and a size
//! @param[in] priorCRC If supplied, continues an existing CRC by feeding it more data
template <typename T>
unsigned int ComputeCRC32(const T* data, size_t dataSize, unsigned int priorCRC = 0xFFFFFFFF)
{
return ComputeCRC32(reinterpret_cast<const char*>(data), dataSize, priorCRC);
}
//! Compute a CRC given a null-terminated string
//! @param[in] priorCRC If supplied, continues an existing CRC by feeding it more data
unsigned int ComputeCRC32Lowercase(const char* inString, unsigned int priorCRC = 0xFFFFFFFF);
//! Compute a CRC given data and a size
//! @param[in] priorCRC If supplied, continues an existing CRC by feeding it more data
unsigned int ComputeCRC32Lowercase(const char* data, size_t dataSize, unsigned int priorCRC = 0xFFFFFFFF);
//! Compute a CRC given data and a size
//! @param[in] priorCRC If supplied, continues an existing CRC by feeding it more data
template <typename T>
unsigned int ComputeCRC32Lowercase(const T* data, size_t dataSize, unsigned int priorCRC = 0xFFFFFFFF)
{
return ComputeCRC32Lowercase(reinterpret_cast<const char*>(data), dataSize, priorCRC);
}
//! attempt to create a workspace for yourself to use as scratch-space, at that starting root folder.
//! If it succeeds, it will return true and set the result to the final absolute folder name.
//! this includes creation of temp folder with numbered/lettered temp characters in it.
//! Note that its up to you to clean this temp workspace up. It will not automatically be deleted!
//! If you fail to delete the temp workspace, it will eventually fill the folder up and cause problems.
bool CreateTempWorkspace(QString startFolder, QString& result);
//! Create a temp workspace in a default location
//! If it succeeds, it will return true and set the result to the final absolute folder name.
//! If it fails, it will return false and result will be an empty string
//! Note that its up to you to clean this temp workspace up. It will not automatically be deleted!
//! If you fail to delete the temp workspace, it will eventually fill the folder up and cause problems.
bool CreateTempWorkspace(QString& result);
bool CreateTempRootFolder(QString startFolder, QDir& tempRoot);
AZStd::string ComputeJobLogFolder();
AZStd::string ComputeJobLogFileName(const AzToolsFramework::AssetSystem::JobInfo& jobInfo);
AZStd::string ComputeJobLogFileName(const AssetProcessor::JobEntry& jobEntry);
AZStd::string ComputeJobLogFileName(const AssetBuilderSDK::CreateJobsRequest& createJobsRequest);
enum class ReadJobLogResult
{
Success,
MissingFileIO,
MissingLogFile,
EmptyLogFile,
};
ReadJobLogResult ReadJobLog(AzToolsFramework::AssetSystem::JobInfo& jobInfo, AzToolsFramework::AssetSystem::AssetJobLogResponse& response);
ReadJobLogResult ReadJobLog(const char* absolutePath, AzToolsFramework::AssetSystem::AssetJobLogResponse& response);
//! interrogate a given file, which is specified as a full path name, and generate a fingerprint for it.
unsigned int GenerateFingerprint(const AssetProcessor::JobDetails& jobDetail);
//! Returns a hash of the contents of the specified file
// hashMsDelay is only for automated tests to test that writing to a file while it's hashing does not cause a crash.
// hashMsDelay is not used in non-unit test builds.
AZ::u64 GetFileHash(const char* filePath, bool force = false, AZ::IO::SizeType* bytesReadOut = nullptr, int hashMsDelay = 0);
inline constexpr AZ::u64 FileHashBufferSize = 1024 * 64;
//! Adjusts a timestamp to fix timezone settings and account for any precision adjustment needed
std::uint64_t AdjustTimestamp(QDateTime timestamp);
// Generates a fingerprint string based on details of the file, will return the string "0" if the file does not exist.
// note that the 'name to use' can be blank, but it used to disambiguate between files that have the same
// modtime and size.
AZStd::string GetFileFingerprint(const AZStd::string& absolutePath, const AZStd::string& nameToUse);
QString GuessProductNameInDatabase(QString path, QString platform, AssetProcessor::AssetDatabaseConnection* databaseConnection);
//! Given a list of source asset Uuids, it returns a list that contains the same source assets Uuids along with all of their dependencies
//! which are discovered recursively. All the returned Uuids are unique, meaning they appear once in the returned list.
AZStd::vector<AZ::Uuid> CollectAssetAndDependenciesRecursively(AssetProcessor::AssetDatabaseConnection& databaseConnection, const AZStd::vector<AZ::Uuid>& assetList);
// A utility function which checks the given path starting at the root and updates the relative path to be the actual case correct path.
bool UpdateToCorrectCase(const QString& rootPath, QString& relativePathFromRoot);
class BuilderFilePatternMatcher
: public AssetBuilderSDK::FilePatternMatcher
{
public:
BuilderFilePatternMatcher() = default;
BuilderFilePatternMatcher(const BuilderFilePatternMatcher& copy);
BuilderFilePatternMatcher(const AssetBuilderSDK::AssetBuilderPattern& pattern, const AZ::Uuid& builderDescID);
const AZ::Uuid& GetBuilderDescID() const;
protected:
AZ::Uuid m_builderDescID;
};
//! QuitListener is an utility class that can be used to listen for application quit notification
class QuitListener
: public AssetProcessor::ApplicationManagerNotifications::Bus::Handler
{
public:
QuitListener();
~QuitListener();
/// ApplicationManagerNotifications::Bus::Handler
void ApplicationShutdownRequested() override;
bool WasQuitRequested() const;
private:
AZStd::atomic<bool> m_requestedQuit;
};
//! JobLogTraceListener listens for job messages
class JobLogTraceListener
: public AZ::Debug::TraceMessageBus::Handler
{
public:
JobLogTraceListener(const AZStd::string& logFileName, AZ::s64 jobKey, bool overwriteLogFile = false);
JobLogTraceListener(const AzToolsFramework::AssetSystem::JobInfo& jobInfo, bool overwriteLogFile = false);
JobLogTraceListener(const AssetProcessor::JobEntry& jobEntry, bool overwriteLogFile = false);
~JobLogTraceListener();
//////////////////////////////////////////////////////////////////////////
// AZ::Debug::TraceMessagesBus - we actually ignore all outputs except those for our ID.
bool OnAssert(const char* message) override;
bool OnException(const char* message) override;
bool OnPreError(const char* window, const char* file, int line, const char* func, const char* message) override;
bool OnWarning(const char* window, const char* message) override;
//////////////////////////////////////////////////////////////////////////
bool OnPrintf(const char* window, const char* message) override;
//////////////////////////////////////////////////////////////////////////
void AppendLog(AzToolsFramework::Logging::LogLine& logLine);
AZ::s64 GetErrorCount() const;
AZ::s64 GetWarningCount() const;
void AddError();
void AddWarning();
private:
AZStd::unique_ptr<AzFramework::LogFile> m_logFile;
AZStd::string m_logFileName;
AZ::s64 m_runKey = 0;
// using m_isLogging bool to prevent an infinite loop which can happen if an error/warning happens when trying to create an invalid logFile,
// because it will cause the appendLog function to be called again, which will again try to create that log file.
bool m_isLogging = false;
bool m_inException = false;
//! If true, log file will be overwritten instead of appended
bool m_forceOverwriteLog = false;
AZ::s64 m_errorCount = 0;
AZ::s64 m_warningCount = 0;
void AppendLog(AzFramework::LogFile::SeverityLevel severity, const char* window, const char* message);
};
} // namespace AssetUtilities
@@ -0,0 +1,208 @@
/*
* 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 <QGuiApplication>
#include <QSettings>
#include <QScreen>
#include "windowscreen.h"
WindowScreen::WindowScreen(QObject* parent)
: QObject(parent)
{
}
WindowScreen::~WindowScreen()
{
}
void WindowScreen::loadSettings(int width, int height, int minimumWidth, int minimumHeight)
{
QSettings loader;
setPositionX(loader.value(m_windowName + "/" + "PositionX", 99999).toInt());
setPositionY(loader.value(m_windowName + "/" + "PositionY", 99999).toInt());
setWidth(loader.value(m_windowName + "/" + "Width", width).toInt());
setHeight(loader.value(m_windowName + "/" + "Height", height).toInt());
setWindowState(static_cast<QWindow::Visibility>(loader.value(m_windowName + "/" + "WindowState", QWindow::Windowed).toInt()));
if (!CheckSettings(minimumWidth, minimumHeight))
{
CenterWindowInPrimaryScreen(minimumWidth, minimumHeight);
}
}
void WindowScreen::saveSettings()
{
QSettings saver;
saver.remove(m_windowName + "/" + "WindowState");
saver.setValue(m_windowName + "/" + "WindowState", m_windowCurrentInfo.m_windowState);
//If the window current state is maximised or fullscreen than we save x,y,width and height
//of the window prev state so that it can be minimised correctly the next time we start the application
WindowScreenInfo* screenInfo = nullptr;
if (m_windowCurrentInfo.m_windowState != QWindow::Maximized && m_windowCurrentInfo.m_windowState != QWindow::FullScreen)
{
screenInfo = &m_windowCurrentInfo;
}
else
{
screenInfo = &m_windowPreviousInfo;
}
saver.remove(m_windowName + "/" + "PositionX");
saver.setValue(m_windowName + "/" + "PositionX", screenInfo->m_positionX);
saver.remove(m_windowName + "/" + "PositionY");
saver.setValue(m_windowName + "/" + "PositionY", screenInfo->m_positionY);
saver.remove(m_windowName + "/" + "Width");
saver.setValue(m_windowName + "/" + "Width", screenInfo->m_width);
saver.remove(m_windowName + "/" + "Height");
saver.setValue(m_windowName + "/" + "Height", screenInfo->m_height);
}
bool WindowScreen::CheckSettings(int minimumWidth, int minimumHeight)
{
if (m_windowCurrentInfo.m_positionX == 99999 && m_windowCurrentInfo.m_positionY == 99999)
{
//we are running the app first time and there is no settings to load from,
//we will centre the window ourselves
return false;
}
if (m_windowCurrentInfo.m_width < minimumWidth || m_windowCurrentInfo.m_height < minimumHeight)
{
return false;
}
//Check whether the window is fully inside the display
QScreen* screen = QGuiApplication::primaryScreen();
bool isPosXOK = m_windowCurrentInfo.m_positionX >= (screen->availableVirtualGeometry().x()) && m_windowCurrentInfo.m_positionX <= (screen->availableVirtualGeometry().width()) ? true : false;
bool isPosYOK = m_windowCurrentInfo.m_positionY >= (screen->availableVirtualGeometry().y()) && m_windowCurrentInfo.m_positionY <= (screen->availableVirtualGeometry().height()) ? true : false;
bool isWidthOK = m_windowCurrentInfo.m_positionX + m_windowCurrentInfo.m_width <= screen->availableVirtualSize().width() ? true : false;
bool isHeightOK = m_windowCurrentInfo.m_positionY + m_windowCurrentInfo.m_height <= screen->availableVirtualSize().height() ? true : false;
return isPosXOK && isPosYOK && isWidthOK && isHeightOK;
}
void WindowScreen::CenterWindowInPrimaryScreen(int minimumWidth, int minimumHeight)
{
//check to see whether the width is more than minimum width
if (m_windowCurrentInfo.m_width < minimumWidth)
{
m_windowCurrentInfo.m_width = minimumWidth;
}
//check to see whether the height is more than minimum height
if (m_windowCurrentInfo.m_height < minimumHeight)
{
m_windowCurrentInfo.m_height = minimumHeight;
}
QScreen* screen = QGuiApplication::primaryScreen();
m_windowCurrentInfo.m_positionX = qRound((screen->availableGeometry().width() - screen->availableGeometry().x()) / 2.0 - m_windowCurrentInfo.m_width / 2.0);
m_windowCurrentInfo.m_positionY = qRound((screen->availableGeometry().height() - screen->availableGeometry().y()) / 2.0 - m_windowCurrentInfo.m_height / 2.0);
m_windowCurrentInfo.m_windowState = QWindow::Windowed;
}
int WindowScreen::positionX() const
{
return m_windowCurrentInfo.m_positionX;
}
void WindowScreen::setPositionX(int posX)
{
if (posX == m_windowCurrentInfo.m_positionX)
{
return;
}
m_windowPreviousInfo.m_positionX = m_windowCurrentInfo.m_positionX;
m_windowCurrentInfo.m_positionX = posX;
Q_EMIT positionXChanged();
}
int WindowScreen::positionY() const
{
return m_windowCurrentInfo.m_positionY;
}
void WindowScreen::setPositionY(int posY)
{
if (posY == m_windowCurrentInfo.m_positionY)
{
return;
}
m_windowPreviousInfo.m_positionY = m_windowCurrentInfo.m_positionY;
m_windowCurrentInfo.m_positionY = posY;
Q_EMIT positionYChanged();
}
int WindowScreen::width() const
{
return m_windowCurrentInfo.m_width;
}
void WindowScreen::setWidth(int width)
{
if (width == m_windowCurrentInfo.m_width)
{
return;
}
m_windowPreviousInfo.m_width = m_windowCurrentInfo.m_width;
m_windowCurrentInfo.m_width = width;
Q_EMIT widthChanged();
}
int WindowScreen::height() const
{
return m_windowCurrentInfo.m_height;
}
void WindowScreen::setHeight(int height)
{
if (height == m_windowCurrentInfo.m_height)
{
return;
}
m_windowPreviousInfo.m_height = m_windowCurrentInfo.m_height;
m_windowCurrentInfo.m_height = height;
Q_EMIT heightChanged();
}
QString WindowScreen::windowName() const
{
return m_windowName;
}
void WindowScreen::setWindowName(QString windowName)
{
m_windowName = windowName;
}
QWindow::Visibility WindowScreen::windowState() const
{
return m_windowCurrentInfo.m_windowState;
}
void WindowScreen::setWindowState(QWindow::Visibility state)
{
if (m_windowCurrentInfo.m_windowState == state)
{
return;
}
m_windowPreviousInfo.m_windowState = m_windowCurrentInfo.m_windowState;
m_windowCurrentInfo.m_windowState = state;
Q_EMIT windowStateChanged();
}
@@ -0,0 +1,102 @@
/*
* 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.
*
*/
#ifndef WINDOWSCREEN_H
#define WINDOWSCREEN_H
#if !defined(Q_MOC_RUN)
#include <QObject>
#include <QString>
#include <QWindow>
#endif
/** The WindowScreenInfo struct stores the x, y, width, height and
* other window state info.
*/
struct WindowScreenInfo
{
int m_positionX = 0;
int m_positionY = 0;
int m_width = 0;
int m_height = 0;
QWindow::Visibility m_windowState = QWindow::Windowed;
};
/** The WindowScreen class is responsible for storing information about the
* application window.
*/
class WindowScreen
: public QObject
{
Q_OBJECT
Q_PROPERTY(int positionX READ positionX WRITE setPositionX NOTIFY positionXChanged)
Q_PROPERTY(int positionY READ positionY WRITE setPositionY NOTIFY positionYChanged)
Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged)
Q_PROPERTY(int height READ height WRITE setHeight NOTIFY heightChanged)
Q_PROPERTY(QWindow::Visibility windowState READ windowState WRITE setWindowState NOTIFY windowStateChanged)
Q_PROPERTY(QString windowName READ windowName WRITE setWindowName)
public:
/// standard Qt constructor
explicit WindowScreen(QObject* parent = 0);
virtual ~WindowScreen();
///Loads settings
Q_INVOKABLE void loadSettings(int width, int heigth, int minimumWidth, int minimumHeight);
///save settings
Q_INVOKABLE void saveSettings();
int positionX() const;
void setPositionX(int positionX);
int positionY() const;
void setPositionY(int positionY);
int width() const;
void setWidth(int width);
int height() const;
void setHeight(int height);
bool isMaximize() const;
void setIsMaximize(bool isMaximize);
QString windowName() const;
void setWindowName(QString windowName);
QWindow::Visibility windowState() const;
void setWindowState(QWindow::Visibility state);
Q_SIGNALS:
void positionXChanged();
void positionYChanged();
void widthChanged();
void heightChanged();
void windowStateChanged();
private:
///Check to see whether settings are valid or not
bool CheckSettings(int minimumWidth, int minimumHeight);
///Centers the window in primary screen
void CenterWindowInPrimaryScreen(int minimumWidth, int minimumHeight);
WindowScreenInfo m_windowCurrentInfo;
WindowScreenInfo m_windowPreviousInfo;
QString m_windowName = QString();
};
#endif // WINDOWSCREEN_H