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,301 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/Trace.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Slice/SliceSystemComponent.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/Asset/AssetCatalogComponent.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzFramework/Input/System/InputSystemComponent.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
#include <AzToolsFramework/Component/EditorComponentAPIComponent.h>
#include <AzToolsFramework/Entity/EditorEntityActionComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/Entity/EditorEntityModelComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySearchComponent.h>
#include <AzToolsFramework/Slice/SliceMetadataEntityContextComponent.h>
#include <AzToolsFramework/ToolsComponents/ToolsAssetCatalogComponent.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AssetBuilderApplication.h>
#include <AssetBuilderComponent.h>
#include <AssetBuilderInfo.h>
#include <AzCore/Interface/Interface.h>
namespace AssetBuilder
{
//! This function returns the build system target name
AZStd::string_view GetBuildTargetName()
{
#if !defined (LY_CMAKE_TARGET)
#error "LY_CMAKE_TARGET must be defined in order to add this source file to a CMake executable target"
#endif
return AZStd::string_view{ LY_CMAKE_TARGET };
}
}
AZ::ComponentTypeList AssetBuilderApplication::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList components = AzFramework::Application::GetRequiredSystemComponents();
for (auto iter = components.begin(); iter != components.end();)
{
if (*iter == azrtti_typeid<AZ::UserSettingsComponent>()
|| *iter == azrtti_typeid<AzFramework::InputSystemComponent>()
|| *iter == azrtti_typeid<AzFramework::AssetCatalogComponent>()
)
{
iter = components.erase(iter);
}
else
{
++iter;
}
}
components.insert(components.end(), {
azrtti_typeid<AZ::SliceSystemComponent>(),
azrtti_typeid<AzToolsFramework::SliceMetadataEntityContextComponent>(),
azrtti_typeid<AssetBuilderComponent>(),
azrtti_typeid<AssetProcessor::ToolsAssetCatalogComponent>(),
azrtti_typeid<AzToolsFramework::AssetSystem::AssetSystemComponent>(),
azrtti_typeid<AzToolsFramework::Components::EditorComponentAPIComponent>(),
azrtti_typeid<AzToolsFramework::Components::EditorEntityActionComponent>(),
azrtti_typeid<AzToolsFramework::Components::EditorEntitySearchComponent>(),
azrtti_typeid<AzToolsFramework::Components::EditorEntityModelComponent>(),
azrtti_typeid<AzToolsFramework::EditorEntityContextComponent>(),
});
return components;
}
AssetBuilderApplication::AssetBuilderApplication(int* argc, char*** argv)
: AzToolsFramework::ToolsApplication(argc, argv)
, m_qtApplication(*argc, *argv)
{
// The settings registry has been created at this point
auto settingsRegistry = AZ::SettingsRegistry::Get();
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
*settingsRegistry, AssetBuilder::GetBuildTargetName());
// Override the /Amazon/AzCore/Bootstrap/sys_game_folder entry in the Settings Registry using the -gameName parameter
if (m_commandLine.GetNumSwitchValues("gameName") > 0)
{
const AZStd::string& gameFolderOverride = m_commandLine.GetSwitchValue("gameName", 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::Interface<IBuilderApplication>::Register(this);
}
AssetBuilderApplication::~AssetBuilderApplication()
{
AZ::Interface<IBuilderApplication>::Unregister(this);
}
void AssetBuilderApplication::RegisterCoreComponents()
{
AzToolsFramework::ToolsApplication::RegisterCoreComponents();
RegisterComponentDescriptor(AssetBuilderComponent::CreateDescriptor());
RegisterComponentDescriptor(AssetProcessor::ToolsAssetCatalogComponent::CreateDescriptor());
}
void AssetBuilderApplication::StartCommon(AZ::Entity* systemEntity)
{
InstallCtrlHandler();
// Merge in the SettingsRegistry for the game being processed. This does not
// necessarily correspond to the project name in the bootstrap.cfg since it
// the AssetBuilder supports overriding the gameName on the command line
AZ::SettingsRegistryInterface& registry = *AZ::SettingsRegistry::Get();
AZ::SettingsRegistryInterface::FixedValueString gameName;
if (m_commandLine.GetNumSwitchValues("gameName") > 0)
{
gameName = AZStd::string_view(m_commandLine.GetSwitchValue("gameName", 0));
}
// Add the supplied gameName to the specialization key in the registry
if (!gameName.empty())
{
auto gameNameSpecialization = AZ::SettingsRegistryInterface::FixedValueString::format("%s/%.*s",
AZ::SettingsRegistryMergeUtils::SpecializationsRootKey, aznumeric_cast<int>(gameName.size()), gameName.data());
registry.Set(gameNameSpecialization, 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 bootstrapGameName; registry.Get(bootstrapGameName, projectKey) && !bootstrapGameName.empty())
{
registry.Set(AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::SpecializationsRootKey, bootstrapGameName.c_str()),
true);
}
}
// Retrieve specializations from the Settings Registry and ComponentApplication derived classes
AZ::SettingsRegistryInterface::Specializations specializations;
SetSettingsRegistrySpecializations(specializations);
// Merge the SettingsRegistry file again using gameName as an additional specialization
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry,
AZ_TRAIT_OS_PLATFORM_CODENAME, specializations);
AzToolsFramework::ToolsApplication::StartCommon(systemEntity);
#if defined(AZ_PLATFORM_MAC)
// The asset builder needs to start astcenc as a child process to compress textures.
// astcenc is started by the PVRTexLib dynamic library. In order for it to be able to find
// the executable, we need to set the PATH environment variable.
AZStd::string exeFolder;
AZ::ComponentApplicationBus::BroadcastResult(exeFolder, &AZ::ComponentApplicationBus::Events::GetExecutableFolder);
setenv("PATH", exeFolder.c_str(), 1);
#endif // AZ_PLATFORM_MAC
AZStd::string gameRoot;
if (m_commandLine.GetNumSwitchValues("gameRoot") > 0)
{
gameRoot = m_commandLine.GetSwitchValue("gameRoot", 0);
}
if (gameRoot.empty())
{
if (IsInDebugMode())
{
if (!AZ::SettingsRegistry::Get()->Get(gameRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_SourceGameFolder))
{
AZ_Error("AssetBuilder", false, "Unable to determine the game root automatically. "
"Make sure a default project has been set or provide a default option on the command line. (See -help for more info.)");
return;
}
}
else
{
AZ_Printf(AssetBuilderSDK::InfoWindow, "gameRoot not specified on the command line, assuming current directory.\n");
AZ_Printf(AssetBuilderSDK::InfoWindow, "gameRoot is best specified as the full path to the game's asset folder.");
}
}
if (!gameRoot.empty())
{
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
if (fileIO)
{
fileIO->SetAlias("@devassets@", gameRoot.c_str());
}
}
// Loads dynamic modules and registers any component descriptors populated into the AZ::Module m_descriptor list
// for each instantiated module class
LoadDynamicModules();
AssetBuilderSDK::InitializeSerializationContext();
AssetBuilderSDK::InitializeBehaviorContext();
// the asset builder app never writes source files, only assets, so there is no need to do any kind of asset upgrading
AZ::Data::AssetManager::Instance().SetAssetInfoUpgradingEnabled(false);
// Disable parallel dependency loads since the builders can't count on all other assets and their info being ready.
// Specifically, asset builders can trigger asset loads during the building process. The ToolsAssetCatalog doesn't
// implement the dependency APIs, so the asset loads will fail to load any dependent assets.
//
// NOTE: The ToolsAssetCatalog could *potentially* implement the dependency APIs by querying the live Asset Processor instance,
// but this will return incomplete dependency information based on the subset of assets that have already processed.
// In theory, if the Asset Builder dependencies are set up correctly, the needed subset should always be processed first,
// but the one edge case that can't be handled is the case where the Asset Builder intends to filter out the dependent load,
// but needs to query enough information about the asset (specifically asset type) to know that it can filter it out. Since
// the assets are being filtered out, they aren't dependencies, might not be built yet, and so might not have asset type available.
AZ::Data::AssetManager::Instance().SetParallelDependentLoadingEnabled(false);
}
bool AssetBuilderApplication::IsInDebugMode() const
{
return AssetBuilderComponent::IsInDebugMode(m_commandLine);
}
bool AssetBuilderApplication::GetOptionalAppRootArg(char destinationRootArgBuffer[], size_t destinationRootArgBufferSize) const
{
// Only continue if the application received any arguments from the command line
if ((!this->m_argC) || (!this->m_argV))
{
return false;
}
int argc = this->m_argC;
char** argv = this->m_argV;
// Search for the app root argument (-approot=<PATH>) where <PATH> is the app root path to set for the application
const static char* appRootArgPrefix = "-approot=";
size_t appRootArgPrefixLen = strlen(appRootArgPrefix);
const char* appRootArg = nullptr;
for (int index = 0; index < argc; index++)
{
if (strncmp(appRootArgPrefix, argv[index], appRootArgPrefixLen) == 0)
{
appRootArg = &argv[index][appRootArgPrefixLen];
break;
}
}
if (appRootArg)
{
AZStd::string_view appRootArgView = appRootArg;
size_t afterStartQuotes = appRootArgView.find_first_not_of(R"(")");
if (afterStartQuotes != AZStd::string_view::npos)
{
appRootArgView.remove_prefix(afterStartQuotes);
}
size_t beforeEndQuotes = appRootArgView.find_last_not_of(R"(")");
if (beforeEndQuotes != AZStd::string_view::npos)
{
appRootArgView.remove_suffix(appRootArgView.size() - (beforeEndQuotes + 1));
}
appRootArgView.copy(destinationRootArgBuffer, destinationRootArgBufferSize);
destinationRootArgBuffer[appRootArgView.size()] = '\0';
const char lastChar = destinationRootArgBuffer[strlen(destinationRootArgBuffer) - 1];
bool needsTrailingPathDelim = (lastChar != AZ_CORRECT_FILESYSTEM_SEPARATOR) && (lastChar != AZ_WRONG_FILESYSTEM_SEPARATOR);
if (needsTrailingPathDelim)
{
azstrncat(destinationRootArgBuffer, destinationRootArgBufferSize, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING, 1);
}
return true;
}
else
{
return false;
}
}
void AssetBuilderApplication::InitializeBuilderComponents()
{
CreateAndAddEntityFromComponentTags(AZStd::vector<AZ::Crc32>({ AssetBuilderSDK::ComponentTags::AssetBuilder }), "AssetBuilders Entity");
}
@@ -0,0 +1,56 @@
/*
* 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 <AssetBuilderSDK/AssetBuilderBusses.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include "AssetBuilderInfo.h"
#include <QCoreApplication>
struct IBuilderApplication
{
AZ_RTTI(IBuilderApplication, "{FEDD188E-D5FF-4852-B945-F82F7CC1CA5F}");
IBuilderApplication() = default;
virtual ~IBuilderApplication() = default;
virtual void InitializeBuilderComponents() = 0;
AZ_DISABLE_COPY_MOVE(IBuilderApplication);
};
class AssetBuilderApplication
: public AzToolsFramework::ToolsApplication
, public IBuilderApplication
{
public:
AssetBuilderApplication(int* argc, char*** argv);
~AssetBuilderApplication();
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
void RegisterCoreComponents() override;
void StartCommon(AZ::Entity* systemEntity) override;
bool IsInDebugMode() const;
bool GetOptionalAppRootArg(char destinationRootArgBuffer[], size_t destinationRootArgBufferSize) const;
void InitializeBuilderComponents() override;
private:
void InstallCtrlHandler();
QCoreApplication m_qtApplication;
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,169 @@
/*
* 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 <AssetBuilderSDK/AssetBuilderBusses.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzCore/Component/Component.h>
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzFramework/Network/SocketConnection.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include "AssetBuilderInfo.h"
//! This bus is used to signal to the AssetBuilderComponent to start up and execute while providing a return code
class BuilderBusTraits
: 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 ~BuilderBusTraits() = default;
virtual bool Run() = 0;
};
typedef AZ::EBus<BuilderBusTraits> BuilderBus;
//! Main component of the AssetBuilder that handles interfacing with the AssetProcessor and the Builder module(s)
//! In resident mode, the component will keep the application up and running indefinitely while accepting job requests from the AP network connection
//! The other mods (create, process) will read a job from an `input` file and write the response to the `output` file and then terminate
class AssetBuilderComponent
: public AZ::Component,
public BuilderBus::Handler,
public AssetBuilderSDK::AssetBuilderBus::Handler,
public AzFramework::EngineConnectionEvents::Bus::Handler,
public AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler
{
public:
AZ_COMPONENT(AssetBuilderComponent, "{04332899-5d73-4d41-86b7-b1017d349673}")
static void Reflect(AZ::ReflectContext* context);
AssetBuilderComponent() = default;
~AssetBuilderComponent() override = default;
void PrintHelp();
// AZ::Component overrides
void Activate() override;
void Deactivate() override;
// BuilderBus Handler
bool Run() override;
// AssetBuilderBus Handler
bool FindBuilderInformation(const AZ::Uuid& builderGuid, AssetBuilderSDK::AssetBuilderDesc& descriptionOut) override;
void RegisterBuilderInformation(const AssetBuilderSDK::AssetBuilderDesc& builderDesc) override;
void RegisterComponentDescriptor(AZ::ComponentDescriptor* descriptor) override;
//EngineConnectionEvents Handler
void Disconnected(AzFramework::SocketConnection* connection) override;
static bool IsInDebugMode(const AzFramework::CommandLine& commandLine);
//AssetDatabaseRequestsBus Handler
bool GetAssetDatabaseLocation(AZStd::string& location) override;
protected:
AZ_DISABLE_COPY_MOVE(AssetBuilderComponent);
enum class JobType
{
Create,
Process
};
//! Describes a job request that came in from the network connection
struct Job
{
JobType m_jobType;
AZ::u32 m_requestSerial;
AZStd::unique_ptr<AzFramework::AssetSystem::BaseAssetProcessorMessage> m_netRequest;
AZStd::unique_ptr<AzFramework::AssetSystem::BaseAssetProcessorMessage> m_netResponse;
};
//! Reads a command line parameter and places it in the outValue parameter. Returns false if the value is empty, true otherwise
//! If required is true, an AZ_Error message is output
bool GetParameter(const char* paramName, AZStd::string& outValue, bool required = true) const;
//! Returns the platform specific extension for dynamic libraries
static const char* GetLibraryExtension();
bool ConnectToAssetProcessor();
bool LoadBuilders(const AZStd::string& builderFolder);
bool LoadBuilder(const AZStd::string& filePath);
void UnloadBuilders();
//! Hooks up net job request handling and keeps the AssetBuilder running indefinitely
bool RunInResidentMode();
bool RunDebugTask(AZStd::string&& debugFile, bool runCreateJobs, bool runProcessJob);
bool RunOneShotTask(const AZStd::string& task);
template<typename TNetRequest, typename TNetResponse>
void ResidentJobHandler(AZ::u32 serial, const void* data, AZ::u32 dataLength, JobType jobType);
void CreateJobsResidentHandler(AZ::u32 typeId, AZ::u32 serial, const void* data, AZ::u32 dataLength);
void ProcessJobResidentHandler(AZ::u32 typeId, AZ::u32 serial, const void* data, AZ::u32 dataLength);
bool IsBuilderForFile(const AZStd::string& filePath, const AssetBuilderSDK::AssetBuilderDesc& builderDescription) const;
//! Run by a separate thread to avoid blocking the net recv thread
//! Handles calling the appropriate builder job function for the incoming job
void JobThread();
void ProcessJob(const AssetBuilderSDK::ProcessJobFunction& job, const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& outResponse);
//! Handles a builder registration request
bool HandleRegisterBuilder(const AZStd::string& inputFilePath, const AZStd::string& outputFilePath) const;
//! If needed looks at collected data and updates the result code from the job accordingly.
void UpdateResultCode(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
//! Handles reading the request from file, passing it to the specified function and writing the response to file
template<typename TRequest, typename TResponse>
bool HandleTask(const AZStd::string& inputFilePath, const AZStd::string& outputFilePath, const AZStd::function<void(const TRequest& request, TResponse& response)>& handlerFunc);
//! Flush the File Streamer cache to ensure that there aren't stale file handles or data between asset job runs.
void FlushFileStreamerCache();
//! Map used to look up the asset builder to handle a request
AZStd::unordered_map<AZ::Uuid, AZStd::unique_ptr<AssetBuilderSDK::AssetBuilderDesc>> m_assetBuilderDescMap;
//! List of loaded builders
AZStd::vector<AZStd::unique_ptr<AssetBuilder::ExternalModuleAssetBuilderInfo>> m_assetBuilderInfoList;
//! Currently loading builder
AssetBuilder::ExternalModuleAssetBuilderInfo* m_currentAssetBuilder = nullptr;
//! Thread for running a job, so we don't block the network thread while doing work
AZStd::thread_desc m_jobThreadDesc;
AZStd::thread m_jobThread;
//! Indicates if resident mode is up and running
AZStd::atomic<bool> m_running{};
//! Main thread will wait on this event in resident mode. Releasing it will shut down the application
AZStd::binary_semaphore m_mainEvent;
//! Use to signal a new job is ready to be processed
AZStd::binary_semaphore m_jobEvent;
//! Lock for m_queuedJob
AZStd::mutex m_jobMutex;
//! Stored job that is waiting to be picked up for processing by the job thread
AZStd::unique_ptr<Job> m_queuedJob;
AZStd::string m_gameName;
AZStd::string m_gameCache;
};
@@ -0,0 +1,193 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AssetBuilderInfo.h>
#include <AssetBuilderApplication.h>
namespace AssetBuilder
{
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_modulePath(modulePath)
, m_library(modulePath)
{
Load();
}
ExternalModuleAssetBuilderInfo::~ExternalModuleAssetBuilderInfo()
{
Unload();
}
const QString& ExternalModuleAssetBuilderInfo::GetName() const
{
return m_builderName;
}
//! Sanity check for the module's status
bool ExternalModuleAssetBuilderInfo::IsLoaded() const
{
return m_library.isLoaded();
}
void ExternalModuleAssetBuilderInfo::Initialize()
{
AZ_Error("AssetBuilder", IsLoaded(), "External module %s not loaded.", m_builderName.toUtf8().data());
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("AssetBuilder", "Init Entity %s\n", GetName().toUtf8().data());
m_entity->Init();
//Activate all the components
m_entity->Activate();
}
void ExternalModuleAssetBuilderInfo::UnInitialize()
{
AZ_Error("AssetBuilder", IsLoaded(), "External module %s not loaded.", m_builderName.toUtf8().data());
AZ_TracePrintf("AssetBuilder", "Uninitializing builder: %s\n", m_modulePath.toUtf8().data());
if (m_entity)
{
m_entity->Deactivate();
delete m_entity;
m_entity = nullptr;
}
for (AZ::ComponentDescriptor* componentDesc : m_componentDescriptorList)
{
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::UnregisterComponentDescriptor, componentDesc);
componentDesc->ReleaseDescriptor(); // this kills the descriptor.
}
m_componentDescriptorList.clear();
m_registeredBuilderDescriptorIDs.clear();
m_uninitializeModuleFunction();
}
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.empty())
{
// a valid 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())
{
AZ_Warning("AssetBuilder", false, "External module %s already loaded.", m_builderName.toUtf8().data());
return AssetBuilderType::None;
}
m_library.setFileName(m_modulePath);
if (!m_library.load())
{
AZ_TracePrintf("AssetBuilder", "Unable to load builder : %s\n", GetName().toUtf8().data());
return AssetBuilderType::Invalid;
}
return GetAssetBuilderType();
}
void ExternalModuleAssetBuilderInfo::Unload()
{
if (IsLoaded())
{
m_library.unload();
}
m_initializeModuleFunction = nullptr;
m_moduleRegisterDescriptorsFunction = nullptr;
m_moduleAddComponentsFunction = nullptr;
m_uninitializeModuleFunction = nullptr;
}
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(),
m_builderName.toUtf8().data());
return;
}
m_registeredBuilderDescriptorIDs.insert(builderDescID);
}
void ExternalModuleAssetBuilderInfo::RegisterComponentDesc(AZ::ComponentDescriptor* descriptor)
{
m_componentDescriptorList.push_back(descriptor);
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::RegisterComponentDescriptor, 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,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
class QString;
class QStringList;
#include <QLibrary>
#include <QVector>
#include <AzCore/std/containers/set.h>
#include <AzCore/Math/Uuid.h>
namespace AZ
{
class ComponentDescriptor;
class Entity;
namespace Internal
{
class EnvironmentInterface;
}
typedef Internal::EnvironmentInterface* EnvironmentInstance;
}
namespace AssetBuilder
{
enum class AssetBuilderType
{
Invalid, Valid, None
};
/**
* Class to manage external module builders for AssetBuilder. Note that this is similar
* to a class in Asset Processor, because both AssetProcessor.exe and AssetBuilder.exe both load builders in a similar manner.
* The implementation details differ.
*/
class ExternalModuleAssetBuilderInfo
{
public:
ExternalModuleAssetBuilderInfo(const QString& modulePath);
virtual ~ExternalModuleAssetBuilderInfo();
const QString& GetName() const;
//! 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();
//! 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);
//! Check to see if the builder has the required functions defined.
AssetBuilder::AssetBuilderType GetAssetBuilderType();
protected:
AssetBuilderType Load();
void Unload();
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;
QString m_modulePath;
QLibrary m_library;
};
} // AssetBuilder
@@ -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.
#
ly_add_target(
NAME AssetBuilder EXECUTABLE
NAMESPACE AZ
FILES_CMAKE
asset_builder_files.cmake
Platform/${PAL_PLATFORM_NAME}/asset_builder_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
BUILD_DEPENDENCIES
PRIVATE
3rdParty::Qt::Core
3rdParty::Qt::Gui
3rdParty::Qt::Network
AZ::AzCore
AZ::AssetBuilderSDK
AZ::AzToolsFramework
)
# Aggregates all combined AssetBuilders into a single LY_ASSET_BUILDERS #define
get_property(asset_builders GLOBAL PROPERTY LY_ASSET_BUILDERS)
string (REPLACE ";" "," asset_builders "${asset_builders}")
ly_add_source_properties(
SOURCES AssetBuilderComponent.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES LY_ASSET_BUILDERS="${asset_builders}"
)
if(TARGET AssetBuilder)
# Adds the AssetBuilder target as a C preprocessor define so that it can be used as a Settings Registry
# specialization in order to look up the generated .setreg which contains the dependencies
# specified for the AssetBuilder in the <Project>/Gem/Code/CMakeLists via ly_add_project_dependencies
ly_add_source_properties(
SOURCES AssetBuilderApplication.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES LY_CMAKE_TARGET="AssetBuilder"
)
else()
message(FATAL_ERROR "Cannot set LY_CMAKE_TARGET define to AssetBuilder as the target doesn't exist anymore."
" Perhaps it has been renamed")
endif()
@@ -0,0 +1,17 @@
/*
* 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 <AssetBuilderApplication.h>
void AssetBuilderApplication::InstallCtrlHandler()
{
}
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
AssetBuilderApplication_linux.cpp
)
@@ -0,0 +1,17 @@
/*
* 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 <AssetBuilderApplication.h>
void AssetBuilderApplication::InstallCtrlHandler()
{
}
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
AssetBuilderApplication_mac.cpp
)
@@ -0,0 +1,35 @@
/*
* 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 <AssetBuilderApplication.h>
#include "shlobj.h"
namespace AssetBuilderApplicationPrivate
{
BOOL WINAPI CtrlHandlerRoutine(DWORD dwCtrlType)
{
(void)dwCtrlType;
// Terminate the process when CTRL+C is pressed
// Builder processes load user-code and we couldn't expect that every single gem
// written by every single external developer be able to shut down cleanly.
TerminateProcess(GetCurrentProcess(), UINT(-1)); // dont ever return a success error code from a terminated process.
return TRUE;
}
}
void AssetBuilderApplication::InstallCtrlHandler()
{
::SetConsoleCtrlHandler(AssetBuilderApplicationPrivate::CtrlHandlerRoutine, TRUE);
}
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
AssetBuilderApplication_windows.cpp
)
@@ -0,0 +1,180 @@
/*
* 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 <AssetBuilderApplication.h>
#include <TraceMessageHook.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/Component/EditorComponentAPIComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/Entity/EditorEntityModelComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySearchComponent.h>
#include <AzToolsFramework/Slice/SliceMetadataEntityContextComponent.h>
namespace AssetBuilder
{
using namespace UnitTest;
using AssetBuilderAppTest = AllocatorsFixture;
TEST_F(AssetBuilderAppTest, GetAppRootArg_AssetBuilderAppNoArgs_NoExtraction)
{
AssetBuilderApplication app(nullptr, nullptr);
char appRootBuffer[AZ_MAX_PATH_LEN];
ASSERT_FALSE(app.GetOptionalAppRootArg(appRootBuffer, AZ_MAX_PATH_LEN));
}
TEST_F(AssetBuilderAppTest, GetAppRootArg_AssetBuilderAppQuotedAppRoot_Success)
{
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
const char* appRootArg = R"str(-approot="C:\path\to\app\root\")str";
const char* expectedResult = R"str(C:\path\to\app\root\)str";
#else
const char* appRootArg = R"str(-approot="/path/to/app/root")str";
const char* expectedResult = R"str(/path/to/app/root/)str";
#endif
const char* argArray[] = {
appRootArg
};
int argc = AZ_ARRAY_SIZE(argArray);
char** argv = const_cast<char**>(argArray); // this is unfortunately necessary to get around osx's strict non-const string literal stance
AssetBuilderApplication app(&argc, &argv);
char appRootBuffer[AZ_MAX_PATH_LEN] = { 0 };
ASSERT_TRUE(app.GetOptionalAppRootArg(appRootBuffer, AZ_ARRAY_SIZE(appRootBuffer)));
ASSERT_STREQ(appRootBuffer, expectedResult);
}
TEST_F(AssetBuilderAppTest, GetAppRootArg_AssetBuilderAppNoQuotedAppRoot_Success)
{
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
const char* appRootArg = R"str(-approot=C:\path\to\app\root\)str";
const char* expectedResult = R"str(C:\path\to\app\root\)str";
#else
const char* appRootArg = R"str(-approot=/path/to/app/root)str";
const char* expectedResult = R"str(/path/to/app/root/)str";
#endif
const char* argArray[] = {
appRootArg
};
int argc = AZ_ARRAY_SIZE(argArray);
char** argv = const_cast<char**>(argArray); // this is unfortunately necessary to get around osx's strict non-const string literal stance
AssetBuilderApplication app(&argc, &argv);
char appRootBuffer[AZ_MAX_PATH_LEN] = { 0 };
ASSERT_TRUE(app.GetOptionalAppRootArg(appRootBuffer, AZ_ARRAY_SIZE(appRootBuffer)));
ASSERT_STREQ(appRootBuffer, expectedResult);
}
TEST_F(AssetBuilderAppTest, AssetBuilder_EditorScriptingComponents_Exists)
{
AssetBuilderApplication app(nullptr, nullptr);
AZ::ComponentTypeList systemComponents = app.GetRequiredSystemComponents();
auto searchFor = [&systemComponents](const AZ::Uuid& typeId) -> bool
{
auto entry = AZStd::find(systemComponents.begin(), systemComponents.end(), typeId);
return systemComponents.end() != entry;
};
EXPECT_TRUE(searchFor(azrtti_typeid<AzToolsFramework::SliceMetadataEntityContextComponent>()));
EXPECT_TRUE(searchFor(azrtti_typeid<AzToolsFramework::Components::EditorComponentAPIComponent>()));
EXPECT_TRUE(searchFor(azrtti_typeid<AzToolsFramework::Components::EditorEntitySearchComponent>()));
EXPECT_TRUE(searchFor(azrtti_typeid<AzToolsFramework::Components::EditorEntityModelComponent>()));
EXPECT_TRUE(searchFor(azrtti_typeid<AzToolsFramework::EditorEntityContextComponent>()));
}
void VerifyOutput(const AZStd::string& output)
{
ASSERT_FALSE(output.empty());
AZStd::vector<AZStd::string> tokens;
AZ::StringFunc::Tokenize(output, tokens, "\n", false, false);
// There should be an even number of lines since every line has a context line printed before it
ASSERT_GT(tokens.size(), 0);
ASSERT_EQ(tokens.size() % 2, 0);
for (int i = 0; i < tokens.size(); i += 2)
{
ASSERT_STREQ(tokens[0].c_str(), "C: [Source] = Test");
}
}
struct LoggingTest
: ScopedAllocatorSetupFixture
{
void SetUp() override
{
m_messageHook.EnableTraceContext(true);
}
TraceMessageHook m_messageHook;
};
TEST_F(LoggingTest, TracePrintf_ContainsContextOnEachLine)
{
testing::internal::CaptureStdout();
AZ_TraceContext("Source", "Test");
AZ_TracePrintf("window", "line1\nline2\nline3");
auto output = testing::internal::GetCapturedStdout();
VerifyOutput(output.c_str());
}
TEST_F(LoggingTest, Warning_ContainsContextOnEachLine)
{
testing::internal::CaptureStdout();
AZ_TraceContext("Source", "Test");
AZ_Warning("window", false, "line1\nline2\nline3");
auto output = testing::internal::GetCapturedStdout();
VerifyOutput(output.c_str());
}
TEST_F(LoggingTest, Error_ContainsContextOnEachLine)
{
testing::internal::CaptureStderr();
AZ_TraceContext("Source", "Test");
AZ_TEST_START_TRACE_SUPPRESSION;
AZ_Error("window", false, "line1\nline2\nline3");
AZ_TEST_STOP_TRACE_SUPPRESSION_NO_COUNT;
auto output = testing::internal::GetCapturedStderr();
VerifyOutput(output.c_str());
}
TEST_F(LoggingTest, Assert_ContainsContextOnEachLine)
{
testing::internal::CaptureStderr();
AZ_TraceContext("Source", "Test");
AZ_TEST_START_TRACE_SUPPRESSION;
AZ_Assert(false, "line1\nline2\nline3");
AZ_TEST_STOP_TRACE_SUPPRESSION_NO_COUNT;
auto output = testing::internal::GetCapturedStderr();
VerifyOutput(output.c_str());
}
} // namespace AssetBuilder
@@ -0,0 +1,268 @@
/*
* 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 <TraceMessageHook.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/Debug/TraceContextLogFormatter.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/PlatformIncl.h>
namespace AssetBuilder
{
constexpr int MaxMessageLength = 4096;
TraceMessageHook::TraceMessageHook()
: m_stacks(nullptr)
, m_inDebugMode(false)
, m_skipErrorsCount(0)
, m_skipWarningsCount(0)
, m_skipPrintfsCount(0)
, m_totalWarningCount(0)
, m_totalErrorCount(0)
{
AssetBuilderSDK::AssetBuilderTraceBus::Handler::BusConnect();
AZ::Debug::TraceMessageBus::Handler::BusConnect();
}
TraceMessageHook::~TraceMessageHook()
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
AssetBuilderSDK::AssetBuilderTraceBus::Handler::BusDisconnect();
delete m_stacks;
m_stacks = nullptr;
}
void TraceMessageHook::EnableTraceContext(bool enable)
{
if (enable)
{
if (!m_stacks)
{
m_stacks = new AzToolsFramework::Debug::TraceContextMultiStackHandler();
}
}
else
{
delete m_stacks;
m_stacks = nullptr;
}
}
void TraceMessageHook::EnableDebugMode(bool enable)
{
m_inDebugMode = enable;
}
bool TraceMessageHook::OnAssert(const char* message)
{
if (m_skipErrorsCount == 0)
{
CleanMessage(stderr, "E", message, true);
std::fflush(stderr);
++m_totalErrorCount;
}
else
{
--m_skipErrorsCount;
}
return !m_inDebugMode;
}
bool TraceMessageHook::OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message)
{
if(m_skipErrorsCount == 0)
{
char header[MaxMessageLength];
azsnprintf(header, MaxMessageLength, "%s: Trace::Error\n>\t%s(%d): '%s'\n", window, fileName, line, func);
CleanMessage(stderr, "E", header, false);
CleanMessage(stderr, "E", message, true, ">\t");
++m_totalErrorCount;
}
else
{
--m_skipErrorsCount;
}
return !m_inDebugMode;
}
bool TraceMessageHook::OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message)
{
if (m_skipWarningsCount == 0)
{
char header[MaxMessageLength];
azsnprintf(header, MaxMessageLength, "%s: Trace::Warning\n>\t%s(%d): '%s'\n", window, fileName, line, func);
CleanMessage(stdout, "W", header, false);
CleanMessage(stdout, "W", message, true, ">\t");
++m_totalWarningCount;
}
else
{
--m_skipWarningsCount;
}
return !m_inDebugMode;
}
bool TraceMessageHook::OnException(const char* message)
{
m_isInException = true;
CleanMessage(stderr, "E", message, true);
++m_totalErrorCount;
AZ::Debug::Trace::HandleExceptions(false);
AZ::Debug::Trace::PrintCallstack("", 3); // Skip all the Trace.cpp function calls
// note that the above call ultimately results in a whole bunch of TracePrint/Outputs, which will end up in OnOutput below.
std::fflush(stderr);
std::fflush(stdout);
// if we don't terminate here, the user may get a dialog box from the OS saying that the program crashed.
// we don't want this, because in this case, the program is one of potentially many, many background worker processes
// that are continuously starting/stopping and they'd get flooded by those message boxes.
AZ::Debug::Trace::Terminate(1);
return false;
}
bool TraceMessageHook::OnOutput(const char* /*window*/, const char* message)
{
if (m_isInException) // all messages that occur during an exception should be considered an error.
{
CleanMessage(stderr, "E", message, true);
return true;
}
return false;
}
bool TraceMessageHook::OnPrintf(const char* window, const char* message)
{
if (m_skipPrintfsCount == 0)
{
CleanMessage(stdout, window, message, false);
}
else
{
--m_skipPrintfsCount;
}
return true;
}
void TraceMessageHook::IgnoreNextErrors(AZ::u32 count)
{
m_skipErrorsCount += count;
}
void TraceMessageHook::IgnoreNextWarning(AZ::u32 count)
{
m_skipWarningsCount += count;
}
void TraceMessageHook::IgnoreNextPrintf(AZ::u32 count)
{
m_skipPrintfsCount += count;
}
void TraceMessageHook::ResetWarningCount()
{
m_totalWarningCount = 0;
}
void TraceMessageHook::ResetErrorCount()
{
m_totalErrorCount = 0;
}
AZ::u32 TraceMessageHook::GetWarningCount()
{
return m_totalWarningCount;
}
AZ::u32 TraceMessageHook::GetErrorCount()
{
return m_totalErrorCount;
}
void TraceMessageHook::DumpTraceContext(FILE* stream) const
{
if (m_stacks)
{
AZStd::shared_ptr<const AzToolsFramework::Debug::TraceContextStack> stack = m_stacks->GetCurrentStack();
if (stack)
{
AZStd::string line;
size_t stackSize = stack->GetStackCount();
for (size_t i = 0; i < stackSize; ++i)
{
line.clear();
AzToolsFramework::Debug::TraceContextLogFormatter::PrintLine(line, *stack, i);
CleanMessage(stream, "C", line.c_str(), false, nullptr, false);
}
}
}
}
void TraceMessageHook::CleanMessage(FILE* stream, const char* prefix, const char* message, bool forceFlush, const char* extraPrefix, bool includeTraceContext) const
{
if (message && message[0])
{
AZStd::vector<AZStd::string> lines;
AzFramework::StringFunc::Tokenize(message, lines, '\n', true, true); // Make sure to keep empty lines because it could be intentional blank lines someone has added for formatting reasons
// If the message ended with a newline, remove it, we're adding newlines to each line already
if(lines.back().empty())
{
lines.pop_back();
}
for (const AZStd::string& line : lines)
{
if(includeTraceContext)
{
DumpTraceContext(stream);
}
if (prefix && prefix[0])
{
fprintf(stream, "%s: ", prefix);
}
if(extraPrefix && extraPrefix[0])
{
fprintf(stream, "%s", extraPrefix);
}
fprintf(stream, "%s\n", line.c_str());
}
// Make sure the message ends with a newline
if (message[AZStd::char_traits<char>::length(message) - 1] != '\n')
{
fprintf(stream, "\n");
}
if (forceFlush)
{
fflush(stream);
}
}
}
} // namespace AssetBuilder
@@ -0,0 +1,65 @@
/*
* 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/Debug/TraceMessageBus.h>
#include <AzToolsFramework/Debug/TraceContextMultiStackHandler.h>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
namespace AssetBuilder
{
class TraceMessageHook
: public AZ::Debug::TraceMessageBus::Handler
, public AssetBuilderSDK::AssetBuilderTraceBus::Handler
{
public:
TraceMessageHook();
~TraceMessageHook() override;
void EnableTraceContext(bool enable);
void EnableDebugMode(bool enable);
bool OnAssert(const char* message) override;
bool OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message);
bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message);
bool OnException(const char* message) override;
bool OnPrintf(const char* window, const char* message) override;
bool OnOutput(const char* window, const char* message) override;
void IgnoreNextErrors(AZ::u32 count) override;
void IgnoreNextWarning(AZ::u32 count) override;
void IgnoreNextPrintf(AZ::u32 count) override;
void ResetWarningCount() override;
void ResetErrorCount() override;
AZ::u32 GetWarningCount() override;
AZ::u32 GetErrorCount() override;
void DumpTraceContext(FILE* stream) const;
void CleanMessage(FILE* stream, const char* prefix, const char* message, bool forceFlush, const char* extraPrefix = nullptr, bool includeTraceContext = true) const;
protected:
AzToolsFramework::Debug::TraceContextMultiStackHandler* m_stacks;
AZ::u32 m_skipErrorsCount;
AZ::u32 m_skipWarningsCount;
AZ::u32 m_skipPrintfsCount;
AZ::u32 m_totalWarningCount;
AZ::u32 m_totalErrorCount;
bool m_inDebugMode;
// once we're in an exception, we accept all log data as error, since we will terminate
// this ensures that call stack info (which is 'traced', not 'exceptioned') is present.
bool m_isInException = false;
};
} // namespace AssetBuilder
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
Platform/AssetBuilderApplication_darwin.cpp
)
@@ -0,0 +1,22 @@
#
# 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.
#
set(FILES
AssetBuilderApplication.h
AssetBuilderApplication.cpp
AssetBuilderComponent.h
AssetBuilderComponent.cpp
main.cpp
AssetBuilderInfo.h
AssetBuilderInfo.cpp
TraceMessageHook.h
TraceMessageHook.cpp
)
@@ -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.
*
*/
#include "AssetBuilderApplication.h"
#include "TraceMessageHook.h"
#include "AssetBuilderComponent.h"
int main(int argc, char** argv)
{
AssetBuilderApplication app(&argc, &argv);
AssetBuilder::TraceMessageHook traceMessageHook; // Hook AZ Debug messages and redirect them to stdout
traceMessageHook.EnableTraceContext(true);
AZ::Debug::Trace::HandleExceptions(true);
// Perform an additional check for an override app root argument, and set it in the startup params if appropriate
char destinationRootArgBuffer[AZ_MAX_PATH_LEN];
AZ::ComponentApplication::StartupParameters startupParams;
if (app.GetOptionalAppRootArg(destinationRootArgBuffer, AZ_MAX_PATH_LEN))
{
startupParams.m_appRootOverride = destinationRootArgBuffer;
}
startupParams.m_loadDynamicModules = false;
app.Start(AzFramework::Application::Descriptor(), startupParams);
traceMessageHook.EnableDebugMode(app.IsInDebugMode());
bool result = false;
BuilderBus::BroadcastResult(result, &BuilderBus::Events::Run);
traceMessageHook.EnableTraceContext(false);
app.Stop();
return result ? 0 : 1;
}
@@ -0,0 +1,120 @@
/*
* 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/Component/Component.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Math/Uuid.h>
namespace AssetBuilderSDK
{
struct CreateJobsRequest;
struct CreateJobsResponse;
struct ProcessJobRequest;
struct ProcessJobResponse;
struct AssetBuilderDesc;
//! This EBUS is used to send commands from the assetprocessor to the builder
//! Every new builder should implement a listener for this bus and implement the CreateJobs, Shutdown and ProcessJobs functions.
class AssetBuilderCommandBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
typedef AZ::Uuid BusIdType;
virtual ~AssetBuilderCommandBusTraits() {};
//! Shutdown() REQUIRED - Handle the message indicating shutdown. Cancel all your tasks and get them stopped ASAP
//! this message will come in from a different thread than your ProcessJob() thread.
//! failure to terminate promptly can cause a hangup on AP shutdown and restart.
virtual void ShutDown() = 0;
};
typedef AZ::EBus<AssetBuilderCommandBusTraits> AssetBuilderCommandBus;
//!This EBUS is used to send information from the builder to the AssetProcessor
class AssetBuilderBusTraits
: 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 ~AssetBuilderBusTraits() {}
virtual bool FindBuilderInformation(const AZ::Uuid& /*builderGuid*/, AssetBuilderDesc& /*descriptionOut*/) { return false; }
// Use this function to send AssetBuilderDesc info to the assetprocessor
virtual void RegisterBuilderInformation(const AssetBuilderDesc& /*builderDesc*/) {}
// Use this function to register all the component descriptors
virtual void RegisterComponentDescriptor(AZ::ComponentDescriptor* /*descriptor*/) {}
// Log functions to report general builder related messages/error.
virtual void BuilderLog(const AZ::Uuid& /*builderId*/, const char* /*message*/, ...) {}
virtual void BuilderLogV(const AZ::Uuid& /*builderId*/, const char* /*message*/, va_list /*list*/) {}
};
typedef AZ::EBus<AssetBuilderBusTraits> AssetBuilderBus;
//! This EBus provides builders access to the Asset Builders issue tracking facilities.
class AssetBuilderTraceTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
virtual ~AssetBuilderTraceTraits() = default;
//! The next <count> requests that the Asset Builder gets to forward errors to the console
//! will be ignored.
virtual void IgnoreNextErrors(AZ::u32 count) = 0;
//! The next <count> requests that the Asset Builder gets to forward warnings to the console
//! will be ignored.
virtual void IgnoreNextWarning(AZ::u32 count) = 0;
//! The next <count> requests that the Asset Builder gets to forward prints to the console
//! will be ignored.
virtual void IgnoreNextPrintf(AZ::u32 count) = 0;
virtual void ResetWarningCount() = 0;
virtual void ResetErrorCount() = 0;
virtual AZ::u32 GetWarningCount() = 0;
virtual AZ::u32 GetErrorCount() = 0;
};
typedef AZ::EBus<AssetBuilderTraceTraits> AssetBuilderTraceBus;
//! This EBUS is used to send commands from the assetprocessor to a specific job
class JobCommandTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
typedef AZStd::recursive_mutex MutexType;
typedef AZ::s64 BusIdType;
virtual ~JobCommandTraits() {}
//! Handle the message indicating that the specific job needs to cancel.
virtual void Cancel() {}
};
typedef AZ::EBus<JobCommandTraits> JobCommandBus;
}
@@ -0,0 +1,71 @@
/*
* 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 ASSETBUILDERUTILEBUSHELPER_H
#define ASSETBUILDERUTILEBUSHELPER_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Math/Uuid.h>
namespace AssetBuilderSDK
{
//!This EBUS is used to send commands from the assetprocessor to the builder
class AssetBuilderCommandBusTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
typedef AZ::Uuid BusIdType;
typedef AZStd::recursive_mutex MutexType;
virtual ~AssetBuilderCommandBusTraits() {}
//Shutdown the builder.
virtual void ShutDown() {}
};
typedef AZ::EBus<AssetBuilderCommandBusTraits> AssetBuilderCommandBus;
//!Information that builders will send to the assetprocessor
struct AssetBuilderDesc
{
AZStd::string m_name;//builder name
AZStd::string m_regex;//builder regex
AZ::Uuid m_busId;// builder id
};
//!This EBUS is used to send information from the builder to the AssetProcessor
class AssetBuilderBusTraits
: 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 ~AssetBuilderBusTraits() {}
//Use this function to send AssetBuilderDesc info to the assetprocessor
virtual void RegisterBuilderInformation(AssetBuilderDesc builderDesc) {}
//Use this function to register all the component descriptors
virtual void RegisterComponentDescriptor(AZ::ComponentDescriptor* descriptor) {}
};
typedef AZ::EBus<AssetBuilderBusTraits> AssetBuilderBus;
}
#endif //ASSETBUILDERUTILEBUSHELPER_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,964 @@
/*
* 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/Debug/TraceMessageBus.h>
#include <AzCore/std/string/regex.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/bitset.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzCore/std/string/string_view.h>
#include "AssetBuilderBusses.h"
/**
* this define exists to turn on and off the support for legacy m_platformFlags and the concept of platforms as an enum
* If you want to upgrade your system to use the new platform tag system, you can turn this define off in order to strip out
* any references to the old stuff and cause compile-time errors anywhere your code tries to use the legacy API.
* It is recommended that you leave this on so that code besides your own code (for example, in 3rd-party gems) continues to function
* until the responsible party upgrades that code also.
*/
#define ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT
namespace AZ
{
class ComponentDescriptor;
class Entity;
}
// This needs to be up here because it needs to be defined before the hash definition, and the hash needs to be defined before the first use (which occurs further down in this file)
namespace AssetBuilderSDK
{
enum class ProductPathDependencyType : AZ::u32
{
SourceFile,
ProductFile
};
/**
* Product dependency information that the builder will send to the assetprocessor
* Indicates a product asset that depends on another product based on the path
* Should only be used by legacy systems. Prefer ProductDependencies whenever possible
*/
struct ProductPathDependency
{
AZ_CLASS_ALLOCATOR(ProductPathDependency, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(ProductPathDependency, "{2632bfae-7490-476f-9214-a6d1f02e6085}");
//! Relative path to the asset dependency
AZStd::string m_dependencyPath;
/**
* Indicates if the dependency path points to a source file or a product file
* A dependency on a source file will be converted into dependencies on all product files produced from the source
* It is preferable to depend on product files whenever possible to avoid introducing unintended dependencies
*/
ProductPathDependencyType m_dependencyType = ProductPathDependencyType::ProductFile;
ProductPathDependency() = default;
ProductPathDependency(AZStd::string_view dependencyPath, ProductPathDependencyType dependencyType);
bool operator==(const ProductPathDependency& rhs) const;
static void Reflect(AZ::ReflectContext* context);
};
}
namespace AZStd
{
template<>
struct hash<AssetBuilderSDK::ProductPathDependency>
{
using argument_type = AssetBuilderSDK::ProductPathDependency;
using result_type = size_t;
result_type operator() (const argument_type& dependency) const
{
size_t h = 0;
hash_combine(h, dependency.m_dependencyPath);
hash_combine(h, dependency.m_dependencyType);
return h;
}
};
} // namespace AZStd
namespace AssetBuilderSDK
{
namespace ComponentTags
{
//! Components with the AssetBuilder tag in their reflect data's attributes as AZ::Edit::Attributes::SystemComponetTags will automatically be created on AssetBuilder startup
const static AZ::Crc32 AssetBuilder = AZ_CRC("AssetBuilder", 0xc739c7d7);
}
extern const char* const ErrorWindow; //Use this window name to log error messages.
extern const char* const WarningWindow; //Use this window name to log warning messages.
extern const char* const InfoWindow; //Use this window name to log info messages.
extern const char* const s_processJobRequestFileName; //!< File name for having job requests send from the Asset Processor.
extern const char* const s_processJobResponseFileName; //!< File name for having job responses returned to the Asset Processor.
// SubIDs uniquely identify a particular output product of a specific source asset
// currently we use a scheme where various bits of the subId (which is a 32 bit unsigned) are used to designate different things.
// we may expand this into a 64-bit "namespace" by adding additional 32 bits at the front at some point, if it becomes necessary.
extern const AZ::u32 SUBID_MASK_ID; //!< mask is 0xFFFF - so you can have up to 64k subids from a single asset before you start running into the upper bits which are used for other reasons.
extern const AZ::u32 SUBID_MASK_LOD_LEVEL; //!< the LOD level can be masked up to 15 LOD levels (it also represents the MIP level). note that it starts at 1.
extern const AZ::u32 SUBID_LOD_LEVEL_SHIFT; //!< the shift to move the LOD level in its expected bits.
extern const AZ::u32 SUBID_FLAG_DIFF; //!< this is a 'diff' map. It may have the alpha, and lod set too if its an alpha of a diff
extern const AZ::u32 SUBID_FLAG_ALPHA; //!< this is an alpha mip or alpha channel.
//! extract only the ID using the above masks
AZ::u32 GetSubID_ID(AZ::u32 packedSubId);
//! extract only the LOD using the above masks. note that it starts at 1, not 0. 0 would be the base asset.
AZ::u32 GetSubID_LOD(AZ::u32 packedSubId);
//! create a subid using the above masks. Note that if you want to add additional bits such as DIFF or ALPHA, you must add them afterwards.
//! fromsubindex contains an existing subindex to replace the LODs and SUBs but no other bits with.
AZ::u32 ConstructSubID(AZ::u32 subIndex, AZ::u32 lodLevel, AZ::u32 fromSubIndex = 0);
//! Initializes the serialization context with all the reflection information for AssetBuilderSDK structures
//! Should be called on startup by standalone builders. Builders run by AssetBuilder will have this set up already
void InitializeSerializationContext();
void InitializeBehaviorContext();
//! This method is used for logging builder related messages/error
//! Do not use this inside ProcessJob, use AZ_TracePrintF instead. This is only for general messages about your builder, not for job-specific messages
extern void BuilderLog(AZ::Uuid builderId, const char* message, ...);
#if defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
/**
* DEPRECATED - LEGACY - this is retained for code compatbility with previous versions. Please just use the m_enabledPlatforms
* structure in all new code.
**/
enum Platform : AZ::u32
{
Platform_NONE = 0x00,
Platform_PC = 0x01,
Platform_ES3 = 0x02,
Platform_IOS = 0x04,
Platform_OSX = 0x08,
Platform_XENIA = 0x10,
Platform_PROVO = 0x20,
Platform_SALEM = 0x40,
Platform_JASPER = 0x80,
//! if you add a new platform entry to this enum, you must add it to allplatforms as well otherwise that platform would not be considered valid.
AllPlatforms = Platform_PC | Platform_ES3 | Platform_IOS | Platform_OSX | Platform_XENIA | Platform_PROVO | Platform_SALEM | Platform_JASPER
};
#endif // defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
//! Map data structure to holder parameters that are passed into a job for ProcessJob requests.
//! These parameters can optionally be set during the create job function of the builder so that they are passed along
//! to the ProcessJobFunction. The values (key and value) are arbitrary and is up to the builder on how to use them
typedef AZStd::unordered_map<AZ::u32, AZStd::string> JobParameterMap;
//! Callback function type for creating jobs from job requests
typedef AZStd::function<void(const CreateJobsRequest& request, CreateJobsResponse& response)> CreateJobFunction;
//! Callback function type for processing jobs from process job requests
typedef AZStd::function<void(const ProcessJobRequest& request, ProcessJobResponse& response)> ProcessJobFunction;
//! Structure defining the type of pattern to use to apply
struct AssetBuilderPattern
{
AZ_CLASS_ALLOCATOR(AssetBuilderPattern, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(AssetBuilderPattern, "{A8818121-D106-495E-9776-11F59E897BAD}");
enum PatternType
{
//! The pattern is a file wildcard pattern (glob)
Wildcard,
//! The pattern is a regular expression pattern
Regex
};
AZStd::string m_pattern;
PatternType m_type;
AssetBuilderPattern() = default;
AssetBuilderPattern(const AssetBuilderPattern& src) = default;
AssetBuilderPattern(const AZStd::string& pattern, PatternType type);
AZStd::string ToString() const;
static void Reflect(AZ::ReflectContext* context);
};
//! This class represents a matching pattern that is based on AssetBuilderSDK::AssetBuilderPattern::PatternType, which can either be a regex
//! pattern or a wildcard (glob) pattern
class FilePatternMatcher
{
public:
FilePatternMatcher() = default;
explicit FilePatternMatcher(const AssetBuilderSDK::AssetBuilderPattern& pattern);
FilePatternMatcher(const AZStd::string& pattern, AssetBuilderSDK::AssetBuilderPattern::PatternType type);
FilePatternMatcher(const FilePatternMatcher& copy);
typedef AZStd::regex RegexType;
FilePatternMatcher& operator=(const FilePatternMatcher& copy);
bool MatchesPath(const AZStd::string& assetPath) const;
bool IsValid() const;
AZStd::string GetErrorString() const;
const AssetBuilderSDK::AssetBuilderPattern& GetBuilderPattern() const;
protected:
static bool ValidatePatternRegex(const AZStd::string& pattern);
AssetBuilderSDK::AssetBuilderPattern m_pattern;
RegexType m_regex;
AZStd::string m_errorString;
bool m_isRegex;
bool m_isValid;
};
//!Information that builders will send to the assetprocessor
struct AssetBuilderDesc
{
AZ_CLASS_ALLOCATOR(AssetBuilderDesc, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(AssetBuilderDesc, "{7778EB3D-7B3B-4231-80C0-94C4226309AF}");
enum class AssetBuilderType
{
Internal, //! Internal Recognizer builders for example. Internal Builders are created and run inside the AP.
External //! External builders are those located within gems that run inside an AssetBuilder application.
};
// you don't have to set any flags but they are used for optimization.
enum BuilderFlags : AZ::u8
{
BF_None = 0,
BF_EmitsNoDependencies = 1<<0, // if you set this flag, dependency-related parts in the code will be skipped
BF_DeleteLastKnownGoodProductOnFailure = 1<<1, // if processing fails, delete previous successful product if it exists
};
//! The name of the Builder
AZStd::string m_name;
//! The collection of asset builder patterns that the builder will use to
//! determine if a file will be processed by that builder
AZStd::vector<AssetBuilderPattern> m_patterns;
//! The builder unique ID
AZ::Uuid m_busId;
//! Changing this version number will cause all your assets to be re-submitted to the builder for job creation and rebuilding.
int m_version = 0;
//! The required create job function callback that the asset processor will call during the job creation phase
CreateJobFunction m_createJobFunction;
//! The required process job function callback that the asset processor will call during the job processing phase
ProcessJobFunction m_processJobFunction;
//! The builder type. We set this to External by default, as that is the typical set up for custom builders (builders in gems and legacy dll builders).
AssetBuilderType m_builderType = AssetBuilderType::External;
/** Analysis Fingerprint
* you can optionally emit an analysis fingerprint, or leave this empty.
* The Analysis Fingerprint, used to quickly skip analysis if the source files modtime has not changed.
* If your analysis fingerprint DOES change, then all source files will be sent to your CreateJobs function regardless of modtime changes.
* This does not necessarily mean that the jobs will need doing, just that CreateJobs will be called.
* For best results, make sure your analysis fingerprint only changes when its likely that you need to re-analyze source files for changes, which
* may result in job fingerprints to be diffent (for example, if you have changed your logic inside your builder).
**/
AZStd::string m_analysisFingerprint;
//! You don't have to set any flags, but if you do, it can improve speed.
//! If you change your flags, bump the version number of your builder, too.
AZ::u8 m_flags = 0;
AZStd::unordered_map<AZStd::string, AZ::u8> m_flagsByJobKey;
void AddFlags(AZ::u8 flag, const AZStd::string& jobKey);
bool HasFlag(AZ::u8 flag, const AZStd::string& jobKey) const;
bool IsExternalBuilder() const;
// Note that we don't serialize the function pointer fields as part of the registration since they should not be
// sent over the wire.
static void Reflect(AZ::ReflectContext* context);
};
//! Source file dependency information that the builder will send to the assetprocessor
//! It is important to note that the builder do not need to provide both the sourceFileDependencyUUID or sourceFileDependencyPath info to the asset processor,
//! any one of them should be sufficient
struct SourceFileDependency
{
AZ_CLASS_ALLOCATOR(SourceFileDependency, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(SourceFileDependency, "{d3c055d8-b5e8-44ab-a6ce-1ecb0da091ec}");
// Corresponds to SourceFileDependencyEntry TypeOfDependency Values
enum class SourceFileDependencyType : AZ::u32
{
Absolute, // Corresponds to DEP_SourceToSource
Wildcards // DEP_SourceLikeMatch
};
/** Filepath on which the source file depends, it can be either be a relative path from the assets folder, or an absolute path.
* if it's relative, the asset processor will check every watched folder in the order specified in the assetprocessor config file until it finds that file.
* For example if the builder sends a SourceFileDependency with m_sourceFileDependencyPath = "texture/blah.tif" to the asset processor,
* it will check all watch folders for a file whose relative path with regard to it is "texture/blah.tif".
* and supposing it finds it in "C:/dev/gamename/texture/blah.tif", it will use that as the dependency.
* You can also send absolute path, which will obey the usual overriding rules.
* @note You must EITHER provide the m_sourceFileDependencyPath OR the m_sourceFileDependencyUUID.
**/
AZStd::string m_sourceFileDependencyPath;
/** UUID of the file on which the source file depends.
* @note You must EITHER provide the m_sourceFileDependencyPath OR the m_sourceFileDependencyUUID if you have that instead.
*/
AZ::Uuid m_sourceFileDependencyUUID = AZ::Uuid::CreateNull();
SourceFileDependencyType m_sourceDependencyType{ SourceFileDependencyType::Absolute };
SourceFileDependency() = default;
SourceFileDependency(const AZStd::string& sourceFileDependencyPath, AZ::Uuid sourceFileDependencyUUID, SourceFileDependencyType sourceDependencyType = SourceFileDependencyType::Absolute)
: m_sourceFileDependencyPath(sourceFileDependencyPath)
, m_sourceFileDependencyUUID(sourceFileDependencyUUID)
, m_sourceDependencyType(sourceDependencyType)
{
}
SourceFileDependency(AZStd::string&& sourceFileDependencyPath, AZ::Uuid sourceFileDependencyUUID, SourceFileDependencyType sourceDependencyType = SourceFileDependencyType::Absolute)
: m_sourceFileDependencyPath(AZStd::move(sourceFileDependencyPath))
, m_sourceFileDependencyUUID(sourceFileDependencyUUID)
, m_sourceDependencyType(sourceDependencyType)
{
}
AZStd::string ToString() const;
static void Reflect(AZ::ReflectContext* context);
};
enum class JobDependencyType : AZ::u32
{
//! This implies that the dependent job should get processed by the assetprocessor, if the fingerprint of job it depends on changes.
Fingerprint,
//! This implies that the dependent job should only run after the job it depends on is processed by the assetprocessor.
Order,
//! This is similiar to Order where the dependent job should only run after all the jobs it depends on are processed by the assetprocessor.
//! The difference is that here only those dependent jobs matter that have never been processed by the asset processor.
//! Also important to note is the fingerprint of the dependent jobs will not alter the the fingerprint of the job.
OrderOnce,
};
//! Job dependency information that the builder will send to the assetprocessor.
struct JobDependency
{
AZ_CLASS_ALLOCATOR(JobDependency, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(JobDependency, "{93A9D915-8C9E-4588-8D86-578C01EEA388}");
//! Source file dependency information that the builder will send to the assetprocessor
//! It is important to note that the builder do not need to provide both the sourceFileDependencyUUID or sourceFileDependencyPath info to the asset processor,
//! any one of them should be sufficient
SourceFileDependency m_sourceFile;
//! JobKey of the dependent job
AZStd::string m_jobKey;
//! Platform Identifier of the dependent job
AZStd::string m_platformIdentifier;
//! Type of Job Dependency (order or fingerprint)
JobDependencyType m_type;
JobDependency() = default;
JobDependency(const AZStd::string& jobKey, const AZStd::string& platformIdentifier, const JobDependencyType& type, const SourceFileDependency& sourceFile);
static void Reflect(AZ::ReflectContext* context);
};
//! JobDescriptor is used by the builder to store job related information
struct JobDescriptor
{
AZ_CLASS_ALLOCATOR(JobDescriptor, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(JobDescriptor, "{bd0472a4-7634-41f3-97ef-00f3b239bae2}");
//! Any builder specific parameters to pass to the Process Job Request
JobParameterMap m_jobParameters;
//! Any additional info that should be taken into account during fingerprinting for this job
AZStd::string m_additionalFingerprintInfo;
//! Job specific key, e.g. TIFF Job, etc
AZStd::string m_jobKey;
#if defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
/**
* DEPRECATED - this remains only for backward compatiblity with older modules
* consider using m_platformIdentifier (via getter/setter) instead. This will still work but as new platforms are added
* using the data-driven approach, your enum will no longer be sufficient.
*/
int m_platform = Platform_NONE;
#endif // defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
//! Priority value for the jobs within the job queue. If less than zero, than the priority of this job is not considered or or is lowest priority.
//! If zero or greater, the value is prioritized by this number (the higher the number, the higher priority). Note: priorities are set within critical
//! and non-critical job separately.
int m_priority = -1;
//! Flag to determine if this is a critical job or not. Critical jobs are given the higher priority in the processing queue than non-critical jobs
bool m_critical = false;
//! Flag to determine whether we need to check the input file for exclusive lock before we process the job
bool m_checkExclusiveLock = false;
//! Flag to determine whether we need to check the server for the outputs of this job
//! before we start processing the job locally.
//! If the asset processor is running in server mode then this will be used to determine whether we need
//! to store the outputs of this jobs in the server.
bool m_checkServer = false;
//! This is required for jobs that want to declare job dependency on other jobs.
AZStd::vector<JobDependency> m_jobDependencyList;
//! If set to true, reported errors, asserts and exceptions will automatically cause the job to fail even is ProcessJobResult_Success is the result code.
bool m_failOnError = false;
/**
* construct using a platformIdentifier from your CreateJobsRequest. it is the m_identifier member of the PlatformInfo.
*/
JobDescriptor(const AZStd::string& additionalFingerprintInfo, AZStd::string jobKey, const char* platformIdentifier);
#if defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
/**
* DEPRECATED - please use the above constructor
* This is retained for backward compatiblity only
* Construct a JobDescriptor using the platform index from the Platform enum.
*/
JobDescriptor(AZStd::string additionalFingerprintInfo, int platform, const AZStd::string& jobKey);
#endif // defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
JobDescriptor() = default;
static void Reflect(AZ::ReflectContext* context);
/** Use this to set the platform identifier. it knows when it needs to retroactively compute
* the old m_platform flag when that code is enabled.
*/
void SetPlatformIdentifier(const char* platformIdentifier);
const AZStd::string& GetPlatformIdentifier() const;
protected:
/**
* This describes which platform its for. It should match one of the enabled platforms passed into CreateJobs.
* It is the identifier of the platform from that PlatformInfo struct.
*/
AZStd::string m_platformIdentifier;
};
//! RegisterBuilderRequest contains input data that will be sent by the AssetProcessor to the builder during the startup registration phase
struct RegisterBuilderRequest
{
AZ_CLASS_ALLOCATOR(RegisterBuilderRequest, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(RegisterBuilderRequest, "{7C6C5198-4766-42B8-9A1E-48479CE2F5EA}");
AZStd::string m_filePath;
RegisterBuilderRequest() {}
explicit RegisterBuilderRequest(const AZStd::string& filePath)
: m_filePath(filePath)
{
}
static void Reflect(AZ::ReflectContext* context);
};
//! INTERNAL USE ONLY - RegisterBuilderResponse contains registration data that will be sent by the builder to the AssetProcessor in response to RegisterBuilderRequest
struct RegisterBuilderResponse
{
AZ_CLASS_ALLOCATOR(RegisterBuilderResponse, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(RegisterBuilderResponse, "{0AE5583F-C763-410E-BA7F-78BD90546C01}");
AZStd::vector<AssetBuilderDesc> m_assetBuilderDescList;
static void Reflect(AZ::ReflectContext* context);
};
/**
* This tells you about a platform in your CreateJobsRequest or your ProcessJobRequest
*/
struct PlatformInfo
{
AZ_CLASS_ALLOCATOR(PlatformInfo, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(PlatformInfo, "{F7DA39A5-C319-4552-954B-3479E2454D3F}");
AZStd::string m_identifier; ///< like "pc" or "es3" or "ios"...
AZStd::unordered_set<AZStd::string> m_tags; ///< The tags like "console" or "tools" on that platform
PlatformInfo() = default;
PlatformInfo(const char* identifier, const AZStd::unordered_set<AZStd::string>& tags);
bool operator==(const PlatformInfo& other);
///! utility function. It just searches the set for you:
bool HasTag(const char* tag) const;
static void Reflect(AZ::ReflectContext* context);
static AZStd::string PlatformVectorAsString(const AZStd::vector<PlatformInfo>& platforms);
};
//! CreateJobsRequest contains input job data that will be send by the AssetProcessor to the builder for creating jobs
struct CreateJobsRequest
{
AZ_CLASS_ALLOCATOR(CreateJobsRequest, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(CreateJobsRequest, "{02d470fb-4cb6-4cd7-876f-f0652910ff75}");
//! The builder id to identify which builder will process this job request
AZ::Uuid m_builderid; // builder id
//! m_watchFolder contains the subfolder that the sourceFile came from, out of all the folders being watched by the Asset Processor.
//! If you combine the Watch Folder with the Source File (m_sourceFile), you will result in the full absolute path to the file.
AZStd::string m_watchFolder;
//! The source file path that is relative to the watch folder (m_watchFolder)
AZStd::string m_sourceFile;
AZ::Uuid m_sourceFileUUID; ///< each source file has a unique UUID.
//! Information about each platform you are expected to build is stored here.
//! You can emit any number of jobs to produce some or all of the assets for each of these platforms.
AZStd::vector<PlatformInfo> m_enabledPlatforms;
CreateJobsRequest();
CreateJobsRequest(AZ::Uuid builderid, AZStd::string sourceFile, AZStd::string watchFolder, const AZStd::vector<PlatformInfo>& enabledPlatforms, const AZ::Uuid& sourceFileUuid);
/**
* New Data-driven platform API - will return true if the m_enabledPlatforms contains
* a platform with that identifier
*/
bool HasPlatform(const char* platformIdentifier) const;
/**
* New Data-driven platform API - will return true if the m_enabledPlatforms contains
* a platform which itself contains that tag. Note that multiple platforms may match this tag.
*/
bool HasPlatformWithTag(const char* platformTag) const;
#if defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
/**
* Legacy - DEPRECATED - use m_enabledPlatforms instead.
* returns the number of platforms that are enabled for the source file
*/
size_t GetEnabledPlatformsCount() const;
/***
* Legacy - DEPRECATED - use m_enabledPlatforms instead.
* returns the enabled platform by index, if no platform is found then we will return Platform_NONE.
*/
AssetBuilderSDK::Platform GetEnabledPlatformAt(size_t index) const;
/***
* Legacy - DEPRECATED - use m_enabledPlatforms instead.
* determine whether the platform is enabled or not, returns true if enabled otherwise false
*/
bool IsPlatformEnabled(AZ::u32 platform) const;
/***
* Legacy - DEPRECATED - use m_enabledPlatforms instead.
* determine whether the inputted platform is valid or not, returns true if valid otherwise false
*/
bool IsPlatformValid(AZ::u32 platform) const;
/**
* Legacy - deprecated! Only here for backward compatibility. Will not support new platforms - please use the m_enabledPlatform APIs going forward
* Platform flags informs the builder which platforms the AssetProcessor is interested in. Its the platforms enum as bitmasks
*/
int m_platformFlags = 0;
#endif // defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
static void Reflect(AZ::ReflectContext* context);
};
//! Possible result codes from CreateJobs requests
enum class CreateJobsResultCode
{
//! Jobs were created successfully
Success,
//! Jobs failed to be created
Failed,
//! The builder is in the process of shutting down
ShuttingDown
};
//! CreateJobsResponse contains job data that will be send by the builder to the assetProcessor in response to CreateJobsRequest
struct CreateJobsResponse
{
AZ_CLASS_ALLOCATOR(CreateJobsResponse, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(CreateJobsResponse, "{32a27d68-25bc-4425-a12b-bab961d6afcd}");
CreateJobsResultCode m_result = CreateJobsResultCode::Failed; // The result code from the create jobs request
AZStd::vector<SourceFileDependency> m_sourceFileDependencyList; // This is required for source files that want to declare dependencies on other source files.
AZStd::vector<JobDescriptor> m_createJobOutputs;
bool Succeeded() const;
static void Reflect(AZ::ReflectContext* context);
};
//! Product dependency information that the builder will send to the assetprocessor
//! Indicates a product asset that depends on another product asset
struct ProductDependency
{
AZ_CLASS_ALLOCATOR(ProductDependency, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(ProductDependency, "{54338921-b437-4f39-a0da-b1d0d1ee7b57}");
//! ID of the asset dependency
AZ::Data::AssetId m_dependencyId;
AZ::Data::ProductDependencyInfo::ProductDependencyFlags m_flags;
// By default, initialize the dependency flags to "NoLoad" so that dependent assets aren't triggered to load.
// Only set dependent assets to load if the creation of a product dependency explicitly requests it. This makes it
// more likely to prevent accidental loads when creating dependencies based solely on IDs or other implicit asset
// references.
ProductDependency()
: m_flags(AZ::Data::ProductDependencyInfo::CreateFlags(AZ::Data::AssetLoadBehavior::NoLoad))
{
}
ProductDependency(AZ::Data::AssetId dependencyId, const AZStd::bitset<64>& flags);
static void Reflect(AZ::ReflectContext* context);
};
using ProductPathDependencySet = AZStd::unordered_set<AssetBuilderSDK::ProductPathDependency>;
//! JobProduct is used by the builder to store job product information
struct JobProduct
{
AZ_CLASS_ALLOCATOR(JobProduct, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(JobProduct, "{d1d35d2c-3e4a-45c6-a13a-e20056344516}");
AZStd::string m_productFileName; // relative or absolute product file path
AZ::Data::AssetType m_productAssetType = AZ::Data::AssetType::CreateNull(); // the type of asset this is
AZ::u32 m_productSubID; ///< a stable product identifier - see note below.
/// LegacySUBIds are other names for the same product for legacy compatibility.
/// if you ever referred to this product by a different sub-id previously but have decided to change your numbering scheme
/// You should emit the prior sub ids into this array. If we ever go looking for an asset and we fail to find it under a
/// canonical product SubID, the system will attempt to look it up in the list of "previously known as..." legacy subIds in case
/// the source data it is reading is old. This allows you to change your subID scheme at any time as long as you include
/// the old scheme in the legacySubIDs list.
AZStd::vector<AZ::u32> m_legacySubIDs;
// SUB ID context: A Stable sub id means a few things. Products (game ready assets) are identified in the engine by AZ::Data::AssetId, which is a combination of source guid which is random and this product sub id. AssetType is currently NOT USED to differentiate assets by the system. So if two or more products of the same source are for the same platform they can not generate the same sub id!!! If they did this would be a COLLISION!!! which would not allow the rngine to access one or more of the products!!! Not using asset type in the differentiation may change in the future, but it is the way it is done for now.
// SUB ID RULES:
// 1. The builder alone is responsible for determining asset type and sub id.
// 2. The sub id has to be build run stable, meaning if the builder were to run again for the same source the same sub id would be generated by the builder to identify this product.
// 3. The sub id has to be location stable, meaning they can not be based on the location of the source or product, so if the source was moved to a different location it should still produce the same sub id for the same product.
// 4. The sub id has to be platform stable, meaning if the builder were to make the equivalent product for a different platform the sub id for the equivalent product on the other platform should be the same.
// 5. The sub id has to be multi output stable and mutually exclusive, meaning if your builder outputs multiple products from a source, the product sub id for each product must be different from one another and reproducible. So if you use an incrementing number scheme to differentiate products, that must also be stable, even when the source changes. So if a change occurs to the source, it gets rebuilt and the sub ids must still be the same. Put another way, if your builder outputs multiple product files, and produces the number and order and type of product, no matter what change to the source is made, then you're good. However, if changing the source may result in less or more products than last time, you may have a problem. The same products this time must have the same sub id as last time and can not have shifted up or down. Its ok if the extra product has the next new number, or if one less product is produced doesn't effect the others, in short they can never shift ids which would be the case for incrementing ids if one should no longer be produced. Note that the builder has no other information from run to run than the source data, it can not access any other data, source, product, database or otherwise receive data from any previous run. If the builder used an enumerated value for different outputs, that would work, say if he diffuse output always uses the enumerated value sub id 2 and the alpha always used 6, that should be fine, even if the source is modified such that it no longer outputs an alpha, the diffuse would still always map to 2.
// SUGGESTIONS:
// 1. If your builder only ever has one product for a source then we recommend that sub id be set to 0, this should satisfy all the above rules.
// 2. Do not base sub id on file paths, if the location of source or destination changes the sub id will not be stable.
// 3. Do not base sub id on source or product file name, extensions usually differ per platform and across platform they should be the stable.
// 4. It might be ok to base sub id on extension-less product file name. It seems likely it would be stable as the product name would most likely be the same no matter its location as the path to the file and files extension could be different per platform and thus using only the extension-less file name would mostly likely be the same across platform. Be careful though, because if you output many same named files just with different extensions FOR THE SAME PLATFORM you will have collision problems.
// 5. Basing the sub id on a simple incrementing number may be reasonable ONLY if order can never change, or the order if changed it would not matter. This may make sense for mip levels of textures if produced as separate products such that the sub id is equal to mip level, or lods for a mesh such that the sub id is the lod level.
// 6. Think about using some other encoding scheme like using enumerations or using flag bits. If we do then we might be able to guess the sub id at runtime, that could be useful. Name spacing using the upper bits might be useful for final determination of product. This could be part of a localization scheme, or user settings options like choosing green blood via upper bits, or switching between products built by different builders which have stable lower bits and different name space upper bits. I am not currently convinced that encoding information into the sub id like this is a really great idea, however if it does not violate the rules, it is allowed, and it may solve a problem or two for specific systems.
// 7. A Tagging system for products (even sources?) that allows the builder to add any tag it want to a product that would be available at tool time (and at runtime?) might be a better way than trying to encode that kind of data in product sub id's.
//! Product assets this asset depends on
AZStd::vector<ProductDependency> m_dependencies;
/// Dependencies specified by relative path in the resource
/// Paths should only be used in legacy systems, put ProductDependency objects in m_dependencies wherever possible.
ProductPathDependencySet m_pathDependencies;
/// Indicate to Asset Processor that the builder has output any possible dependencies (including if there are none).
/// This should only be set if the builder really does take care of outputting its dependencies OR the output product never has dependencies.
/// When false, AP will emit a warning that dependencies have not been handled.
bool m_dependenciesHandled{ false };
JobProduct() = default;
JobProduct(const AZStd::string& productName, AZ::Data::AssetType productAssetType = AZ::Data::AssetType::CreateNull(), AZ::u32 productSubID = 0);
JobProduct(AZStd::string&& productName, AZ::Data::AssetType productAssetType = AZ::Data::AssetType::CreateNull(), AZ::u32 productSubID = 0);
//////////////////////////////////////////////////////////////////////////
// Legacy compatibility
// when builders output asset type, but don't specify what type they actually are, we guess by file extension and other
// markers. This is not ideal. If you're writing a new builder, endeavor to actually select a product asset type and a subId
// that matches your needs.
static AZ::Data::AssetType InferAssetTypeByProductFileName(const char* productFile);
static AZ::u32 InferSubIDFromProductFileName(const AZ::Data::AssetType& assetType, const char* productFile);
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* context);
};
//! ProcessJobRequest contains input job data that will be send by the AssetProcessor to the builder for processing jobs
struct ProcessJobRequest
{
AZ_CLASS_ALLOCATOR(ProcessJobRequest, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(ProcessJobRequest, "{20461454-d2f9-4079-ab95-703905e06002}");
AZStd::string m_sourceFile; ///! relative source file name
AZStd::string m_watchFolder; ///! watch folder for this source file
AZStd::string m_fullPath; ///! full source file name
AZ::Uuid m_builderGuid; ///! builder id
JobDescriptor m_jobDescription; ///! job descriptor for this job. Note that this still contains the job parameters from when you emitted it during CreateJobs
PlatformInfo m_platformInfo; ///! the information about the platform that this job was emitted for.
AZStd::string m_tempDirPath; // temp directory that the builder should use to create job outputs for this job request
AZ::u64 m_jobId; ///! job id for this job, this is also the address for the JobCancelListener
AZ::Uuid m_sourceFileUUID; ///! the UUID of the source file. Will be used as the uuid of the AssetID of the product when combined with the subID.
AZStd::vector<SourceFileDependency> m_sourceFileDependencyList;
static void Reflect(AZ::ReflectContext* context);
};
enum ProcessJobResultCode
{
ProcessJobResult_Success = 0,
ProcessJobResult_Failed = 1,
ProcessJobResult_Crashed = 2,
ProcessJobResult_Cancelled = 3,
ProcessJobResult_NetworkIssue = 4
};
//! ProcessJobResponse contains job data that will be send by the builder to the assetProcessor in response to ProcessJobRequest
struct ProcessJobResponse
{
AZ_CLASS_ALLOCATOR(ProcessJobResponse, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(ProcessJobResponse, "{6b48ada5-0d52-43be-ad57-0bf8aeaef04b}");
ProcessJobResultCode m_resultCode = ProcessJobResult_Failed;
AZStd::vector<JobProduct> m_outputProducts;
bool m_requiresSubIdGeneration = true; //!< Used to determine if legacy RC products need sub ids generated for them.
//! Populate m_sourcesToReprocess with sources by absolute path which you want to trigger a rebuild for
//! To reprocess these sources, make sure to update fingerprints in CreateJobs of those builders which process them, like changing source dependencies.
AZStd::vector<AZStd::string> m_sourcesToReprocess;
bool Succeeded() const;
static void Reflect(AZ::ReflectContext* context);
};
//! BuilderHelloRequest is sent by an AssetBuilder that is attempting to connect to the AssetProcessor to register itself as a worker
class BuilderHelloRequest : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(BuilderHelloRequest, AZ::OSAllocator, 0);
AZ_RTTI(BuilderHelloRequest, "{5fab5962-a1d8-42a5-bf7a-fb1a8c5a9588}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
static unsigned int MessageType();
unsigned int GetMessageType() const override;
//! Unique ID assigned to this builder to identify it
AZ::Uuid m_uuid = AZ::Uuid::CreateNull();
};
//! BuilderHelloResponse contains the AssetProcessor's response to a builder connection attempt, indicating if it is accepted and the ID that it was assigned
class BuilderHelloResponse : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(BuilderHelloResponse, AZ::OSAllocator, 0);
AZ_RTTI(BuilderHelloResponse, "{5f3d7c11-6639-4c6f-980a-32be546903c2}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
unsigned int GetMessageType() const override;
//! Indicates if the builder was accepted by the AP
bool m_accepted = false;
//! Unique ID assigned to the builder. If the builder isn't a local process, this is the ID assigned by the AP
AZ::Uuid m_uuid = AZ::Uuid::CreateNull();
};
class CreateJobsNetRequest : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(CreateJobsNetRequest, AZ::OSAllocator, 0);
AZ_RTTI(CreateJobsNetRequest, "{97fa717d-3a09-4d21-95c6-b2eafd773f1c}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
static unsigned int MessageType();
unsigned int GetMessageType() const override;
CreateJobsRequest m_request;
};
class CreateJobsNetResponse : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(CreateJobsNetResponse, AZ::OSAllocator, 0);
AZ_RTTI(CreateJobsNetResponse, "{b2c7c2d3-b60e-4b27-b699-43e0ba991c33}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
unsigned int GetMessageType() const override;
CreateJobsResponse m_response;
};
class ProcessJobNetRequest : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(ProcessJobNetRequest, AZ::OSAllocator, 0);
AZ_RTTI(ProcessJobNetRequest, "{05288de1-020b-48db-b9de-715f17284efa}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
static unsigned int MessageType();
unsigned int GetMessageType() const override;
ProcessJobRequest m_request;
};
class ProcessJobNetResponse : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(ProcessJobNetResponse, AZ::OSAllocator, 0);
AZ_RTTI(ProcessJobNetResponse, "{26ddf882-246c-4cfb-912f-9b8e389df4f6}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
unsigned int GetMessageType() const override;
ProcessJobResponse m_response;
};
//! JobCancelListener can be used by builders in their processJob method to listen for job cancellation request.
//! The address of this listener is the jobid which can be found in the process job request.
class JobCancelListener : public JobCommandBus::Handler
{
public:
explicit JobCancelListener(AZ::u64 jobId);
~JobCancelListener() override;
JobCancelListener(const JobCancelListener&) = delete;
//////////////////////////////////////////////////////////////////////////
//!JobCommandBus::Handler overrides
//!Note: This will be called on a thread other than your processing job thread.
//!You can derive from JobCancelListener and reimplement Cancel if you need to do something special in order to cancel your job.
void Cancel() override;
///////////////////////////////////////////////////////////////////////
bool IsCancelled() const;
private:
AZStd::atomic_bool m_cancelled;
};
// the Assert Absorber here is used to absorb asserts during regex creation.
// it only absorbs asserts spawned by this thread;
class AssertAbsorber
: public AZ::Debug::TraceMessageBus::Handler
{
public:
AssertAbsorber();
~AssertAbsorber();
bool OnAssert(const char* message) override;
AZStd::string m_assertMessage;
// only absorb messages for your thread!
static AZ_THREAD_LOCAL bool s_onAbsorbThread;
};
//! Trace hook for asserts/errors.
//! This allows us to detect any errors that occur during a job so we can fail it.
class AssertAndErrorAbsorber
: public AZ::Debug::TraceMessageBus::Handler
{
public:
explicit AssertAndErrorAbsorber(bool errorsWillFailJob);
~AssertAndErrorAbsorber() override;
bool OnAssert(const char* message) override;
bool OnError(const char* window, const char* message) override;
size_t GetErrorCount() const;
private:
bool m_errorsWillFailJob;
size_t m_errorsOccurred = 0;
//! The id of the thread that created this object.
//! There can be multiple builders running at once, so we need to filter out ones coming from other builders
AZStd::thread_id m_jobThreadId;
};
} // namespace AssetBuilderSDK
namespace AZ
{
AZ_TYPE_INFO_SPECIALIZE(AssetBuilderSDK::AssetBuilderPattern::PatternType, "{8519E97D-1159-4CA4-A6DD-16043349B15A}");
AZ_TYPE_INFO_SPECIALIZE(AssetBuilderSDK::CreateJobsResultCode, "{D3F90549-CE6C-4155-BE19-33E4C05373DB}");
AZ_TYPE_INFO_SPECIALIZE(AssetBuilderSDK::JobDependencyType, "{854ADE4E-0C2F-43BC-B5F6-8D99C26A17DF}");
AZ_TYPE_INFO_SPECIALIZE(AssetBuilderSDK::ProcessJobResultCode, "{15797D63-4980-436A-9DE1-E0CCA9B5DB19}");
AZ_TYPE_INFO_SPECIALIZE(AssetBuilderSDK::ProductPathDependencyType, "{EF77742B-9627-4072-B431-396AA7183C80}");
AZ_TYPE_INFO_SPECIALIZE(AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType, "{BE9C8805-DB17-4500-944A-EB33FD0BE347}");
}
//! This macro should be used by every AssetBuilder to register itself,
//! AssetProcessor uses these exported function to identify whether a dll is an Asset Builder or not
//! If you want something highly custom you can do these entry points yourself instead of using the macro.
#define REGISTER_ASSETBUILDER \
extern void BuilderOnInit(); \
extern void BuilderDestroy(); \
extern void BuilderRegisterDescriptors(); \
extern void BuilderAddComponents(AZ::Entity * entity); \
extern "C" \
{ \
AZ_DLL_EXPORT int IsAssetBuilder() \
{ \
return 0; \
} \
\
AZ_DLL_EXPORT void InitializeModule(AZ::EnvironmentInstance sharedEnvironment) \
{ \
AZ::Environment::Attach(sharedEnvironment); \
BuilderOnInit(); \
} \
\
AZ_DLL_EXPORT void UninitializeModule() \
{ \
BuilderDestroy(); \
AZ::Environment::Detach(); \
} \
\
AZ_DLL_EXPORT void ModuleRegisterDescriptors() \
{ \
BuilderRegisterDescriptors(); \
} \
\
AZ_DLL_EXPORT void ModuleAddComponents(AZ::Entity * entity) \
{ \
BuilderAddComponents(entity); \
} \
}
// confusion-reducing note: above end-brace is part of the macro, not a namespace
@@ -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.
*
*/
#include <AssetBuilderSDK/SerializationDependencies.h>
namespace AssetBuilderSDK
{
bool UpdateDependenciesFromClassData(
const AZ::SerializeContext& serializeContext,
void* instancePointer,
const AZ::SerializeContext::ClassData* classData,
const AZ::SerializeContext::ClassElement* classElement,
UniqueDependencyList& productDependencySet,
ProductPathDependencySet& productPathDependencySet,
bool enumerateChildren)
{
if(classData == nullptr)
{
return false;
}
if (classData->m_typeId == AZ::GetAssetClassId())
{
auto* asset = reinterpret_cast<AZ::Data::Asset<AZ::Data::AssetData>*>(instancePointer);
if (asset->GetId().IsValid())
{
productDependencySet[asset->GetId()] = AZ::Data::ProductDependencyInfo::CreateFlags(asset->GetAutoLoadBehavior());
}
}
else if (classData->m_typeId == azrtti_typeid<AZ::Data::AssetId>())
{
auto* assetId = reinterpret_cast<AZ::Data::AssetId*>(instancePointer);
if (assetId->IsValid())
{
// For asset ID dependencies, set the behavior to "NoLoad" so that loading the parent asset doesn't trigger a load
// of the dependent asset.
productDependencySet[*assetId] = AZ::Data::ProductDependencyInfo::CreateFlags(AZ::Data::AssetLoadBehavior::NoLoad);
}
}
else if (classData->m_azRtti && classData->m_azRtti->IsTypeOf(azrtti_typeid<AzFramework::SimpleAssetReferenceBase>()))
{
auto* asset = reinterpret_cast<AzFramework::SimpleAssetReferenceBase*>(instancePointer);
if (!asset->GetAssetPath().empty())
{
AZStd::string filePath = asset->GetAssetPath();
AZStd::string fileExtension;
if (!AzFramework::StringFunc::Path::GetExtension(filePath.c_str(), fileExtension))
{
// GetFileFilter can return either
// 1) one file extension like "*.fileExtension"
// 2) one file extension like "fileExtension"
// 3) a semi colon separated list of file extensions like "*.fileExtension1; *.fileExtension2"
// Please note that if file extension is missing from the path and we get a list of semicolon separated file extensions
// we will extract the first file extension and use that.
fileExtension = asset->GetFileFilter();
AZStd::regex fileExtensionRegex("^(?:\\*\\.)?(\\w+);?");
AZStd::smatch match;
if (AZStd::regex_search(fileExtension, match, fileExtensionRegex))
{
fileExtension = match[1];
AzFramework::StringFunc::Path::ReplaceExtension(filePath, fileExtension.c_str());
}
}
productPathDependencySet.emplace(filePath, ProductPathDependencyType::ProductFile);
}
}
else if(enumerateChildren)
{
auto beginCallback = [&serializeContext, &productDependencySet, &productPathDependencySet](void* instancePointer, const AZ::SerializeContext::ClassData* classData, const AZ::SerializeContext::ClassElement* classElement)
{
// EnumerateInstance calls are already recursive, so no need to keep going, set enumerateChildren to false.
return UpdateDependenciesFromClassData(serializeContext, instancePointer, classData, classElement, productDependencySet, productPathDependencySet, false);
};
AZ::SerializeContext::EnumerateInstanceCallContext callContext(
beginCallback,
{},
&serializeContext,
AZ::SerializeContext::ENUM_ACCESS_FOR_READ,
nullptr
);
return serializeContext.EnumerateInstance(&callContext, instancePointer, classData->m_typeId, classData, classElement);
}
return true;
}
void FillDependencyVectorFromSet(
AZStd::vector<ProductDependency>& productDependencies,
UniqueDependencyList& productDependencySet)
{
productDependencies.reserve(productDependencySet.size());
for (const auto& thisEntry : productDependencySet)
{
constexpr int flags = 0;
productDependencies.emplace_back(thisEntry.first, thisEntry.second);
}
}
bool GatherProductDependenciesForFile(
AZ::SerializeContext& serializeContext,
const AZStd::string& filePath,
AZStd::vector<ProductDependency>& productDependencies,
ProductPathDependencySet& productPathDependencySet)
{
AZ::IO::FileIOStream fileStream;
if (!fileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary))
{
return false;
}
UniqueDependencyList productDependencySet;
// UpdateDependenciesFromClassData is also looking for assets. In some cases, the assets may not be ready to use
// in UpdateDependenciesFromClassData, and have an invalid asset ID. This asset filter will be called with valid, ready to use assets,
// but it's only called on assets and not other supported types, and it's only available when loading the file, and not on an in-memory stream.
AZ::ObjectStream::FilterDescriptor assetReadyFilterDescriptor([&productDependencySet](const AZ::Data::AssetFilterInfo& filterInfo)
{
if (filterInfo.m_assetId.IsValid())
{
productDependencySet[filterInfo.m_assetId] = AZ::Data::ProductDependencyInfo::CreateFlags(filterInfo.m_loadBehavior);
}
return false;
});
if (!AZ::ObjectStream::LoadBlocking(&fileStream, serializeContext, [&productDependencySet, &productPathDependencySet](void* instancePointer, const AZ::Uuid& classId, const AZ::SerializeContext* callbackSerializeContext)
{
auto classData = callbackSerializeContext->FindClassData(classId);
// LoadBlocking only enumerates the topmost level objects, so call UpdateDependenciesFromClassData with enumerateChildren set.
UpdateDependenciesFromClassData(*callbackSerializeContext, instancePointer, classData, nullptr, productDependencySet, productPathDependencySet, true);
return true;
}, assetReadyFilterDescriptor))
{
return false;
}
FillDependencyVectorFromSet(productDependencies, productDependencySet);
return true;
}
bool GatherProductDependencies(
AZ::SerializeContext& serializeContext,
void* obj,
AZ::TypeId typeId,
AZStd::vector<ProductDependency>& productDependencies,
ProductPathDependencySet& productPathDependencySet,
const DependencyHandler& handler)
{
if (obj == nullptr)
{
AZ_Error("AssetBuilderSDK", false, "Cannot gather product dependencies for null data.");
return false;
}
// start with a set to make it easy to avoid duplicate entries.
UniqueDependencyList productDependencySet;
auto beginCallback = [&serializeContext, &productDependencySet, &productPathDependencySet, handler](void* instancePointer, const AZ::SerializeContext::ClassData* classData, const AZ::SerializeContext::ClassElement* classElement)
{
// EnumerateObject already visits every element, so no need to enumerate farther, set enumerateChildren to false.
return handler(serializeContext, instancePointer, classData, classElement, productDependencySet, productPathDependencySet, false);
};
bool enumerateResult = serializeContext.EnumerateInstanceConst(obj, typeId, beginCallback, {}, AZ::SerializeContext::ENUM_ACCESS_FOR_READ, nullptr, nullptr);
FillDependencyVectorFromSet(productDependencies, productDependencySet);
return enumerateResult;
}
bool OutputObject(void* obj, AZ::TypeId typeId, AZStd::string_view outputPath, AZ::Data::AssetType assetType, AZ::u32 subId, JobProduct& jobProduct, AZ::SerializeContext* serializeContext, const DependencyHandler& handler)
{
if (!serializeContext)
{
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
}
if(!serializeContext)
{
AZ_Error("AssetBuilderSDK", false, "Failed to retrieve serialization context.");
return false;
}
jobProduct = JobProduct(outputPath, assetType, subId);
if (GatherProductDependencies(*serializeContext, obj, typeId, jobProduct.m_dependencies, jobProduct.m_pathDependencies, handler))
{
jobProduct.m_dependenciesHandled = true;
return true;
}
jobProduct = {};
return false;
}
}
@@ -0,0 +1,107 @@
/*
* 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/Asset/AssetCommon.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzFramework/Asset/SimpleAsset.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzCore/std/string/regex.h>
#include <AzCore/Component/ComponentApplicationBus.h>
namespace AZ
{
class SerializeContext;
}
namespace AssetBuilderSDK
{
using UniqueDependencyList = AZStd::unordered_map<AZ::Data::AssetId, AZ::Data::ProductDependencyInfo::ProductDependencyFlags>;
bool UpdateDependenciesFromClassData(
const AZ::SerializeContext& serializeContext,
void* instancePointer,
const AZ::SerializeContext::ClassData* classData,
const AZ::SerializeContext::ClassElement* classElement,
UniqueDependencyList& productDependencySet,
ProductPathDependencySet& productPathDependencySet,
bool enumerateChildren);
void FillDependencyVectorFromSet(AZStd::vector<AssetBuilderSDK::ProductDependency>& productDependencies, UniqueDependencyList& productDependencySet);
bool GatherProductDependenciesForFile(
AZ::SerializeContext& serializeContext,
const AZStd::string& filePath,
AZStd::vector<AssetBuilderSDK::ProductDependency>& productDependencies,
ProductPathDependencySet& productPathDependencySet);
using DependencyHandler = AZStd::function<bool(
const AZ::SerializeContext& /*serializeContext*/,
void* /*instancePointer*/,
const AZ::SerializeContext::ClassData* /*classData*/,
const AZ::SerializeContext::ClassElement* /*classElement*/,
UniqueDependencyList& /*productDependencySet*/,
ProductPathDependencySet& /*productPathDependencySet*/,
bool enumerateChildren)>;
bool GatherProductDependencies(
AZ::SerializeContext& serializeContext,
void* obj,
AZ::TypeId typeId,
AZStd::vector<ProductDependency>& productDependencies,
ProductPathDependencySet& productPathDependencySet,
const DependencyHandler& handler = &UpdateDependenciesFromClassData);
template<class T>
bool GatherProductDependencies(
AZ::SerializeContext& serializeContext,
AZ::Data::Asset<T>* obj,
AZ::TypeId typeId,
AZStd::vector<ProductDependency>& productDependencies,
ProductPathDependencySet& productPathDependencySet,
const DependencyHandler& handler = &UpdateDependenciesFromClassData)
{
AZ_Error("AssetBuilderSDK", false, "Can't output dependencies for AZ::Data::Asset<T>* - Use T* or another underlying type");
return false;
}
template<class T>
bool GatherProductDependencies(
AZ::SerializeContext& serializeContext,
T* obj,
AZStd::vector<ProductDependency>& productDependencies,
ProductPathDependencySet& productPathDependencySet,
const DependencyHandler& handler = &UpdateDependenciesFromClassData)
{
return GatherProductDependencies(serializeContext, obj, azrtti_typeid<T>(), productDependencies, productPathDependencySet, handler);
}
bool OutputObject(void* obj, AZ::TypeId typeId, AZStd::string_view outputPath, AZ::Data::AssetType assetType, AZ::u32 subId, JobProduct& jobProduct, AZ::SerializeContext* serializeContext = nullptr,
const DependencyHandler& handler = &UpdateDependenciesFromClassData);
template<class T>
bool OutputObject(T* obj, AZStd::string_view outputPath, AZ::Data::AssetType assetType, AZ::u32 subId, JobProduct& jobProduct, AZ::SerializeContext* serializeContext = nullptr,
const DependencyHandler& handler = &UpdateDependenciesFromClassData)
{
return OutputObject(obj, azrtti_typeid<T>(), outputPath, assetType, subId, jobProduct, serializeContext, handler);
}
template<class T>
bool OutputObject(AZ::Data::Asset<T>* obj, AZStd::string_view outputPath, AZ::Data::AssetType assetType, AZ::u32 subId, JobProduct& jobProduct, AZ::SerializeContext* serializeContext = nullptr,
const DependencyHandler& handler = &UpdateDependenciesFromClassData)
{
AZ_Error("AssetBuilderSDK", false, "Can't output dependencies for AZ::Data::Asset<T>* - Use T* or another underlying type");
return false;
}
}
@@ -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.
#
ly_get_pal_tool_dirs(pal_tool_dirs ${CMAKE_CURRENT_LIST_DIR}/AssetBuilderSDK/Platform)
set(pal_files "")
foreach(enabled_platform ${LY_PAL_TOOLS_ENABLED})
string(TOLOWER ${enabled_platform} enabled_platform_lowercase)
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/AssetBuilderSDK/Platform/${enabled_platform})
list(APPEND pal_files ${pal_dir}/assetbuildersdk_${enabled_platform_lowercase}_files.cmake)
endforeach()
ly_add_target(
NAME AssetBuilderSDK STATIC
NAMESPACE AZ
FILES_CMAKE
assetbuilder_files.cmake
${pal_files}
INCLUDE_DIRECTORIES
PUBLIC
.
PRIVATE
${pal_tool_dirs}
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
PUBLIC
AZ::AzFramework
AZ::AzToolsFramework
)
ly_add_source_properties(
SOURCES AssetBuilderSDK/AssetBuilderSDK.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES ${LY_PAL_TOOLS_DEFINES}
)
@@ -0,0 +1,18 @@
#
# 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.
#
set(FILES
AssetBuilderSDK/AssetBuilderSDK.h
AssetBuilderSDK/AssetBuilderSDK.cpp
AssetBuilderSDK/AssetBuilderBusses.h
AssetBuilderSDK/SerializationDependencies.h
AssetBuilderSDK/SerializationDependencies.cpp
)
+205
View File
@@ -0,0 +1,205 @@
#
# 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.
#
if(NOT PAL_TRAIT_BUILD_HOST_TOOLS)
return()
endif()
# Builders need to be defined first because we collect the builders and pass them
# to AssetBuilder and AssetProcessor so it loads them.
add_subdirectory(AssetBuilderSDK)
add_subdirectory(AssetBuilder)
include(${CMAKE_CURRENT_SOURCE_DIR}/Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
ly_add_target(
NAME AssetProcessor.Static STATIC
NAMESPACE AZ
AUTOMOC
AUTORCC
FILES_CMAKE
assetprocessor_static_files.cmake
Platform/${PAL_PLATFORM_NAME}/assetprocessor_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
Platform/${PAL_PLATFORM_NAME}
PRIVATE
native
BUILD_DEPENDENCIES
PUBLIC
3rdParty::Qt::Core
3rdParty::Qt::Gui
3rdParty::Qt::Network
3rdParty::RapidJSON
3rdParty::SQLite
3rdParty::XXHash
AZ::AzCore
AZ::AzFramework
AZ::AzQtComponents
AZ::AzToolsFramework
AZ::AssetBuilderSDK
${additional_dependencies}
RUNTIME_DEPENDENCIES
AZ::AssetBuilder
Legacy::RC
)
# Aggregates all combined AssetBuilders into a single LY_ASSET_BUILDERS #define
get_property(asset_builders GLOBAL PROPERTY LY_ASSET_BUILDERS)
string (REPLACE ";" "," asset_builders "${asset_builders}")
ly_add_source_properties(
SOURCES native/utilities/ApplicationManager.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES LY_ASSET_BUILDERS="${asset_builders}"
)
ly_add_target(
NAME AssetProcessor ${PAL_TRAIT_BUILD_ASSETPROCESSOR_APPLICATION_TYPE}
NAMESPACE AZ
AUTOMOC
AUTOUIC
AUTORCC
FILES_CMAKE
assetprocessor_gui_files.cmake
Platform/${PAL_PLATFORM_NAME}/assetprocessor_gui_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
PLATFORM_INCLUDE_FILES
Platform/${PAL_PLATFORM_NAME}/assetprocessor_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
PRIVATE
native
BUILD_DEPENDENCIES
PRIVATE
AZ::AssetProcessor.Static
)
ly_add_source_properties(
SOURCES native/utilities/BatchApplicationManager.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES LY_METRICS_BUILD_TIME=${LY_METRICS_BUILD_TIME}
)
# Adds the AssetProcessor target as a C preprocessor define so that it can be used as a Settings Registry
# specialization in order to look up the generated .setreg which contains the dependencies
# specified for the target.
if(TARGET AssetProcessor)
set_source_files_properties(
native/AssetProcessorBuildTarget.cpp
PROPERTIES
COMPILE_DEFINITIONS
LY_CMAKE_TARGET="AssetProcessor"
)
else()
message(FATAL_ERROR "Cannot set LY_CMAKE_TARGET define to AssetProcessor as the target doesn't exist anymore."
" Perhaps it has been renamed")
endif()
ly_add_target(
NAME AssetProcessorBatch.Static STATIC
NAMESPACE AZ
AUTOMOC
FILES_CMAKE
assetprocessor_static_batch_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
PRIVATE
native
BUILD_DEPENDENCIES
PUBLIC
AZ::AssetProcessor.Static
)
ly_add_target(
NAME AssetProcessorBatch EXECUTABLE
NAMESPACE AZ
AUTOMOC
FILES_CMAKE
assetprocessor_batch_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
native
BUILD_DEPENDENCIES
PRIVATE
AZ::AssetProcessorBatch.Static
)
# Adds the AssetProcessorBatch target as a C preprocessor define so that it can be used as a Settings Registry
# specialization in order to look up the generated .setreg which contains the dependencies
# specified for the target.
if(TARGET AssetProcessorBatch)
set_source_files_properties(
native/AssetProcessorBatchBuildTarget.cpp
PROPERTIES
COMPILE_DEFINITIONS
LY_CMAKE_TARGET="AssetProcessorBatch"
)
else()
message(FATAL_ERROR "Cannot set LY_CMAKE_TARGET define to AssetProcessorBatch as the target doesn't exist anymore."
" Perhaps it has been renamed")
endif()
################################################################################
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME AssetProcessor.Tests EXECUTABLE
NAMESPACE AZ
AUTOMOC
AUTORCC
FILES_CMAKE
assetprocessor_test_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
native
.
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AssetProcessorBatch.Static
AZ::AzToolsFrameworkTestCommon
)
ly_add_source_properties(
SOURCES native/tests/assetBuilderSDK/assetBuilderSDKTest.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES ${LY_PAL_TOOLS_DEFINES}
)
ly_add_source_properties(
SOURCES native/unittests/AssetProcessorManagerUnitTests.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES LY_CMAKE_BINARY_DIR="${CMAKE_BINARY_DIR}"
)
# Have the AssetProcessorTest use the LY_CMAKE_TARGET define of AssetProcessorBatch for the purpose
# of looking up the generated cmake build dependencies settings registry .setreg file
# It is tied to the UnitTestRunner.cpp file
if(TARGET AssetProcessorBatch)
set_source_files_properties(
native/unittests/UnitTestRunner.cpp
PROPERTIES
COMPILE_DEFINITIONS
LY_CMAKE_TARGET="AssetProcessorBatch"
)
else()
message(FATAL_ERROR "Cannot set LY_CMAKE_TARGET define to AssetProcessorBatch as the target doesn't exist anymore."
" Perhaps it has been renamed")
endif()
ly_add_googletest(
NAME AZ::AssetProcessor.Tests
TEST_COMMAND $<TARGET_FILE:AZ::AssetProcessor.Tests> --unittest
)
endif()
@@ -0,0 +1,15 @@
/*
* 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
#define ASSETPROCESSOR_TRAIT_LEGACY_RC_RELATIVE_PATH "/rc"
#define ASSETPROCESSOR_TRAIT_CASE_SENSITIVE_FILESYSTEM true
@@ -0,0 +1,14 @@
/*
* 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 <AssetProcessor_Traits_Linux.h>
@@ -0,0 +1,13 @@
#
# 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.
#
set (PAL_TRAIT_BUILD_ASSETPROCESSOR_APPLICATION_TYPE APPLICATION)
@@ -0,0 +1,13 @@
#
# 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.
#
set(FILES
)
@@ -0,0 +1,11 @@
#
# 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.
#
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
native/FileWatcher/FileWatcher_linux.cpp
)
@@ -0,0 +1,52 @@
/*
* 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/FileWatcher/FileWatcher.h>
struct FolderRootWatch::PlatformImplementation
{
PlatformImplementation() { }
};
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
FolderRootWatch::FolderRootWatch(const QString rootFolder)
: m_root(rootFolder)
, m_shutdownThreadSignal(false)
, m_fileWatcher(nullptr)
, m_platformImpl(new PlatformImplementation())
{
}
FolderRootWatch::~FolderRootWatch()
{
// Destructor is required in here since this file contains the definition of struct PlatformImplementation
Stop();
delete m_platformImpl;
}
bool FolderRootWatch::Start()
{
// TODO: Implement for Linux
return false;
}
void FolderRootWatch::Stop()
{
// TODO: Implement for Linux
}
void FolderRootWatch::WatchFolderLoop()
{
// TODO: Implement for Linux
}
@@ -0,0 +1,134 @@
/*
* 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/FileWatcher/FileWatcher.h>
#include <AzCore/PlatformIncl.h>
struct FolderRootWatch::PlatformImplementation
{
PlatformImplementation() : m_directoryHandle(nullptr), m_ioHandle(nullptr) { }
HANDLE m_directoryHandle;
HANDLE m_ioHandle;
};
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
FolderRootWatch::FolderRootWatch(const QString rootFolder)
: m_root(rootFolder)
, m_shutdownThreadSignal(false)
, m_fileWatcher(nullptr)
, m_platformImpl(new PlatformImplementation())
{
}
FolderRootWatch::~FolderRootWatch()
{
// Destructor is required in here since this file contains the definition of struct PlatformImplementation
Stop();
delete m_platformImpl;
}
bool FolderRootWatch::Start()
{
m_platformImpl->m_directoryHandle = ::CreateFileW(m_root.toStdWString().data(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
if (m_platformImpl->m_directoryHandle != INVALID_HANDLE_VALUE)
{
m_platformImpl->m_ioHandle = ::CreateIoCompletionPort(m_platformImpl->m_directoryHandle, nullptr, 1, 0);
if (m_platformImpl->m_ioHandle != INVALID_HANDLE_VALUE)
{
m_shutdownThreadSignal = false;
m_thread = std::thread(std::bind(&FolderRootWatch::WatchFolderLoop, this));
return true;
}
}
return false;
}
void FolderRootWatch::Stop()
{
m_shutdownThreadSignal = true;
CloseHandle(m_platformImpl->m_ioHandle);
m_platformImpl->m_ioHandle = nullptr;
if (m_thread.joinable())
{
m_thread.join(); // wait for the thread to finish
m_thread = std::thread(); //destroy
}
CloseHandle(m_platformImpl->m_directoryHandle);
m_platformImpl->m_directoryHandle = nullptr;
}
void FolderRootWatch::WatchFolderLoop()
{
FILE_NOTIFY_INFORMATION aFileNotifyInformationList[50000];
QString path;
OVERLAPPED aOverlapped;
LPOVERLAPPED pOverlapped;
DWORD dwByteCount;
ULONG_PTR ulKey;
while (!m_shutdownThreadSignal)
{
::memset(aFileNotifyInformationList, 0, sizeof(aFileNotifyInformationList));
::memset(&aOverlapped, 0, sizeof(aOverlapped));
if (::ReadDirectoryChangesW(m_platformImpl->m_directoryHandle, aFileNotifyInformationList, sizeof(aFileNotifyInformationList), true, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_FILE_NAME, nullptr, &aOverlapped, nullptr))
{
//wait for up to a second for I/O to signal
dwByteCount = 0;
if (::GetQueuedCompletionStatus(m_platformImpl->m_ioHandle, &dwByteCount, &ulKey, &pOverlapped, INFINITE))
{
//if we are signaled to shutdown bypass
if (!m_shutdownThreadSignal && ulKey)
{
if (dwByteCount)
{
int offset = 0;
FILE_NOTIFY_INFORMATION* pFileNotifyInformation = aFileNotifyInformationList;
do
{
pFileNotifyInformation = (FILE_NOTIFY_INFORMATION*)((char*)pFileNotifyInformation + offset);
path.clear();
path.append(m_root);
path.append(QString::fromWCharArray(pFileNotifyInformation->FileName, pFileNotifyInformation->FileNameLength / 2));
QString file = QDir::toNativeSeparators(QDir::cleanPath(path));
switch (pFileNotifyInformation->Action)
{
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME:
ProcessNewFileEvent(file);
break;
case FILE_ACTION_REMOVED:
case FILE_ACTION_RENAMED_OLD_NAME:
ProcessDeleteFileEvent(file);
break;
case FILE_ACTION_MODIFIED:
ProcessModifyFileEvent(file);
break;
}
offset = pFileNotifyInformation->NextEntryOffset;
} while (offset);
}
}
}
}
}
}
@@ -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.
*
*/
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Editor.rc
//
#define IDI_ICON1 2
#define IDC_STATIC -1
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 3
#define _APS_NEXT_COMMAND_VALUE 32769
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 110
#endif
#endif
@@ -0,0 +1,15 @@
/*
* 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
#define ASSETPROCESSOR_TRAIT_LEGACY_RC_RELATIVE_PATH "/rc"
#define ASSETPROCESSOR_TRAIT_CASE_SENSITIVE_FILESYSTEM false
@@ -0,0 +1,14 @@
/*
* 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 <AssetProcessor_Traits_Mac.h>
@@ -0,0 +1,59 @@
{
"images" : [
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a396a13d3bc7ca074cdc6fdd072ba18c2e359bd24408783809b6c90adf23e22a
size 21937
@@ -0,0 +1,59 @@
{
"images" : [
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a396a13d3bc7ca074cdc6fdd072ba18c2e359bd24408783809b6c90adf23e22a
size 21937
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,13 @@
#
# 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.
#
set (PAL_TRAIT_BUILD_ASSETPROCESSOR_APPLICATION_TYPE APPLICATION)
@@ -0,0 +1,16 @@
#
# 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.
#
set(FILES
gui_info.plist
native/utilities/MacDockIconHandler.mm
native/utilities/MacDockIconHandler.h
)
@@ -0,0 +1,25 @@
#
# 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.
#
ly_add_bundle_resources(
TARGET AssetProcessor
FILES
${CMAKE_SOURCE_DIR}/Code/Tools/RC/Config/rc/xmlfilter.txt
${CMAKE_SOURCE_DIR}/Code/Tools/RC/Config/rc/rc.ini
)
# Set resources directory for app icons
target_sources(AssetProcessor PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/Images.xcassets)
set_target_properties(AssetProcessor PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/gui_info.plist
RESOURCE ${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/Images.xcassets
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AssetProcessorAppIcon
)
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
native/FileWatcher/FileWatcher_macos.cpp
)
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>NSHumanReadableCopyright</key>
<string>This file Copyright (c) Amazon.com, Inc. or its affiliates. All Rights Reserved.</string>
<key>CFBundleSignature</key>
<string>ASPR</string>
<key>CFBundleExecutable</key>
<string>AssetProcessor</string>
<key>CFBundleIdentifier</key>
<string>com.Amazon.AssetProcessor</string>
</dict>
</plist>
@@ -0,0 +1,176 @@
/*
* 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/FileWatcher/FileWatcher.h>
#include <native/utilities/BatchApplicationManager.h>
#include <AzCore/Debug/Trace.h>
#include <CoreServices/CoreServices.h>
void FileEventStreamCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);
struct FolderRootWatch::PlatformImplementation
{
PlatformImplementation() : m_stream(nullptr), m_runLoop(nullptr) { }
FSEventStreamRef m_stream;
CFRunLoopRef m_runLoop;
QString m_renameFileDirectory;
};
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
FolderRootWatch::FolderRootWatch(const QString rootFolder)
: m_root(rootFolder)
, m_shutdownThreadSignal(false)
, m_fileWatcher(nullptr)
, m_platformImpl(new PlatformImplementation())
{
}
FolderRootWatch::~FolderRootWatch()
{
// Destructor is required in here since this file contains the definition of struct PlatformImplementation
Stop();
delete m_platformImpl;
}
bool FolderRootWatch::Start()
{
m_shutdownThreadSignal = false;
CFStringRef rootPath = CFStringCreateWithCString(kCFAllocatorDefault, m_root.toStdString().data(), kCFStringEncodingMacRoman);
CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&rootPath, 1, NULL);
// The larger this number, the larger the delay between the kernel knowing a file changed
// and us actually consuming the event. It is very important for asset processor to deal with
// file changes as fast as possible, since we use file "fencing" to control network access
// For example, if someone asks (over the network) "does file xyz exist?" we actually put a random file on disk
// and only answer their query when we see that file appear on our file monitor, so that we know all other
// file creations/modifications/deletions have been seen before we answer their question.
// as such, having a slow response time here can cause a dramatic slowdown for all other operations.
CFAbsoluteTime timeBetweenKernelUpdateAndNotification = 0.001;
// Set ourselves as the value for the context info field so that in the callback
// we get passed into it and the callback can call our public API to handle
// the file change events
FSEventStreamContext streamContext;
::memset(&streamContext, 0, sizeof(streamContext));
streamContext.info = this;
m_platformImpl->m_stream = FSEventStreamCreate(NULL,
FileEventStreamCallback,
&streamContext,
pathsToWatch,
kFSEventStreamEventIdSinceNow,
timeBetweenKernelUpdateAndNotification,
kFSEventStreamCreateFlagFileEvents);
AZ_Error("FileWatcher", (m_platformImpl->m_stream != nullptr), "FSEventStreamCreate returned a nullptr. No file events will be reported for %s", m_root.toStdString().c_str());
m_thread = std::thread(std::bind(&FolderRootWatch::WatchFolderLoop, this));
CFRelease(pathsToWatch);
CFRelease(rootPath);
return (m_platformImpl->m_stream != nullptr);
}
void FolderRootWatch::Stop()
{
m_shutdownThreadSignal = true;
if (m_thread.joinable())
{
m_thread.join(); // wait for the thread to finish
m_thread = std::thread(); //destroy
}
FSEventStreamStop(m_platformImpl->m_stream);
FSEventStreamInvalidate(m_platformImpl->m_stream);
FSEventStreamRelease(m_platformImpl->m_stream);
}
void FolderRootWatch::WatchFolderLoop()
{
// Use a half second timeout interval so that we can check if
// m_shutdownThreadSignal has been changed while we were running the RunLoop
static const CFTimeInterval secondsToProcess = 0.5;
m_platformImpl->m_runLoop = CFRunLoopGetCurrent();
FSEventStreamScheduleWithRunLoop(m_platformImpl->m_stream, m_platformImpl->m_runLoop, kCFRunLoopDefaultMode);
FSEventStreamStart(m_platformImpl->m_stream);
const bool returnAfterFirstEventHandled = false;
while (!m_shutdownThreadSignal)
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, secondsToProcess, returnAfterFirstEventHandled);
}
}
void FileEventStreamCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[])
{
FolderRootWatch* watcher = reinterpret_cast<FolderRootWatch*>(clientCallBackInfo);
const char** filePaths = reinterpret_cast<const char**>(eventPaths);
for (int i = 0; i < numEvents; ++i)
{
QFileInfo fileInfo(QDir::cleanPath(filePaths[i]));
QString fileAndPath = fileInfo.absoluteFilePath();
if (!fileInfo.isHidden())
{
// Some events will be aggreated into one so it is possible we will get
// multiple event flags set for a single file (create/modify as an example)
// so check for all of them
if (eventFlags[i] & kFSEventStreamEventFlagItemCreated)
{
watcher->ProcessNewFileEvent(fileAndPath);
}
if (eventFlags[i] & kFSEventStreamEventFlagItemModified)
{
watcher->ProcessModifyFileEvent(fileAndPath);
}
if (eventFlags[i] & kFSEventStreamEventFlagItemRemoved)
{
watcher->ProcessDeleteFileEvent(fileAndPath);
}
if (eventFlags[i] & kFSEventStreamEventFlagItemRenamed)
{
if (fileInfo.exists())
{
watcher->ProcessNewFileEvent(fileAndPath);
// macOS does not send out an event for the directory being
// modified when a file has been renamed but the FileWatcher
// API expects it so send out the modification event ourselves.
watcher->ProcessModifyFileEvent(fileInfo.absolutePath());
}
else
{
watcher->ProcessDeleteFileEvent(fileAndPath);
// macOS does not send out an event for the directory being
// modified when a file has been renamed but the FileWatcher
// API expects it so send out the modification event ourselves.
watcher->ProcessModifyFileEvent(fileInfo.absolutePath());
}
}
}
}
}
@@ -0,0 +1,134 @@
/*
* 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/FileWatcher/FileWatcher.h>
#include <AzCore/PlatformIncl.h>
struct FolderRootWatch::PlatformImplementation
{
PlatformImplementation() : m_directoryHandle(nullptr), m_ioHandle(nullptr) { }
HANDLE m_directoryHandle;
HANDLE m_ioHandle;
};
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
FolderRootWatch::FolderRootWatch(const QString rootFolder)
: m_root(rootFolder)
, m_shutdownThreadSignal(false)
, m_fileWatcher(nullptr)
, m_platformImpl(new PlatformImplementation())
{
}
FolderRootWatch::~FolderRootWatch()
{
// Destructor is required in here since this file contains the definition of struct PlatformImplementation
Stop();
delete m_platformImpl;
}
bool FolderRootWatch::Start()
{
m_platformImpl->m_directoryHandle = ::CreateFileW(m_root.toStdWString().data(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
if (m_platformImpl->m_directoryHandle != INVALID_HANDLE_VALUE)
{
m_platformImpl->m_ioHandle = ::CreateIoCompletionPort(m_platformImpl->m_directoryHandle, nullptr, 1, 0);
if (m_platformImpl->m_ioHandle != INVALID_HANDLE_VALUE)
{
m_shutdownThreadSignal = false;
m_thread = std::thread(std::bind(&FolderRootWatch::WatchFolderLoop, this));
return true;
}
}
return false;
}
void FolderRootWatch::Stop()
{
m_shutdownThreadSignal = true;
CloseHandle(m_platformImpl->m_ioHandle);
m_platformImpl->m_ioHandle = nullptr;
if (m_thread.joinable())
{
m_thread.join(); // wait for the thread to finish
m_thread = std::thread(); //destroy
}
CloseHandle(m_platformImpl->m_directoryHandle);
m_platformImpl->m_directoryHandle = nullptr;
}
void FolderRootWatch::WatchFolderLoop()
{
FILE_NOTIFY_INFORMATION aFileNotifyInformationList[50000];
QString path;
OVERLAPPED aOverlapped;
LPOVERLAPPED pOverlapped;
DWORD dwByteCount;
ULONG_PTR ulKey;
while (!m_shutdownThreadSignal)
{
::memset(aFileNotifyInformationList, 0, sizeof(aFileNotifyInformationList));
::memset(&aOverlapped, 0, sizeof(aOverlapped));
if (::ReadDirectoryChangesW(m_platformImpl->m_directoryHandle, aFileNotifyInformationList, sizeof(aFileNotifyInformationList), true, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_FILE_NAME, nullptr, &aOverlapped, nullptr))
{
//wait for up to a second for I/O to signal
dwByteCount = 0;
if (::GetQueuedCompletionStatus(m_platformImpl->m_ioHandle, &dwByteCount, &ulKey, &pOverlapped, INFINITE))
{
//if we are signaled to shutdown bypass
if (!m_shutdownThreadSignal && ulKey)
{
if (dwByteCount)
{
int offset = 0;
FILE_NOTIFY_INFORMATION* pFileNotifyInformation = aFileNotifyInformationList;
do
{
pFileNotifyInformation = (FILE_NOTIFY_INFORMATION*)((char*)pFileNotifyInformation + offset);
path.clear();
path.append(m_root);
path.append(QString::fromWCharArray(pFileNotifyInformation->FileName, pFileNotifyInformation->FileNameLength / 2));
QString file = QDir::toNativeSeparators(QDir::cleanPath(path));
switch (pFileNotifyInformation->Action)
{
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME:
ProcessNewFileEvent(file);
break;
case FILE_ACTION_REMOVED:
case FILE_ACTION_RENAMED_OLD_NAME:
ProcessDeleteFileEvent(file);
break;
case FILE_ACTION_MODIFIED:
ProcessModifyFileEvent(file);
break;
}
offset = pFileNotifyInformation->NextEntryOffset;
} while (offset);
}
}
}
}
}
}
@@ -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.
*
*/
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Editor.rc
//
#define IDI_ICON1 2
#define IDC_STATIC -1
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 3
#define _APS_NEXT_COMMAND_VALUE 32769
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 110
#endif
#endif
@@ -0,0 +1,38 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* 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 MACDOCKICONHANDLER_H
#define MACDOCKICONHANDLER_H
#include <QObject>
#ifdef __OBJC__
@class DockIconClickEventHandler;
#else
class DockIconClickEventHandler;
#endif
class MacDockIconHandler : public QObject
{
Q_OBJECT
public:
MacDockIconHandler(QObject* parent = nullptr);
~MacDockIconHandler();
Q_SIGNALS:
void dockIconClicked();
private:
DockIconClickEventHandler* m_dockIconClickEventHandler;
};
#endif // MACDOCKICONCLICKHANDLER_H
@@ -0,0 +1,71 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* 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 <Cocoa/Cocoa.h>
#include <native/utilities/MacDockIconHandler.h>
#include <QTimer>
@interface DockIconClickEventHandler : NSObject
{
MacDockIconHandler* dockIconHandler;
}
@end
@implementation DockIconClickEventHandler
- (id)initWithDockIconHandler:(MacDockIconHandler *)aDockIconHandler
{
self = [super init];
if (self)
{
dockIconHandler = aDockIconHandler;
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:self
andSelector:@selector(handleDockClickEvent:withReplyEvent:)
forEventClass:kCoreEventClass
andEventID:kAEReopenApplication];
}
return self;
}
- (void)handleDockClickEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
Q_UNUSED(event)
Q_UNUSED(replyEvent)
dockIconHandler->dockIconClicked();
}
@end
MacDockIconHandler::MacDockIconHandler(QObject* parent)
: QObject(parent)
{
// this has to be delayed, since Qt installs the same handler
// using 0 is timeout is not enough.
QTimer* t = new QTimer;
t->setSingleShot(true);
t->start(1);
connect(t, &QTimer::timeout, this, [t, this]()
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
m_dockIconClickEventHandler = [[DockIconClickEventHandler alloc] initWithDockIconHandler:this];
[pool release];
t->deleteLater();
});
}
MacDockIconHandler::~MacDockIconHandler()
{
[m_dockIconClickEventHandler release];
}
#include <native/utilities/moc_MacDockIconHandler.cpp>
@@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "../../native/ui/style/lyassetprocessor.ico"
@@ -0,0 +1,14 @@
/*
* 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 <AssetProcessor_Traits_Windows.h>
@@ -0,0 +1,15 @@
/*
* 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
#define ASSETPROCESSOR_TRAIT_LEGACY_RC_RELATIVE_PATH "/rc.exe"
#define ASSETPROCESSOR_TRAIT_CASE_SENSITIVE_FILESYSTEM false
@@ -0,0 +1,13 @@
#
# 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.
#
set (PAL_TRAIT_BUILD_ASSETPROCESSOR_APPLICATION_TYPE APPLICATION)
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
AssetProcessor.rc
)
@@ -0,0 +1,11 @@
#
# 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.
#
@@ -0,0 +1,15 @@
#
# 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.
#
set(FILES
native/FileWatcher/FileWatcher_win.cpp
native/resource.h
)
@@ -0,0 +1,134 @@
/*
* 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/FileWatcher/FileWatcher.h>
#include <AzCore/PlatformIncl.h>
struct FolderRootWatch::PlatformImplementation
{
PlatformImplementation() : m_directoryHandle(nullptr), m_ioHandle(nullptr) { }
HANDLE m_directoryHandle;
HANDLE m_ioHandle;
};
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
FolderRootWatch::FolderRootWatch(const QString rootFolder)
: m_root(rootFolder)
, m_shutdownThreadSignal(false)
, m_fileWatcher(nullptr)
, m_platformImpl(new PlatformImplementation())
{
}
FolderRootWatch::~FolderRootWatch()
{
// Destructor is required in here since this file contains the definition of struct PlatformImplementation
Stop();
delete m_platformImpl;
}
bool FolderRootWatch::Start()
{
m_platformImpl->m_directoryHandle = ::CreateFileW(m_root.toStdWString().data(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
if (m_platformImpl->m_directoryHandle != INVALID_HANDLE_VALUE)
{
m_platformImpl->m_ioHandle = ::CreateIoCompletionPort(m_platformImpl->m_directoryHandle, nullptr, 1, 0);
if (m_platformImpl->m_ioHandle != INVALID_HANDLE_VALUE)
{
m_shutdownThreadSignal = false;
m_thread = std::thread(std::bind(&FolderRootWatch::WatchFolderLoop, this));
return true;
}
}
return false;
}
void FolderRootWatch::Stop()
{
m_shutdownThreadSignal = true;
CloseHandle(m_platformImpl->m_ioHandle);
m_platformImpl->m_ioHandle = nullptr;
if (m_thread.joinable())
{
m_thread.join(); // wait for the thread to finish
m_thread = std::thread(); //destroy
}
CloseHandle(m_platformImpl->m_directoryHandle);
m_platformImpl->m_directoryHandle = nullptr;
}
void FolderRootWatch::WatchFolderLoop()
{
FILE_NOTIFY_INFORMATION aFileNotifyInformationList[50000];
QString path;
OVERLAPPED aOverlapped;
LPOVERLAPPED pOverlapped;
DWORD dwByteCount;
ULONG_PTR ulKey;
while (!m_shutdownThreadSignal)
{
::memset(aFileNotifyInformationList, 0, sizeof(aFileNotifyInformationList));
::memset(&aOverlapped, 0, sizeof(aOverlapped));
if (::ReadDirectoryChangesW(m_platformImpl->m_directoryHandle, aFileNotifyInformationList, sizeof(aFileNotifyInformationList), true, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_FILE_NAME, nullptr, &aOverlapped, nullptr))
{
//wait for up to a second for I/O to signal
dwByteCount = 0;
if (::GetQueuedCompletionStatus(m_platformImpl->m_ioHandle, &dwByteCount, &ulKey, &pOverlapped, INFINITE))
{
//if we are signaled to shutdown bypass
if (!m_shutdownThreadSignal && ulKey)
{
if (dwByteCount)
{
int offset = 0;
FILE_NOTIFY_INFORMATION* pFileNotifyInformation = aFileNotifyInformationList;
do
{
pFileNotifyInformation = (FILE_NOTIFY_INFORMATION*)((char*)pFileNotifyInformation + offset);
path.clear();
path.append(m_root);
path.append(QString::fromWCharArray(pFileNotifyInformation->FileName, pFileNotifyInformation->FileNameLength / 2));
QString file = QDir::toNativeSeparators(QDir::cleanPath(path));
switch (pFileNotifyInformation->Action)
{
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME:
ProcessNewFileEvent(file);
break;
case FILE_ACTION_REMOVED:
case FILE_ACTION_RENAMED_OLD_NAME:
ProcessDeleteFileEvent(file);
break;
case FILE_ACTION_MODIFIED:
ProcessModifyFileEvent(file);
break;
}
offset = pFileNotifyInformation->NextEntryOffset;
} while (offset);
}
}
}
}
}
}
@@ -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.
*
*/
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Editor.rc
//
#define IDI_ICON1 2
#define IDC_STATIC -1
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 3
#define _APS_NEXT_COMMAND_VALUE 32769
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 110
#endif
#endif
@@ -0,0 +1,15 @@
#
# 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.
#
set(FILES
native/main_batch.cpp
native/AssetProcessorBatchBuildTarget.cpp
)
@@ -0,0 +1,61 @@
#
# 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.
#
set(FILES
native/AssetProcessorBuildTarget.cpp
native/FileServer/fileServer.cpp
native/FileServer/fileServer.h
native/ui/style/AssetProcessor.qrc
native/ui/style/AssetProcessor.qss
native/ui/style/AssetProcessorConfig.ini
native/ui/style/AssetsTab.qss
native/ui/style/LogsTab.qss
native/ui/AssetDetailsPanel.h
native/ui/AssetDetailsPanel.cpp
native/ui/AssetTreeFilterModel.h
native/ui/AssetTreeFilterModel.cpp
native/ui/AssetTreeModel.h
native/ui/AssetTreeModel.cpp
native/ui/AssetTreeItem.h
native/ui/AssetTreeItem.cpp
native/ui/ConnectionEditDialog.h
native/ui/ConnectionEditDialog.cpp
native/ui/GoToButton.h
native/ui/GoToButton.cpp
native/ui/GoToButton.ui
native/ui/MainWindow.h
native/ui/MainWindow.cpp
native/ui/MainWindow.ui
native/ui/ProductAssetDetailsPanel.h
native/ui/ProductAssetDetailsPanel.cpp
native/ui/ProductAssetDetailsPanel.ui
native/ui/ProductAssetTreeItemData.h
native/ui/ProductAssetTreeItemData.cpp
native/ui/ProductAssetTreeModel.h
native/ui/ProductAssetTreeModel.cpp
native/ui/SourceAssetDetailsPanel.h
native/ui/SourceAssetDetailsPanel.cpp
native/ui/SourceAssetDetailsPanel.ui
native/ui/SourceAssetTreeItemData.h
native/ui/SourceAssetTreeItemData.cpp
native/ui/SourceAssetTreeModel.h
native/ui/SourceAssetTreeModel.cpp
native/utilities/GUIApplicationServer.cpp
native/utilities/GUIApplicationServer.h
native/utilities/GUIApplicationManager.cpp
native/utilities/GUIApplicationManager.h
native/utilities/windowscreen.cpp
native/utilities/windowscreen.h
native/utilities/AssetUtilEBusHelper.h
native/utilities/LogPanel.h
native/utilities/LogPanel.cpp
native/main_gui.cpp
)
@@ -0,0 +1,18 @@
#
# 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.
#
set(FILES
native/AssetProcessorBatchBuildTarget.cpp
native/utilities/BatchApplicationManager.cpp
native/utilities/BatchApplicationManager.h
native/utilities/BatchApplicationServer.cpp
native/utilities/BatchApplicationServer.h
)
@@ -0,0 +1,114 @@
#
# 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.
#
set(FILES
native/AssetDatabase/AssetDatabase.cpp
native/AssetDatabase/AssetDatabase.h
native/AssetManager/AssetCatalog.cpp
native/AssetManager/AssetCatalog.h
native/AssetManager/assetProcessorManager.cpp
native/AssetManager/assetProcessorManager.h
native/AssetManager/AssetRequestHandler.cpp
native/AssetManager/AssetRequestHandler.h
native/AssetManager/assetScanFolderInfo.h
native/AssetManager/assetScanner.cpp
native/AssetManager/assetScanner.h
native/AssetManager/assetScannerWorker.cpp
native/AssetManager/assetScannerWorker.h
native/AssetManager/FileStateCache.cpp
native/AssetManager/FileStateCache.h
native/AssetManager/PathDependencyManager.cpp
native/AssetManager/PathDependencyManager.h
native/AssetManager/SourceFileRelocator.cpp
native/AssetManager/SourceFileRelocator.h
native/AssetManager/ControlRequestHandler.cpp
native/AssetManager/ControlRequestHandler.h
native/assetprocessor.h
native/connection/connection.cpp
native/connection/connection.h
native/connection/connectionManager.cpp
native/connection/connectionManager.h
native/connection/connectionMessages.h
native/connection/connectionworker.cpp
native/connection/connectionworker.h
native/FileProcessor/FileProcessor.cpp
native/FileProcessor/FileProcessor.h
native/FileWatcher/FileWatcher.cpp
native/FileWatcher/FileWatcher.h
native/FileWatcher/FileWatcherAPI.h
native/InternalBuilders/SettingsRegistryBuilder.cpp
native/InternalBuilders/SettingsRegistryBuilder.h
native/resourcecompiler/JobsModel.cpp
native/resourcecompiler/JobsModel.h
native/resourcecompiler/RCBuilder.cpp
native/resourcecompiler/RCBuilder.h
native/resourcecompiler/RCCommon.cpp
native/resourcecompiler/RCCommon.h
native/resourcecompiler/rccontroller.cpp
native/resourcecompiler/rccontroller.h
native/resourcecompiler/rcjob.cpp
native/resourcecompiler/rcjob.h
native/resourcecompiler/rcjoblistmodel.cpp
native/resourcecompiler/rcjoblistmodel.h
native/resourcecompiler/RCJobSortFilterProxyModel.cpp
native/resourcecompiler/RCJobSortFilterProxyModel.h
native/resourcecompiler/RCQueueSortModel.cpp
native/resourcecompiler/RCQueueSortModel.h
native/shadercompiler/shadercompilerjob.cpp
native/shadercompiler/shadercompilerjob.h
native/shadercompiler/shadercompilerManager.cpp
native/shadercompiler/shadercompilerManager.h
native/shadercompiler/shadercompilerMessages.h
native/shadercompiler/shadercompilerModel.cpp
native/shadercompiler/shadercompilerModel.h
native/utilities/ApplicationManagerAPI.h
native/utilities/ApplicationManager.cpp
native/utilities/ApplicationManager.h
native/utilities/ApplicationManagerBase.cpp
native/utilities/ApplicationManagerBase.h
native/utilities/ApplicationServer.cpp
native/utilities/ApplicationServer.h
native/utilities/AssetBuilderInfo.cpp
native/utilities/AssetBuilderInfo.h
native/utilities/AssetServerHandler.cpp
native/utilities/AssetServerHandler.h
native/utilities/AssetUtilEBusHelper.h
native/utilities/assetUtils.cpp
native/utilities/assetUtils.h
native/utilities/BuilderConfigurationBus.h
native/utilities/BuilderConfigurationManager.cpp
native/utilities/BuilderConfigurationManager.h
native/utilities/BuilderManager.cpp
native/utilities/BuilderManager.h
native/utilities/BuilderManager.inl
native/utilities/ByteArrayStream.cpp
native/utilities/ByteArrayStream.h
native/utilities/CommunicatorTracePrinter.cpp
native/utilities/CommunicatorTracePrinter.h
native/utilities/IniConfiguration.cpp
native/utilities/IniConfiguration.h
native/utilities/JobDiagnosticTracker.cpp
native/utilities/JobDiagnosticTracker.h
native/utilities/LineByLineDependencyScanner.cpp
native/utilities/LineByLineDependencyScanner.h
native/utilities/MissingDependencyScanner.cpp
native/utilities/MissingDependencyScanner.h
native/utilities/PlatformConfiguration.cpp
native/utilities/PlatformConfiguration.h
native/utilities/PotentialDependencies.h
native/utilities/SpecializedDependencyScanner.h
native/utilities/ThreadHelper.cpp
native/utilities/ThreadHelper.h
)
set(SKIP_UNITY_BUILD_INCLUSION_FILES
native/resourcecompiler/JobsModel.cpp
)
@@ -0,0 +1,91 @@
#
# 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.
#
set(FILES
testdata/unittests.qrc
testdata/config_broken_badplatform/AssetProcessorPlatformConfig.ini
testdata/config_broken_noplatform/AssetProcessorPlatformConfig.ini
testdata/config_broken_noscans/AssetProcessorPlatformConfig.ini
testdata/config_broken_recognizers/AssetProcessorPlatformConfig.ini
testdata/config_regular/AssetProcessorPlatformConfig.ini
testdata/config_regular_platform_scanfolder/AssetProcessorPlatformConfig.ini
testdata/EmptyDummyProject/AssetProcessorGamePlatformConfig.ini
testdata/DummyProject/AssetProcessorGamePlatformConfig.ini
native/tests/AssetProcessorTest.h
native/tests/AssetProcessorTest.cpp
native/tests/BaseAssetProcessorTest.h
native/tests/assetdatabase/AssetDatabaseTest.cpp
native/tests/resourcecompiler/RCBuilderTest.cpp
native/tests/resourcecompiler/RCBuilderTest.h
native/tests/resourcecompiler/RCControllerTest.cpp
native/tests/resourcecompiler/RCControllerTest.h
native/tests/resourcecompiler/RCJobTest.cpp
native/tests/resourcecompiler/RCJobTest.h
native/tests/assetBuilderSDK/assetBuilderSDKTest.h
native/tests/assetBuilderSDK/assetBuilderSDKTest.cpp
native/tests/assetBuilderSDK/SerializationDependenciesTests.cpp
native/tests/assetmanager/AssetProcessorManagerTest.cpp
native/tests/assetmanager/AssetProcessorManagerTest.h
native/tests/utilities/assetUtilsTest.cpp
native/tests/platformconfiguration/platformconfigurationtests.cpp
native/tests/platformconfiguration/platformconfigurationtests.h
native/tests/utilities/JobModelTest.cpp
native/tests/utilities/JobModelTest.h
native/tests/AssetCatalog/AssetCatalogUnitTests.cpp
native/tests/assetscanner/AssetScannerTests.h
native/tests/assetscanner/AssetScannerTests.cpp
native/tests/BuilderConfiguration/BuilderConfigurationTests.cpp
native/tests/FileProcessor/FileProcessorTests.h
native/tests/FileProcessor/FileProcessorTests.cpp
native/tests/FileStateCache/FileStateCacheTests.h
native/tests/FileStateCache/FileStateCacheTests.cpp
native/tests/InternalBuilders/SettingsRegistryBuilderTests.cpp
native/tests/MissingDependencyScannerTests.cpp
native/tests/SourceFileRelocatorTests.cpp
native/tests/AssetProcessorMessagesTests.cpp
native/unittests/AssetProcessingStateDataUnitTests.cpp
native/unittests/AssetProcessingStateDataUnitTests.h
native/unittests/AssetProcessorManagerUnitTests.cpp
native/unittests/AssetProcessorManagerUnitTests.h
native/unittests/AssetProcessorServerUnitTests.cpp
native/unittests/AssetProcessorServerUnitTests.h
native/unittests/AssetScannerUnitTests.cpp
native/unittests/AssetScannerUnitTests.h
native/unittests/ConnectionUnitTests.cpp
native/unittests/ConnectionUnitTests.h
native/unittests/ConnectionManagerUnitTests.cpp
native/unittests/ConnectionManagerUnitTests.h
native/unittests/FileWatcherUnitTests.cpp
native/unittests/FileWatcherUnitTests.h
native/unittests/PlatformConfigurationUnitTests.cpp
native/unittests/PlatformConfigurationUnitTests.h
native/unittests/RCcontrollerUnitTests.cpp
native/unittests/RCcontrollerUnitTests.h
native/unittests/ShaderCompilerUnitTests.cpp
native/unittests/ShaderCompilerUnitTests.h
native/unittests/UnitTestRunner.cpp
native/unittests/UnitTestRunner.h
native/unittests/UtilitiesUnitTests.cpp
native/unittests/UtilitiesUnitTests.h
native/unittests/AssetRequestHandlerUnitTests.cpp
native/unittests/AssetRequestHandlerUnitTests.h
native/unittests/MockConnectionHandler.h
native/unittests/MockApplicationManager.cpp
native/unittests/MockApplicationManager.h
native/unittests/BuilderSDKUnitTests.cpp
native/utilities/UnitTestShaderCompilerServer.cpp
native/utilities/UnitTestShaderCompilerServer.h
native/tests/test_main.cpp
)
set(SKIP_UNITY_BUILD_INCLUSION_FILES
native/tests/utilities/JobModelTest.cpp
)
+27
View File
@@ -0,0 +1,27 @@
android-no-sdk {
target.path = /data/user/qt
export(target.path)
INSTALLS += target
} else:android {
x86 {
target.path = /libs/x86
} else: armeabi-v7a {
target.path = /libs/armeabi-v7a
} else {
target.path = /libs/armeabi
}
export(target.path)
INSTALLS += target
} else:unix {
isEmpty(target.path) {
qnx {
target.path = /tmp/$${TARGET}/bin
} else {
target.path = /opt/$${TARGET}/bin
}
export(target.path)
}
INSTALLS += target
}
export(INSTALLS)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,236 @@
#ifndef ASSETPROCESSOR_ASSETDATABASE_H
#define ASSETPROCESSOR_ASSETDATABASE_H
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
#include <QtCore/QSet>
#include <QtCore/QString>
#include "AzToolsFramework/API/EditorAssetSystemAPI.h"
class QStringList;
namespace AssetProcessor
{
//! the Asset Processor's database manager's job is to create and modify the actual underlying
//! SQL database. All queries to make changes to the database go through here. This includes
//! connecting to existing database and altering or creating database tables, etc.
class AssetDatabaseConnection
: public AzToolsFramework::AssetDatabase::AssetDatabaseConnection
{
public:
AZ_CLASS_ALLOCATOR(AssetDatabaseConnection, AZ::SystemAllocator, 0);
AssetDatabaseConnection();
~AssetDatabaseConnection();
//////////////////////////////////////////////////////////////////////////
// AzToolsFramework::AssetDatabase::Connection
public:
bool IsReadOnly() const override
{
return false;// return false, we actually curate/write to this database.
}
void VacuumAndAnalyze();
protected:
void CreateStatements() override;
bool PostOpenDatabase() override;
//////////////////////////////////////////////////////////////////////////
public:
bool DataExists();
void LoadData();
void ClearData();
//////////////////////////////////////////////////////////////////////////
//Queries
//NOTE: When passing in a structure to the Set<> functions, a default constructed structure has -1 for
//the table generated id and is filled in by the query, that is why it is passed by non const reference.
//For instance when SetSource(scanFolderEntry); is called it evaluates the query and fills in the
//main m_sourceID from the query. If you pass in a structure with the id already filled in, i.e. not -1,
//it is interpreted as an update to the database only if the contents differ in any way from whats
//already in he database. Obviously if it is filled in and does not exist it returns false.
//NOTE: The return bool for these queries only return true if both the query succeeded AND you got a result
//////////////////////////////////////////////////////////////////////////
//scan folders
bool GetScanFolders(AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntryContainer& container);
bool GetScanFolderByScanFolderID(AZ::s64 scanFolderID, AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& entry);
bool GetScanFolderBySourceID(AZ::s64 sourceID, AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& entry);
bool GetScanFolderByJobID(AZ::s64 jobID, AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& entry);
bool GetScanFolderByProductID(AZ::s64 productID, AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& entry);
bool GetScanFolderByPortableKey(QString portableKey, AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& entry);
bool SetScanFolder(AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& entry); //on success sets scanfolderID, if already exists updates it
bool RemoveScanFolder(AZ::s64 scanFolderID);
bool RemoveScanFolders(AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntryContainer& container);
//sources
bool GetSources(AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer& container);
bool GetSourceBySourceGuid(AZ::Uuid sourceGuid, AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry);
bool GetSourceBySourceID(AZ::s64 sourceID, AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry);
bool GetSourceBySourceName(QString exactSourceName, AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry);
bool GetSourcesBySourceName(QString exactSourceName, AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer& source);
bool GetSourcesBySourceNameScanFolderId(QString exactSourceName, AZ::s64 scanFolderID, AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer& source);
bool GetSourcesLikeSourceName(QString likeSourceName, LikeType likeType, AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer& container);
bool GetSourceByJobID(AZ::s64 jobID, AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry);
bool GetSourceByProductID(AZ::s64 productID, AzToolsFramework::AssetDatabase::SourceDatabaseEntry& source);
bool GetSourcesByProductName(QString exactProductName, AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer& container);
bool GetSourcesLikeProductName(QString likeProductName, LikeType likeType, AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer& container);
bool SetSource(AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry); //on success sets sourceID, if it already exists updates it
bool RemoveSource(AZ::s64 sourceID);
bool RemoveSources(AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer& container);
bool RemoveSourcesByScanFolderID(AZ::s64 scanFolderID);
bool InvalidateSourceAnalysisFingerprints();
//jobs
// used to initialize the predictor for job Run Keys
AZ::s64 GetHighestJobRunKey();
bool GetJobs(AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool GetJobByJobID(AZ::s64 jobID, AzToolsFramework::AssetDatabase::JobDatabaseEntry& entry);
bool GetJobByProductID(AZ::s64 productID, AzToolsFramework::AssetDatabase::JobDatabaseEntry& entry);
bool GetJobsBySourceID(AZ::s64 sourceID, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool GetJobsBySourceName(QString exactSourceName, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool GetJobsLikeSourceName(QString likeSourceName, LikeType likeType, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool GetJobsByProductName(QString exactProductName, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool GetJobsLikeProductName(QString likeProductName, LikeType likeType, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool SetJob(AzToolsFramework::AssetDatabase::JobDatabaseEntry& entry); //on success sets jobID, if it already exists updates it
bool RemoveJob(AZ::s64 jobID);
bool RemoveJobs(AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container);
bool RemoveJobByProductID(AZ::s64 productID);
//products
bool GetProducts(AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool GetProductsByJobID(AZ::s64 jobID, AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container);
// note that the pair of (JobID, SubID) uniquely identifies a single job, and thus the result is always only one entry:
bool GetProductByJobIDSubId(AZ::s64 jobID, AZ::u32 subID, AzToolsFramework::AssetDatabase::ProductDatabaseEntry& result);
bool GetProductBySourceGuidSubId(AZ::Uuid sourceGuid, AZ::u32 subId, AzToolsFramework::AssetDatabase::ProductDatabaseEntry& result);
bool GetProductByProductID(AZ::s64 productID, AzToolsFramework::AssetDatabase::ProductDatabaseEntry& entry);
bool GetProductsByProductName(QString exactProductName, AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool GetProductsLikeProductName(QString likeProductName, LikeType likeType, AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool GetProductsBySourceID(AZ::s64 sourceID, AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool GetProductsBySourceName(QString exactSourceName, AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool GetProductsLikeSourceName(QString likeSourceName, LikeType likeType, AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
bool SetProduct(AzToolsFramework::AssetDatabase::ProductDatabaseEntry& entry); //on success sets productID, if it already exists updates it
bool SetProducts(AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container); //on success sets productID, if it already exists updates it
bool RemoveProducts(AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container);
bool RemoveProduct(AZ::s64 productID);
bool RemoveProductsByJobID(AZ::s64 jobID);
bool RemoveProductsBySourceID(AZ::s64 sourceID, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
//jobinfo
bool GetJobInfoByJobID(AZ::s64 jobID, AzToolsFramework::AssetSystem::JobInfo& jobInfo);
bool GetJobInfoByJobKey(AZStd::string jobKey, AzToolsFramework::AssetSystem::JobInfoContainer& container);
bool GetJobInfoByJobRunKey(AZ::u64 jobRunKey, AzToolsFramework::AssetSystem::JobInfoContainer& container);
bool GetJobInfoBySourceName(QString exactSourceName, AzToolsFramework::AssetSystem::JobInfoContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
/* --------------------- Source Dependency Table -------------------
* For example, this table records when a source file depends on another source file either directly (DEP_SourceToSource)
* but also whether then source file depends on another source file indirectly because it depends on a job which processes
* that source file
*/
/// Set a row in the table. It is invalid to overwrite existing rows without removing them first.
bool SetSourceFileDependency(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry& entry);
/// Set a batch of rows. It is invalid to overwrite existing rows, so consider using RemoveSourceFileDependencies first.
bool SetSourceFileDependencies(AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer& container);
/// Remove a dependency, given a row ID
bool RemoveSourceFileDependency(AZ::s64 sourceFileDependencyId);
/// Batch remove a bunch of rows by container
bool RemoveSourceFileDependencies(const AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer& container);
/// Batch remove a bunch of rows by IDs
bool RemoveSourceFileDependencies(const AZStd::unordered_set<AZ::s64>& container);
/// Direct retrieval by ID (does not use any filtering)
bool GetSourceFileDependencyBySourceDependencyId(AZ::s64 sourceDependencyId, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry& sourceDependencyEntry);
// The following functions are all search functions (as opposed to the above functions which fetch or operate on specific rows)
// They tend to take a "Type of Dependency" filter - you can use DEP_Any to query all kinds of dependencies.
/// Given a source file, what does it DEPEND ON?
bool GetDependsOnSourceBySource(const char* source, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::TypeOfDependency typeOfDependency, AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer& container);
/// Given a source file and a builder UUID, does it DEPEND ON?
bool GetSourceFileDependenciesByBuilderGUIDAndSource(const AZ::Uuid& builderGuid, const char* source, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::TypeOfDependency typeOfDependency, AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer& container);
/// Given a source file, what depends ON IT? ('reverse dependency')
bool GetSourceFileDependenciesByDependsOnSource(const QString& dependsOnSource, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::TypeOfDependency typeOfDependency, AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer& container);
// --------------------- Legacy SUBID table -------------------
bool CreateOrUpdateLegacySubID(AzToolsFramework::AssetDatabase::LegacySubIDsEntry& entry); // create or overwrite operation.
bool RemoveLegacySubID(AZ::s64 legacySubIDsEntryID);
bool RemoveLegacySubIDsByProductID(AZ::s64 productID);
//ProductDependencies
bool GetProductDependencies(AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& container);
bool GetProductDependencyByProductDependencyID(AZ::s64 productDependencyID, AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& productDependencyEntry);
bool GetProductDependenciesByProductID(AZ::s64 productID, AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& container);
bool GetDirectProductDependencies(AZ::s64 productID, AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container);
bool GetDirectReverseProductDependenciesBySourceGuidSubId(AZ::Uuid dependencySourceGuid, AZ::u32 dependencySubId, AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container);
bool GetAllProductDependencies(AZ::s64 productID, AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& container);
bool GetUnresolvedProductDependencies(AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& container);
bool SetProductDependency(AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry);
// Missing product dependencies
bool SetMissingProductDependency(AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry& entry);
bool GetMissingProductDependenciesByProductId(AZ::s64 productId, AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntryContainer& container);
bool GetMissingProductDependencyByMissingProductDependencyId(AZ::s64 missingProductDependencyId, AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry& missingProductDependencyEntry);
// updates or inserts multiple dependencies in a single transaction. Unlike SetProductDependencies, this does *not* delete existing dependencies
bool UpdateProductDependencies(AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& container);
// bulk inserts are lighter weight and don't change the input data. Note that this also deletes old dependencies for the products mentioned in the container.
bool SetProductDependencies(const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& container);
bool RemoveProductDependencyByProductId(AZ::s64 productID);
// bulk replace builder info table with new builder info table. Replaces the existing table of data.
// Note: newEntries will have their m_builderInfoID member set to their inserted rowId if this call succeeds.
bool SetBuilderInfoTable(AzToolsFramework::AssetDatabase::BuilderInfoEntryContainer& newEntries);
//Files
bool GetFileByFileID(AZ::s64 fileID, AzToolsFramework::AssetDatabase::FileDatabaseEntry& entry);
bool GetFileByFileNameAndScanFolderId(QString fileName, AZ::s64 scanFolderId, AzToolsFramework::AssetDatabase::FileDatabaseEntry& entry);
bool GetFilesLikeFileName(QString likeFileName, LikeType likeType, AzToolsFramework::AssetDatabase::FileDatabaseEntryContainer& container);
bool InsertFiles(AzToolsFramework::AssetDatabase::FileDatabaseEntryContainer& entry);
bool InsertFile(AzToolsFramework::AssetDatabase::FileDatabaseEntry& entry, bool& entryAlreadyExists);
bool UpdateFile(AzToolsFramework::AssetDatabase::FileDatabaseEntry& entry, bool& entryAlreadyExists);
// updates the modtime and hash for a file if it exists. Only returns true if the row existed and was successfully updated
bool UpdateFileModTimeAndHashByFileNameAndScanFolderId(QString fileName, AZ::s64 scanFolderId, AZ::u64 modTime, AZ::u64 hash);
bool RemoveFile(AZ::s64 sourceID);
protected:
void SetDatabaseVersion(AzToolsFramework::AssetDatabase::DatabaseVersion ver);
void ExecuteCreateStatements();
private:
AZStd::vector<AZStd::string> m_createStatements; // contains all statements required to create the tables
};
}//namespace EditorFramework
#endif // ASSETPROCESSOR_ASSETDATABASE_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,220 @@
/*
* 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/smart_ptr/unique_ptr.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <QObject>
#include <QString>
#include <QTimer>
#include <QStringList>
#include <QHash>
#include <QDir>
#include "native/AssetDatabase/AssetDatabase.h"
#include "native/assetprocessor.h"
#include "native/utilities/AssetUtilEBusHelper.h"
#include "native/utilities/PlatformConfiguration.h"
#include <AzFramework/Asset/AssetRegistry.h>
#include <QMutex>
#include <QMultiMap>
#include <AzCore/IO/SystemFile.h>
#include <AzToolsFramework/ToolsComponents/ToolsAssetCatalogBus.h>
#endif
#include "AssetRequestHandler.h"
namespace AzFramework
{
class AssetRegistry;
namespace AssetSystem
{
class AssetNotificationMessage;
}
}
namespace AssetProcessor
{
class AssetDatabaseConnection;
class AssetCatalog
: public QObject
, private AssetRegistryRequestBus::Handler
, private AzToolsFramework::AssetSystemRequestBus::Handler
, private AzToolsFramework::ToolsAssetSystemBus::Handler
, private AZ::Data::AssetCatalogRequestBus::Handler
{
using NetworkRequestID = AssetProcessor::NetworkRequestID;
using BaseAssetProcessorMessage = AzFramework::AssetSystem::BaseAssetProcessorMessage;
Q_OBJECT;
public:
AssetCatalog(QObject* parent, AssetProcessor::PlatformConfiguration* platformConfiguration);
virtual ~AssetCatalog();
Q_SIGNALS:
// outgoing message to the network
void SendAssetMessage(AzFramework::AssetSystem::AssetNotificationMessage message);
void AsyncAssetCatalogStatusResponse(AssetCatalogStatus status);
public Q_SLOTS:
// incoming message from the AP
void OnAssetMessage(AzFramework::AssetSystem::AssetNotificationMessage message);
void OnDependencyResolved(const AZ::Data::AssetId& assetId, const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry);
void SaveRegistry_Impl();
virtual AzFramework::AssetSystem::GetUnresolvedDependencyCountsResponse HandleGetUnresolvedDependencyCountsRequest(MessageData<AzFramework::AssetSystem::GetUnresolvedDependencyCountsRequest> messageData);
virtual void HandleSaveAssetCatalogRequest(MessageData<AzFramework::AssetSystem::SaveAssetCatalogRequest> messageData);
void BuildRegistry();
void OnSourceQueued(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid, QString rootPath, QString relativeFilePath);
void OnSourceFinished(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid);
void AsyncAssetCatalogStatusRequest();
protected:
//////////////////////////////////////////////////////////////////////////
// AssetRegistryRequestBus::Handler overrides
int SaveRegistry() override;
void ValidatePreLoadDependency() override;
//////////////////////////////////////////////////////////////////////////
void RegistrySaveComplete(int assetCatalogVersion, bool allCatalogsSaved);
//////////////////////////////////////////////////////////////////////////
// AzToolsFramework::AssetSystem::AssetSystemRequestBus::Handler overrides
const char* GetAbsoluteDevGameFolderPath() override;
const char* GetAbsoluteDevRootFolderPath() override;
bool GetRelativeProductPathFromFullSourceOrProductPath(const AZStd::string& fullPath, AZStd::string& relativeProductPath) override;
bool GetFullSourcePathFromRelativeProductPath(const AZStd::string& relPath, AZStd::string& fullSourcePath) override;
bool GetAssetInfoById(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, AZStd::string& rootFilePath) override;
bool GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder) override;
bool GetSourceInfoBySourceUUID(const AZ::Uuid& sourceUuid, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder) override;
bool GetScanFolders(AZStd::vector<AZStd::string>& scanFolders) override;
bool GetAssetSafeFolders(AZStd::vector<AZStd::string>& assetSafeFolders) override;
bool IsAssetPlatformEnabled(const char* platform) override;
int GetPendingAssetsForPlatform(const char* platform) override;
bool GetAssetsProducedBySourceUUID(const AZ::Uuid& sourceUuid, AZStd::vector<AZ::Data::AssetInfo>& productsAssetInfo) override;
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// AssetCatalogRequestBus overrides
AZStd::string GetAssetPathById(const AZ::Data::AssetId& id) override;
AZ::Data::AssetId GetAssetIdByPath(const char* path, const AZ::Data::AssetType& typeToRegister, bool autoRegisterIfNotFound) override;
AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& id) override;
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> GetDirectProductDependencies(const AZ::Data::AssetId& id) override;
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> GetAllProductDependencies(const AZ::Data::AssetId& id) override;
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> GetLoadBehaviorProductDependencies(
const AZ::Data::AssetId& id, AZStd::unordered_set<AZ::Data::AssetId>& noloadSet,
AZ::Data::PreloadAssetListType& preloadAssetList) override;
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AzToolsFramework::ToolsAssetSystemBus::Handler
void RegisterSourceAssetType(const AZ::Data::AssetType& assetType, const char* assetFileFilter);
void UnregisterSourceAssetType(const AZ::Data::AssetType& assetType);
//////////////////////////////////////////////////////////////////////////
//! given some absolute path, please respond with its relative product path. For now, this will be a
//! string like 'textures/blah.tif' (we don't care about extensions), but eventually, this will
//! be an actual asset UUID.
void ProcessGetRelativeProductPathFromFullSourceOrProductPathRequest(const AZStd::string& fullPath, AZStd::string& relativeProductPath);
//! This function helps in determining the full product path of an relative product path.
//! In the future we will be sending an asset UUID to this function to request for full path.
void ProcessGetFullSourcePathFromRelativeProductPathRequest(const AZStd::string& relPath, AZStd::string& fullSourcePath);
//! Gets the source file info for an Asset by checking the DB first and the APM queue second
bool GetSourceFileInfoFromAssetId(const AZ::Data::AssetId &assetId, AZStd::string& watchFolder, AZStd::string& relativePath);
//! Gets the product AssetInfo based on a platform and assetId. If you specify a null or empty platform the current or first available will be used.
AZ::Data::AssetInfo GetProductAssetInfo(const char* platformName, const AZ::Data::AssetId& id);
//! GetAssetInfo that tries to figure out if the asset is a product or source so it can return info about the product or source respectively
bool GetAssetInfoByIdOnly(const AZ::Data::AssetId& id, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, AZStd::string& rootFilePath);
//! Checks in the currently-in-queue assets list for info on an asset (by source Id)
bool GetQueuedAssetInfoById(const AZ::Uuid& guid, AZStd::string& watchFolder, AZStd::string& relativePath);
//! Checks in the currently-in-queue assets list for info on an asset (by source name)
bool GetQueuedAssetInfoByRelativeSourceName(const char* sourceName, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder);
//! Gets the source info for a source that is not in the DB or APM queue
bool GetUncachedSourceInfoFromDatabaseNameAndWatchFolder(const char* sourceDatabasePath, const char* watchFolder, AZ::Data::AssetInfo& assetInfo);
bool ConnectToDatabase();
bool CheckValidatedAssets(AZ::Data::AssetId assetId, const QString& platform);
//! For lookups that don't provide a specific platform, provide a default platform to use.
QString GetDefaultAssetPlatform();
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> GetAllProductDependenciesFilter(
const AZ::Data::AssetId& id,
const AZStd::unordered_set<AZ::Data::AssetId>& exclusionList,
const AZStd::vector<AZStd::string>& wildcardPatternExclusionList);
bool DoesAssetIdMatchWildcardPattern(const AZ::Data::AssetId& assetId, const AZStd::string& wildcardPattern);
void AddAssetDependencies(
const AZ::Data::AssetId& searchAssetId,
AZStd::unordered_set<AZ::Data::AssetId>& assetSet,
AZStd::vector<AZ::Data::ProductDependency>& dependencyList,
const AZStd::unordered_set<AZ::Data::AssetId>& exclusionList,
const AZStd::vector<AZStd::string>& wildcardPatternExclusionList,
AZ::Data::PreloadAssetListType& preloadAssetList);
//! List of AssetTypes that should return info for the source instead of the product
AZStd::unordered_set<AZ::Data::AssetType> m_sourceAssetTypes;
AZStd::unordered_map<AZStd::string, AZ::Data::AssetType> m_sourceAssetTypeFilters;
AZStd::mutex m_sourceAssetTypesMutex;
//! Used to protect access to the database connection, only one thread can use it at a time
AZStd::mutex m_databaseMutex;
struct SourceInfo
{
QString m_watchFolder;
QString m_sourceName;
};
AZStd::mutex m_sourceUUIDToSourceNameMapMutex;
using SourceUUIDToSourceNameMap = AZStd::unordered_map<AZ::Uuid, SourceInfo>;
using SourceNameToSourceUuidMap = AZStd::unordered_map<AZStd::string, AZ::Uuid>;
SourceUUIDToSourceNameMap m_sourceUUIDToSourceNameMap; // map of uuids to source file names for assets that are currently in the processing queue
SourceNameToSourceUuidMap m_sourceNameToSourceUUIDMap;
QMutex m_registriesMutex;
QHash<QString, AzFramework::AssetRegistry> m_registries; // per platform.
AssetProcessor::PlatformConfiguration* m_platformConfig;
QStringList m_platforms;
AZStd::unique_ptr<AssetDatabaseConnection> m_db;
QDir m_cacheRoot;
bool m_registryBuiltOnce;
bool m_catalogIsDirty = true;
bool m_currentlySavingCatalog = false;
bool m_currentlyValidatingPreloadDependency = false;
int m_currentRegistrySaveVersion = 0;
QMutex m_savingRegistryMutex;
QMultiMap<int, AssetProcessor::NetworkRequestID> m_queuedSaveCatalogRequest;
AZStd::vector<AZStd::pair<AZ::Data::AssetId, QString>> m_preloadAssetList;
AZStd::unordered_multimap<AZ::Data::AssetId, QString> m_cachedNoPreloadDependenyAssetList;
AZStd::vector<char> m_saveBuffer; // so that we don't realloc all the time
char m_absoluteDevFolderPath[AZ_MAX_PATH_LEN];
char m_absoluteDevGameFolderPath[AZ_MAX_PATH_LEN];
QDir m_cacheRootDir;
};
}
@@ -0,0 +1,138 @@
/*
* 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_ASSETDATA_H
#define ASSETPROCESSOR_ASSETDATA_H
#include <QMetaType>
#include <QString>
#include <QSet>
#include <AzCore/Math/Uuid.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
namespace AssetProcessor
{
using namespace AzToolsFramework::AssetDatabase;
//Check the extension of all the products
//return true if any one of the product extension matches the input extension, else return false
bool CheckProductsExtension( const ProductDatabseEntryContainer& products, const char* ext );
//! this is the interface which we use to speak to the legacy database tables.
// its known as the legacy database interface because the forthcoming tables will completely replace these
// but this layer exits for compatibility with the previous version and allows us to upgrade in place.
class AssetDatabaseInterface
{
public:
AssetDatabaseInterface()
{
qRegisterMetaType<SourceDatabaseEntry>( "SourceEntry" );
qRegisterMetaType<ProductDatabaseEntry>( "ProductEntry" );
qRegisterMetaType<SourceDatabaseEntryContainer>( "SourceEntryContainer" );
qRegisterMetaType<ProductDatabseEntryContainer>( "ProductEntryContainer" );
}
virtual ~AssetDatabaseInterface()
{
}
//! Returns true if the database or file exists already
virtual bool DataExists() = 0;
//! Actually connects to the database, loads it, or creates empty database depending on above.
virtual void LoadData() = 0;
//! Use with care. Resets all data! This causes an immediate commit and save!
virtual void ClearData() = 0;
//! Retrieve the scan folders
virtual void GetScanFolders(QStringList& scanFolderList) = 0;
//! Retrieves a specific scan folder by id, return false if not found
virtual bool GetScanFolder(AZ::s64 scanFolderID, QString& scanFolder) = 0;
//! Adds a scan folder
virtual AZ::s64 AddScanFolder(QString scanFolder) = 0;
// ! remove a scanfolder
virtual void RemoveScanFolder(AZ::s64 scanFolderID) = 0;
virtual void RemoveScanFolder(QString scanFolder) = 0;
//! query the scanFolder ID for a given folder, return false if not found
virtual bool GetScanFolderID(QString scanfolder, AZ::s64& scanFolderID) = 0;
//! query the sourceID of a source
virtual bool GetSourceID(QString sourceName, QString jobDescription, AZ::s64& sourceID) = 0;
//! Retrieve the fingerprint for a given source name on a given platform for a particular jobDescription
//! This could return zero if its never seen this file before.
virtual bool GetFingerprintForSource(QString sourceName, QString jobDescription, AZ::u32& fingerprint) = 0;
//! Set the fingerprint for the given source name, platform and jobDescription to the value provided
//! If updating a existing fingerprint you do not have to supply guid or scanfolderid
virtual void SetSource(QString sourceName, QString jobDescription, AZ::u32 fingerprint, AZ::Uuid guid = AZ::Uuid::CreateNull(), AZ::s64 scanFolderID = 0) = 0;
//! Removing a fingerprint will destroy its entry in the database
//! and any entries that refer to it (products, etc). if you want to merely set it dirty
//! Then instead call SetSource to zero
virtual void RemoveSource(QString sourceName, QString jobDescription) = 0;
virtual void RemoveSource(AZ::s64 sourceID) = 0;
//! Given a source name, jobDescription, and platform return the list of products by the last compile of that file
//! returns false if it doesn't know about source or if the source did not emitted any products.
virtual bool GetProductsForSource(QString sourceName, QString jobDescription, ProductDatabseEntryContainer& products, QString platform = QString()) = 0;
//! Given a source name and platform return the list of all jobDescriptions associated with them from the last compile of that file
//! returns false if it doesn't know about any job description for that source and platform.
virtual bool GetJobDescriptionsForSource(QString sourceName, QStringList& jobDescription) = 0;
//! Given an product file name, compute source file name
//! False is returned if its never heard of that product.
virtual bool GetSourceFromProductName(QString productName, SourceDatabaseEntry& source) = 0;
//! For a given source, set the list of products for that source.
//! Removes any data that's present and overwrites it with the new list
//! Note that an empty list is acceptable data, it means the source emitted no products
virtual void SetProductsForSource(QString sourceName, QString jobDescription, const ProductDatabseEntryContainer& productList = ProductDatabseEntryContainer(), QString platform = QString()) = 0;
//! Clear the products for a given source. This removes the entry entirely, not just sets it to empty.
virtual void RemoveProducts(QString sourceName, QString jobDescription, QString platform = QString()) = 0;
virtual void RemoveProduct(AZ::s64 productID) = 0;
//! GetMatchingProductFiles - checks the database for all products that begin with the given match check
//! Note that the input string is expected to not include the cache folder
//! so it probably starts with platform name.
virtual void GetMatchingProducts(QString matchCheck, ProductDatabseEntryContainer& products, QString platform = QString()) = 0;
//! GetMatchingSourceFiles - checks the database for all source files that begin with the given match check
//! note that the input string is expected to be the relative path name
//! and the output is the relative name (so to convert it to a full path, you will need to call the appropriate function)
virtual void GetMatchingSources(QString matchCheck, SourceDatabaseEntryContainer& sources) = 0;
//! Get a giant list of ALL known source files in the database.
virtual void GetSources(SourceDatabaseEntryContainer& sources) = 0;
//! Get a giant list of ALL known products in the database.
virtual void GetProducts(ProductDatabseEntryContainer& products, QString platform = QString()) = 0;
//! finds all elements in the database that ends with the given input. (Used to look things up by extensions, in general)
virtual void GetSourcesByExtension(QString extension, SourceDatabaseEntryContainer& sources) = 0;
//! SetJobLogForSource updates the Job Log table to record the status of a particular job.
//! It also sets all prior jobs that match that job exactly to not be the "latest one" but keeps them in the database.
virtual void SetJobLogForSource(AZ::s64 jobId, const AZStd::string& sourceName, const AZStd::string& platform, const AZ::Uuid& builderUuid, const AZStd::string& jobKey, AzToolsFramework::AssetProcessor::JobStatus status) = 0;
};
} // namespace AssetProcessor
#endif // ASSETPROCESSOR_ASSETDATA_H
@@ -0,0 +1,559 @@
/*
* 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 "AssetRequestHandler.h"
#include <QDir>
#include <QTimer>
#include <AzToolsFramework/ToolsComponents/ToolsAssetCatalogBus.h>
using namespace AssetProcessor;
namespace
{
static const uint32_t s_assetPath = AssetUtilities::ComputeCRC32Lowercase("assetPath");
}
AssetRequestHandler::AssetRequestLine::AssetRequestLine(QString platform, QString searchTerm, const AZ::Data::AssetId& assetId, bool isStatusRequest, int searchType)
: m_platform(platform)
, m_searchTerm(searchTerm)
, m_isStatusRequest(isStatusRequest)
, m_assetId(assetId)
, m_searchType(searchType)
{
}
bool AssetRequestHandler::AssetRequestLine::IsStatusRequest() const
{
return m_isStatusRequest;
}
QString AssetRequestHandler::AssetRequestLine::GetPlatform() const
{
return m_platform;
}
QString AssetRequestHandler::AssetRequestLine::GetSearchTerm() const
{
return m_searchTerm;
}
int AssetRequestHandler::AssetRequestLine::GetSearchType() const
{
return m_searchType;
}
const AZ::Data::AssetId& AssetRequestHandler::AssetRequestLine::GetAssetId() const
{
return m_assetId;
}
QString AssetRequestHandler::AssetRequestLine::GetDisplayString() const
{
if (m_assetId.IsValid())
{
return QString::fromUtf8(m_assetId.ToString<AZStd::string>().c_str());
}
return m_searchTerm;
}
int AssetRequestHandler::GetNumOutstandingAssetRequests() const
{
return m_pendingAssetRequests.size();
}
namespace
{
using namespace AzToolsFramework::AssetSystem;
using namespace AzFramework::AssetSystem;
GetFullSourcePathFromRelativeProductPathResponse HandleGetFullSourcePathFromRelativeProductPathRequest(MessageData<GetFullSourcePathFromRelativeProductPathRequest> messageData)
{
bool fullPathFound = false;
AZStd::string fullSourcePath;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, messageData.m_message->m_relativeProductPath, fullSourcePath);
if (!fullPathFound)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Could not find full source path from the relative product path (%s).\n", messageData.m_message->m_relativeProductPath.c_str());
}
return GetFullSourcePathFromRelativeProductPathResponse(fullPathFound, fullSourcePath);
}
GetRelativeProductPathFromFullSourceOrProductPathResponse HandleGetRelativeProductPathFromFullSourceOrProductPathRequest(MessageData<GetRelativeProductPathFromFullSourceOrProductPathRequest> messageData)
{
bool relPathFound = false;
AZStd::string relProductPath;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(relPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath, messageData.m_message->m_sourceOrProductPath, relProductPath);
if (!relPathFound)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Could not find relative product path for the source file (%s).", messageData.m_message->m_sourceOrProductPath.c_str());
}
return GetRelativeProductPathFromFullSourceOrProductPathResponse(relPathFound, relProductPath);
}
SourceAssetInfoResponse HandleSourceAssetInfoRequest(MessageData<SourceAssetInfoRequest> messageData)
{
SourceAssetInfoResponse response;
if (messageData.m_message->m_assetId.IsValid())
{
AZStd::string rootFolder;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(response.m_found, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourceUUID, messageData.m_message->m_assetId.m_guid, response.m_assetInfo, rootFolder);
if (response.m_found)
{
response.m_assetInfo.m_assetId.m_subId = messageData.m_message->m_assetId.m_subId;
response.m_assetInfo.m_assetType = messageData.m_message->m_assetType;
response.m_rootFolder = rootFolder.c_str();
}
else
{
response.m_assetInfo.m_assetId.SetInvalid();
}
}
else if (!messageData.m_message->m_assetPath.empty())
{
AZStd::string rootFolder;
// its being asked for via path instead of ID. slightly different call.
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(response.m_found, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath, messageData.m_message->m_assetPath.c_str(), response.m_assetInfo, rootFolder);
response.m_rootFolder = rootFolder.c_str();
}
// note that in the case of an invalid request, response is defaulted to false for m_found, so there is no need to
// populate the response in that case.
return response;
}
SourceAssetProductsInfoResponse HandleSourceAssetProductsInfoRequest(MessageData<SourceAssetProductsInfoRequest> messageData)
{
SourceAssetProductsInfoResponse response;
if (messageData.m_message->m_assetId.IsValid())
{
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(response.m_found, &AssetSystemRequest::GetAssetsProducedBySourceUUID,
messageData.m_message->m_assetId.m_guid, response.m_productsAssetInfo);
}
// note that in the case of an invalid request, response is defaulted to false for m_found, so there is no need to
// populate the response in that case.
return response;
}
GetScanFoldersResponse HandleGetScanFoldersRequest(MessageData<GetScanFoldersRequest> messageData)
{
bool success = true;
AZStd::vector<AZStd::string> scanFolders;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(success, &AzToolsFramework::AssetSystemRequestBus::Events::GetScanFolders, scanFolders);
if (!success)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Could not acquire a list of scan folders from the database.");
}
return GetScanFoldersResponse(move(scanFolders));
}
GetAssetSafeFoldersResponse HandleGetAssetSafeFoldersRequest(MessageData<GetAssetSafeFoldersRequest> messageData)
{
bool success = true;
AZStd::vector<AZStd::string> assetSafeFolders;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(success, &AzToolsFramework::AssetSystemRequestBus::Events::GetAssetSafeFolders, assetSafeFolders);
if (!success)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Could not acquire a list of asset safe folders from the database.");
}
return GetAssetSafeFoldersResponse(move(assetSafeFolders));
}
void HandleRegisterSourceAssetRequest(MessageData<RegisterSourceAssetRequest> messageData)
{
AzToolsFramework::ToolsAssetSystemBus::Broadcast(&AzToolsFramework::ToolsAssetSystemRequests::RegisterSourceAssetType, messageData.m_message->m_assetType, messageData.m_message->m_assetFileFilter.c_str());
}
void HandleUnregisterSourceAssetRequest(MessageData<UnregisterSourceAssetRequest> messageData)
{
AzToolsFramework::ToolsAssetSystemBus::Broadcast(&AzToolsFramework::ToolsAssetSystemRequests::UnregisterSourceAssetType, messageData.m_message->m_assetType);
}
AssetInfoResponse HandleAssetInfoRequest(MessageData<AssetInfoRequest> messageData)
{
AssetInfoResponse response;
if (messageData.m_message->m_assetId.IsValid())
{
AZStd::string rootFilePath;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(response.m_found, &AzToolsFramework::AssetSystemRequestBus::Events::GetAssetInfoById,
messageData.m_message->m_assetId, messageData.m_message->m_assetType, messageData.m_message->m_platformName, response.m_assetInfo, rootFilePath);
response.m_rootFolder = rootFilePath;
}
else if (!messageData.m_message->m_assetPath.empty())
{
bool autoRegisterIfNotFound = false;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(response.m_assetInfo.m_assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, messageData.m_message->m_assetPath.c_str(), AZ::Data::s_invalidAssetType, autoRegisterIfNotFound);
response.m_found = response.m_assetInfo.m_assetId.IsValid();
}
return response;
}
AssetDependencyInfoResponse HandleAssetDependencyInfoRequest(MessageData<AssetDependencyInfoRequest> messageData)
{
using namespace AzFramework::AssetSystem;
AssetDependencyInfoResponse response;
if (messageData.m_message->m_assetId.IsValid())
{
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> result = AZ::Failure(AZStd::string());
// Call the appropriate AssetCatalog API based on the type of dependencies requested.
switch (messageData.m_message->m_dependencyType)
{
case AssetDependencyInfoRequest::DependencyType::DirectDependencies:
AZ::Data::AssetCatalogRequestBus::BroadcastResult(result,
&AZ::Data::AssetCatalogRequestBus::Events::GetDirectProductDependencies, messageData.m_message->m_assetId);
break;
case AssetDependencyInfoRequest::DependencyType::AllDependencies:
AZ::Data::AssetCatalogRequestBus::BroadcastResult(result,
&AZ::Data::AssetCatalogRequestBus::Events::GetAllProductDependencies, messageData.m_message->m_assetId);
break;
case AssetDependencyInfoRequest::DependencyType::LoadBehaviorDependencies:
AZ::Data::AssetCatalogRequestBus::BroadcastResult(result,
&AZ::Data::AssetCatalogRequestBus::Events::GetLoadBehaviorProductDependencies,
messageData.m_message->m_assetId, response.m_noloadSet, response.m_preloadAssetList);
break;
}
// Decompose the AZ::Outcome into separate variables, since AZ::Outcome is not a serializable type.
response.m_found = result.IsSuccess();
if (response.m_found)
{
response.m_dependencies = result.GetValue();
}
else
{
response.m_errorString = result.GetError();
}
}
else
{
response.m_found = false;
response.m_errorString.assign("Invalid Asset Id");
}
return response;
}
}
void AssetRequestHandler::HandleRequestEscalateAsset(MessageData<RequestEscalateAsset> messageData)
{
if (!messageData.m_message->m_assetUuid.IsNull())
{
// search by UUID is preferred.
Q_EMIT RequestEscalateAssetByUuid(messageData.m_platform, messageData.m_message->m_assetUuid);
}
else if (!messageData.m_message->m_searchTerm.empty())
{
// fall back to search term.
Q_EMIT RequestEscalateAssetBySearchTerm(messageData.m_platform, QString::fromUtf8(messageData.m_message->m_searchTerm.c_str()));
}
else
{
AZ_Warning(AssetProcessor::DebugChannel, false, "Invalid RequestEscalateAsset. Both the search term and uuid are empty/null\n");
}
}
bool AssetRequestHandler::InvokeHandler(MessageData<AzFramework::AssetSystem::BaseAssetProcessorMessage> messageData)
{
// This function checks to see whether the incoming message is either one of those request, which require decoding the type of message and then invoking the appropriate EBUS handler.
// If the message is not one of those type than it checks to see whether some one has registered a request handler for that message type and then invokes it.
using namespace AzFramework::AssetSystem;
{
auto located = m_requestRouter.m_messageHandlers.find(messageData.m_message->GetMessageType());
if(located != m_requestRouter.m_messageHandlers.end())
{
located->second(messageData);
return false;
}
AZ_Warning(AssetProcessor::DebugChannel, false, "OnNewIncomingRequest: Message Handler not found for message type %d, ignoring."
" Make sure to register new messages with IRequestRouter::RegisterMessageHandler", messageData.m_message->GetMessageType());
return true;
}
}
void AssetRequestHandler::ProcessAssetRequest(MessageData<RequestAssetStatus> messageData)
{
if ((messageData.m_message->m_searchTerm.empty())&&(!messageData.m_message->m_assetId.IsValid()))
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Failed to decode incoming RequestAssetStatus - both path and uuid is empty\n");
SendAssetStatus(messageData.m_key, RequestAssetStatus::MessageType, AssetStatus_Unknown);
return;
}
AssetRequestLine newLine(messageData.m_platform, QString::fromUtf8(messageData.m_message->m_searchTerm.c_str()), messageData.m_message->m_assetId, messageData.m_message->m_isStatusRequest, messageData.m_message->m_searchType);
AZ_TracePrintf(AssetProcessor::DebugChannel, "GetAssetStatus/CompileAssetSync: %s.\n", newLine.GetDisplayString().toUtf8().constData());
QString assetPath = QString::fromUtf8(messageData.m_message->m_searchTerm.c_str()); // utf8-decode just once here, reuse below
m_pendingAssetRequests.insert(messageData.m_key, newLine);
Q_EMIT RequestCompileGroup(messageData.m_key, messageData.m_platform, assetPath, messageData.m_message->m_assetId, messageData.m_message->m_isStatusRequest, messageData.m_message->m_searchType);
}
void AssetRequestHandler::OnCompileGroupCreated(NetworkRequestID groupID, AssetStatus status)
{
using namespace AzFramework::AssetSystem;
auto located = m_pendingAssetRequests.find(groupID);
if (located == m_pendingAssetRequests.end())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "OnCompileGroupCreated: No such asset group found, ignoring.\n");
return;
}
if (status == AssetStatus_Unknown)
{
// if this happens it means we made an async request and got a response from the build queue that no such thing
// exists in the queue. It might still be a valid asset - for example, it may have already finished compiling and thus
// won't be in the queue. To cover this we also make a request to the asset manager here (its also async)
Q_EMIT RequestAssetExists(groupID, located.value().GetPlatform(), located.value().GetSearchTerm(), located.value().GetAssetId(), located.value().GetSearchType());
}
else
{
// if its a status request, return it immediately and then remove it.
if (located.value().IsStatusRequest())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "GetAssetStatus: Responding with status of: %s\n", located.value().GetDisplayString().toUtf8().constData());
SendAssetStatus(groupID, RequestAssetStatus::MessageType, status);
m_pendingAssetRequests.erase(located);
}
// if its not a status request then we'll wait for OnCompileGroupFinished before responding.
}
}
void AssetRequestHandler::OnCompileGroupFinished(NetworkRequestID groupID, AssetStatus status)
{
auto located = m_pendingAssetRequests.find(groupID);
if (located == m_pendingAssetRequests.end())
{
// this is okay to happen if its a status request.
return;
}
// if the compile group finished, but the request was for a SPECIFIC asset, we have to take an extra step since
// the compile group being finished just means the source file has compiled, doesn't necessarly mean that specific asset is emitted.
if (located.value().GetAssetId().IsValid())
{
Q_EMIT RequestAssetExists(groupID, located.value().GetPlatform(), located.value().GetSearchTerm(), located.value().GetAssetId(), located.value().GetSearchType());
}
else
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Compile Group finished: %s.\n", located.value().GetDisplayString().toUtf8().constData());
SendAssetStatus(groupID, RequestAssetStatus::MessageType, status);
m_pendingAssetRequests.erase(located);
}
}
//! Called from the outside in response to a RequestAssetExists.
void AssetRequestHandler::OnRequestAssetExistsResponse(NetworkRequestID groupID, bool exists)
{
using namespace AzFramework::AssetSystem;
auto located = m_pendingAssetRequests.find(groupID);
if (located == m_pendingAssetRequests.end())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "OnRequestAssetExistsResponse: No such compile group found, ignoring.\n");
return;
}
AZ_TracePrintf(AssetProcessor::DebugChannel, "GetAssetStatus / CompileAssetSync: Asset %s is %s.\n",
located.value().GetDisplayString().toUtf8().constData(),
exists ? "compiled already" : "missing" );
SendAssetStatus(groupID, RequestAssetStatus::MessageType, exists ? AssetStatus_Compiled : AssetStatus_Missing);
m_pendingAssetRequests.erase(located);
}
void AssetRequestHandler::SendAssetStatus(NetworkRequestID groupID, unsigned int /*type*/, AssetStatus status)
{
ResponseAssetStatus resp;
resp.m_assetStatus = status;
EBUS_EVENT_ID(groupID.first, AssetProcessor::ConnectionBus, SendResponse, groupID.second, resp);
}
AssetRequestHandler::AssetRequestHandler()
{
m_requestRouter.RegisterQueuedCallbackHandler(this, &AssetRequestHandler::ProcessAssetRequest);
m_requestRouter.RegisterMessageHandler(&HandleGetFullSourcePathFromRelativeProductPathRequest);
m_requestRouter.RegisterMessageHandler(&HandleGetRelativeProductPathFromFullSourceOrProductPathRequest);
m_requestRouter.RegisterMessageHandler(&HandleSourceAssetInfoRequest);
m_requestRouter.RegisterMessageHandler(&HandleSourceAssetProductsInfoRequest);
m_requestRouter.RegisterMessageHandler(&HandleGetScanFoldersRequest);
m_requestRouter.RegisterMessageHandler(&HandleGetAssetSafeFoldersRequest);
m_requestRouter.RegisterMessageHandler(&HandleRegisterSourceAssetRequest);
m_requestRouter.RegisterMessageHandler(&HandleUnregisterSourceAssetRequest);
m_requestRouter.RegisterMessageHandler(&HandleAssetInfoRequest);
m_requestRouter.RegisterMessageHandler(&HandleAssetDependencyInfoRequest);
m_requestRouter.RegisterMessageHandler(ToFunction(&AssetRequestHandler::HandleRequestEscalateAsset));
}
QString AssetRequestHandler::CreateFenceFile(unsigned int fenceId)
{
QDir fenceDir;
if (!AssetUtilities::ComputeFenceDirectory(fenceDir))
{
return QString();
}
QString fileName = QString("fenceFile~%1.%2").arg(fenceId).arg(FENCE_FILE_EXTENSION);
QString fenceFileName = fenceDir.filePath(fileName);
QFileInfo fileInfo(fenceFileName);
if (!fileInfo.absoluteDir().exists())
{
// if fence dir does not exists ,than try to create it
if (!fileInfo.absoluteDir().mkpath("."))
{
return QString();
}
}
QFile fenceFile(fenceFileName);
if (fenceFile.exists())
{
return QString();
}
bool result = fenceFile.open(QFile::WriteOnly);
if (!result)
{
return QString();
}
fenceFile.close();
return fileInfo.absoluteFilePath();
}
bool AssetRequestHandler::DeleteFenceFile(QString fenceFileName)
{
return QFile::remove(fenceFileName);
}
void AssetRequestHandler::DeleteFenceFile_Retry(unsigned int fenceId, QString fenceFileName, NetworkRequestID key, AZStd::shared_ptr<BaseAssetProcessorMessage> message, QString platform, int retriesRemaining)
{
if (DeleteFenceFile(fenceFileName))
{
// add an entry in map
// We have successfully created and deleted the fence file, insert an entry for it in the pendingFenceRequest map
// and return, we will only process this request once the APM indicates that it has detected the fence file
m_pendingFenceRequestMap[fenceId] = AZStd::move(RequestInfo(key, AZStd::move(message), platform));
return;
}
retriesRemaining--;
if (retriesRemaining == 0)
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "AssetProcessor was unable to delete the fence file");
// send request to the appropriate handler with fencingfailed set to true and return
InvokeHandler(MessageData(AZStd::move(message), key, platform, true));
}
else
{
auto deleteFenceFilefunctor = [this, fenceId, fenceFileName, key, message = AZStd::move(message), platform, retriesRemaining]() mutable
{
DeleteFenceFile_Retry(fenceId, fenceFileName, key, AZStd::move(message), platform, retriesRemaining);
};
QTimer::singleShot(100, this, AZStd::move(deleteFenceFilefunctor));
}
}
void AssetRequestHandler::OnNewIncomingRequest(unsigned int connId, unsigned int serial, QByteArray payload, QString platform)
{
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "Unable to retrieve serialize context.");
AZStd::shared_ptr<BaseAssetProcessorMessage> message{ AZ::Utils::LoadObjectFromBuffer<BaseAssetProcessorMessage>(payload.constData(), payload.size(), serializeContext) };
if (!message)
{
AZ_Warning("Asset Request Handler", false, "OnNewIncomingRequest: Invalid object sent as network message to AssetRequestHandler.");
return;
}
NetworkRequestID key(connId, serial);
QString fenceFileName;
if (message->RequireFencing())
{
bool successfullyCreatedFenceFile = false;
int fenceID = 0;
for (int idx = 0; idx < g_RetriesForFenceFile; ++idx)
{
fenceID = ++m_fenceId;
fenceFileName = CreateFenceFile(fenceID);
if (!fenceFileName.isEmpty())
{
successfullyCreatedFenceFile = true;
break;
}
}
if (!successfullyCreatedFenceFile)
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "AssetProcessor was unable to create the fence file");
// send request to the appropriate handler with fencingFailed set to true and return
InvokeHandler(MessageData(AZStd::move(message), key, platform, true));
}
else
{
// if we are here it means that we were able to create the fence file, we will try to delete it now with a fixed number of retries
DeleteFenceFile_Retry(fenceID, fenceFileName, key, AZStd::move(message), platform, g_RetriesForFenceFile);
}
}
else
{
// If we are here it indicates that the request does not require fencing, we either call the required bus or invoke the handler directly
InvokeHandler(MessageData(AZStd::move(message), key, platform));
}
}
void AssetRequestHandler::OnFenceFileDetected(unsigned int fenceId)
{
auto fenceRequestFound = m_pendingFenceRequestMap.find(fenceId);
if (fenceRequestFound == m_pendingFenceRequestMap.end())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "OnFenceFileDetected: Fence File Request not found, ignoring.\n");
return;
}
InvokeHandler(MessageData(fenceRequestFound->second.m_message, fenceRequestFound->second.m_requestId, fenceRequestFound->second.m_platform));
m_pendingFenceRequestMap.erase(fenceRequestFound);
}
@@ -0,0 +1,307 @@
/*
* 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/assetprocessor.h"
#include "native/utilities/assetUtils.h"
#include <QString>
#include <QByteArray>
#include <QHash>
#include <QObject>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/Asset/AssetSystemTypes.h>
#include <connection/connectionManager.h>
#endif
namespace AzFramework
{
namespace AssetSystem
{
class BaseAssetProcessorMessage;
} // namespace AssetSystem
} // namespace AzFramework
namespace AssetProcessor
{
class AssetRequestHandler;
template<typename TRequest>
struct MessageData
{
static_assert(AZStd::is_base_of<AzFramework::AssetSystem::BaseAssetProcessorMessage, TRequest>::value, "TRequest must derive from BaseAssetProcessorMessage");
AZStd::shared_ptr<TRequest> m_message;
NetworkRequestID m_key;
QString m_platform;
bool m_fencingFailed{ false };
MessageData() = default;
MessageData(AZStd::shared_ptr<TRequest> message, NetworkRequestID key, QString platform, bool fencingFailed = false)
: m_message(message), m_key(key), m_platform(platform), m_fencingFailed(fencingFailed)
{}
template<typename TOther>
MessageData(const MessageData<TOther>& rhs)
{
m_message = AZStd::rtti_pointer_cast<TRequest>(rhs.m_message);
m_key = rhs.m_key;
m_platform = rhs.m_platform;
m_fencingFailed = rhs.m_fencingFailed;
}
};
struct IRequestRouter
{
friend class AssetRequestHandler;
AZ_RTTI(IRequestRouter, "{FC7F875C-2CD1-4CD2-AC63-71097DF612AC}");
IRequestRouter(AZStd::function<void(unsigned int, unsigned int, QByteArray, QString)> requestHandler)
: m_requestHandler(AZStd::move(requestHandler))
{
AZ::Interface<IRequestRouter>::Register(this);
}
virtual ~IRequestRouter()
{
AZ::Interface<IRequestRouter>::Unregister(this);
}
//! Registers a QT object callback as a handler for a TRequest type of message.
//! The callback function will be run on obj's thread
//! If the return value of the handler is void, no response will be sent.
//! Not thread-safe, do not call after AP initialization stage
template<typename TRequest, typename TResponse, typename TClass>
void RegisterQueuedCallbackHandler(TClass* obj, TResponse(TClass::* handler)(AssetProcessor::MessageData<TRequest>))
{
// Return type is set to void here since the response needs to be delayed along with the handler call
// HandleResponse gets called twice in this whole chain but the first time won't attempt to send a response because of this void
RegisterMessageHandler<TRequest, void>([=](MessageData<TRequest> messageData)
{
QMetaObject::invokeMethod(obj, [=]()
{
// This will run on the obj's thread and handle sending the response now that we're ready to process
HandleResponse<TRequest, TResponse>([obj, handler](MessageData<TRequest> messageData) -> TResponse
{
return (obj->*handler)(messageData);
}, messageData);
}, Qt::ConnectionType::QueuedConnection);
});
}
//! Registers a callback as a handler for a TRequest type of message.
//! If the return value of the handler is void, no response will be sent.
//! Not thread-safe, do not call after AP initialization stage
template <class TRequest, class TResponse>
void RegisterMessageHandler(TResponse(*handler)(MessageData<TRequest> messageData))
{
RegisterMessageHandler<TRequest, TResponse>(AZStd::function<TResponse(MessageData<TRequest>)>(AZStd::move(handler)));
}
//! Registers a callback as a handler for a TRequest type of message.
//! If the return value of the handler is void, no response will be sent.
//! Not thread-safe, do not call after AP initialization stage
template <class TRequest, class TResponse>
void RegisterMessageHandler(AZStd::function<TResponse(MessageData<TRequest>)> handler)
{
static constexpr unsigned int MessageType = TRequest::MessageType;
m_messageHandlers[MessageType] = [this, handler = AZStd::move(handler)](MessageData<AzFramework::AssetSystem::BaseAssetProcessorMessage> messageData)
{
MessageData<TRequest> downcastData = messageData;
if (downcastData.m_message)
{
IRequestRouter::HandleResponse<TRequest, TResponse>(AZStd::move(handler), downcastData);
}
else
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Expected message type (%d) but incoming message type is %d.\n", MessageType, messageData.m_message->GetMessageType());
}
};
using namespace AZStd::placeholders;
ConnectionManagerRequestBus::Broadcast(&ConnectionManagerRequestBus::Events::RegisterService, MessageType, AZStd::bind(m_requestHandler, _1, _3, _4, _5));
}
template<class TRequest>
void UnregisterMessageHandler()
{
static constexpr unsigned int MessageType = TRequest::MessageType;
auto messageItr = m_messageHandlers.find(MessageType);
if(messageItr != m_messageHandlers.end())
{
m_messageHandlers.erase(messageItr);
}
}
AZ_DISABLE_COPY_MOVE(IRequestRouter);
protected:
//! Helper to handle sending a response for a message if one is needed.
template<class TRequest, class TResponse, typename AZStd::enable_if_t<!AZStd::is_void_v<TResponse>>* = nullptr>
static void HandleResponse(AZStd::function<TResponse(MessageData<TRequest>)> handler, MessageData<TRequest> messageData)
{
auto&& response = handler(messageData);
ConnectionBus::Event(messageData.m_key.first, &ConnectionBus::Events::SendResponse, messageData.m_key.second, response);
}
template<class TRequest, class TResponse, typename AZStd::enable_if_t<AZStd::is_void_v<TResponse>>* = nullptr>
static void HandleResponse(AZStd::function<TResponse(MessageData<TRequest>)> handler, MessageData<TRequest> messageData)
{
// This template handles void returns which mean no response should be sent
handler(messageData);
}
using MessageHandler = AZStd::function<void(MessageData<AzFramework::AssetSystem::BaseAssetProcessorMessage>)>;
//! Map of messageType to message handler callback
AZStd::unordered_map<unsigned int /*messageType*/, MessageHandler> m_messageHandlers;
//! Parent object callback which will be registered with the ConnectionManager for each message
AZStd::function<void(unsigned int, unsigned int, QByteArray, QString)> m_requestHandler;
};
//! AssetRequestHandler
//! this exists to handle requests from outside sources to compile assets.
//! or to get the status of groups of assets.
class AssetRequestHandler
: public QObject
{
using AssetStatus = AzFramework::AssetSystem::AssetStatus;
using BaseAssetProcessorMessage = AzFramework::AssetSystem::BaseAssetProcessorMessage;
Q_OBJECT
public:
AssetRequestHandler();
protected:
//! This function creates a fence file.
//! It will return the fencefile path if it succeeds, otherwise it returns an empty string
virtual QString CreateFenceFile(unsigned int fenceId);
//! This function delete a fence file.
//! it will return true if it succeeds, otherwise it returns false.
virtual bool DeleteFenceFile(QString fenceFileName);
Q_SIGNALS:
//! Request that a compile group is created for all assets that match that platform and search term.
//! emitting this signal will ultimately result in OnCompileGroupCreated and OnCompileGroupFinished being executed
//! at some later time with the same groupID.
void RequestCompileGroup(NetworkRequestID groupID, QString platform, QString searchTerm, AZ::Data::AssetId assetId, bool isStatusRequest, int searchType);
//! This request goes out to ask the system in general whether an asset can be found (as a product).
void RequestAssetExists(NetworkRequestID groupID, QString platform, QString searchTerm, AZ::Data::AssetId assetId, int searchType);
void RequestEscalateAssetByUuid(QString platform, AZ::Uuid escalatedAssetUUID);
void RequestEscalateAssetBySearchTerm(QString platform, QString escalatedSearchTerm);
public Q_SLOTS:
//! ProcessGetAssetStatus - someone on the network wants to know about the status of an asset.
//! isStatusRequest will be TRUE if its a status request. If its false it means its a compile request
void ProcessAssetRequest(MessageData<AzFramework::AssetSystem::RequestAssetStatus> messageData);
//! OnCompileGroupCreated is invoked in response to asking for a compile group to be created.
//! Its status will either be Unknown if no assets are queued or in flight that match that pattern
//! or it will be Queued or Compiling if some were matched.
//! If you get a Queued or Compiling, you will eventually get a OnCompileGroupFinished with the same group ID.
void OnCompileGroupCreated(NetworkRequestID groupID, AssetStatus status);
//! OnCompileGroupFinished is expected to be called when a compile group completes or fails.
//! the status is expected to be either Compiled or Failed.
void OnCompileGroupFinished(NetworkRequestID groupID, AssetStatus status);
//! Called from the outside in response to a RequestAssetExists.
void OnRequestAssetExistsResponse(NetworkRequestID groupID, bool exists);
void OnFenceFileDetected(unsigned int fenceId);
//! This will get called for every asset related messages or messages that require fencing
void OnNewIncomingRequest(unsigned int connId, unsigned int serial, QByteArray payload, QString platform);
public:
//! Just return how many in flight requests there are.
int GetNumOutstandingAssetRequests() const;
protected:
template<typename TRequest, typename TResponse>
AZStd::function<TResponse(MessageData<TRequest>)> ToFunction(TResponse(AssetRequestHandler::* func)(MessageData<TRequest>))
{
using namespace AZStd::placeholders;
return AZStd::function<TResponse(MessageData<TRequest>)>(AZStd::bind(func, this, _1));
}
// Invokes the appropriate handler and returns true if the message should be deleted by the caller and false if the request handler is responsible for deleting the message
virtual bool InvokeHandler(MessageData<AzFramework::AssetSystem::BaseAssetProcessorMessage> message);
private:
void DeleteFenceFile_Retry(unsigned fenceId, QString fenceFileName, NetworkRequestID key, AZStd::shared_ptr<BaseAssetProcessorMessage> message, QString platform, int retriesRemaining);
void SendAssetStatus(NetworkRequestID groupID, unsigned int type, AssetStatus status);
void HandleRequestEscalateAsset(MessageData<AzFramework::AssetSystem::RequestEscalateAsset> messageData);
// we keep state about a request in this class:
class AssetRequestLine
{
public:
AssetRequestLine(QString platform, QString searchTerm, const AZ::Data::AssetId& assetId, bool isStatusRequest, int searchType);
bool IsStatusRequest() const;
QString GetPlatform() const;
QString GetSearchTerm() const;
const AZ::Data::AssetId& GetAssetId() const;
QString GetDisplayString() const;
int GetSearchType() const;
private:
bool m_isStatusRequest;
QString m_platform;
QString m_searchTerm;
AZ::Data::AssetId m_assetId;
int m_searchType{ 0 };
};
// this map keeps track of whether a request was for a compile (FALSE), or a status (TRUE)
QHash<NetworkRequestID, AssetRequestLine> m_pendingAssetRequests;
//! This is an internal struct that is used for storing all the necessary information for requests that require fencing
struct RequestInfo
{
RequestInfo() = default;
RequestInfo(NetworkRequestID requestId, AZStd::shared_ptr<BaseAssetProcessorMessage> message, QString platform)
:m_requestId(requestId)
, m_message(AZStd::move(message))
, m_platform(platform)
{
}
NetworkRequestID m_requestId{};
AZStd::shared_ptr<BaseAssetProcessorMessage> m_message{};
QString m_platform{};
};
AZStd::unordered_map<unsigned int, RequestInfo> m_pendingFenceRequestMap;
unsigned int m_fenceId = 0;
IRequestRouter m_requestRouter{ [this](unsigned int connId, unsigned int serial, QByteArray payload, QString platform) {OnNewIncomingRequest(connId, serial, payload, platform); } };
};
} // namespace AssetProcessor
@@ -0,0 +1,163 @@
/*
* 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/AssetManager/ControlRequestHandler.h>
#if !defined(Q_MOC_RUN)
#include <QHostAddress>
#include <QTcpSocket>
#include <QTcpServer>
#endif
#include <native/assetprocessor.h>
#include <native/utilities/ApplicationManagerBase.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Debug/Trace.h>
ControlRequestHandler::ControlRequestHandler(ApplicationManagerBase* parent) : QObject(parent),
m_applicationManager(parent)
{
connect(m_applicationManager, &ApplicationManagerBase::FullIdle, this, &ControlRequestHandler::AssetManagerIdleStateChange);
StartListening(0);
}
ControlRequestHandler::~ControlRequestHandler()
{
}
bool ControlRequestHandler::StartListening(unsigned short port)
{
if (!m_tcpServer)
{
m_tcpServer = new QTcpServer(this);
}
if (!m_tcpServer->isListening())
{
if (!m_tcpServer->listen(QHostAddress::LocalHost, port))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Control Request Handler couldn't listen on requested port %d", port);
return false;
}
port = m_tcpServer->serverPort();
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Control Port: %d\n", port);
connect(m_tcpServer, &QTcpServer::newConnection, this, &ControlRequestHandler::GotConnection);
AZ_TracePrintf(AssetProcessor::DebugChannel, "Asset Processor Control Request Handler listening on port %d\n", port);
}
return true;
}
void ControlRequestHandler::GotConnection()
{
if (m_tcpServer->hasPendingConnections())
{
QTcpSocket* newSocket = m_tcpServer->nextPendingConnection();
connect(newSocket, &QTcpSocket::stateChanged, this, &ControlRequestHandler::SocketStateUpdate);
connect(newSocket, &QTcpSocket::readyRead, this, &ControlRequestHandler::DataReceived);
connect(newSocket, &QTcpSocket::disconnected, this, &ControlRequestHandler::Disconnected);
m_listenSockets.push_back(newSocket);
AZ_TracePrintf(AssetProcessor::DebugChannel, "Asset Processor Control Request Handler got new connection\n");
if (newSocket->bytesAvailable())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Asset Processor Control Request Handler socket had data available\n");
ReadData(newSocket);
}
}
}
void ControlRequestHandler::SocketStateUpdate(QAbstractSocket::SocketState newState)
{
if (newState == QAbstractSocket::UnconnectedState)
{
m_listenSockets.removeOne(static_cast<QTcpSocket*>(QObject::sender()));
}
}
void ControlRequestHandler::DataReceived()
{
QTcpSocket* incoming = static_cast<QTcpSocket*>(QObject::sender());
ReadData(incoming);
}
void ControlRequestHandler::ReadData(QTcpSocket* incoming)
{
if (!incoming)
{
AZ_Error(AssetProcessor::DebugChannel, false, "Attempting to read from null QTcpSocket in ControlRequestHandler");
return;
}
auto sentMessage = incoming->readAll().toStdString();
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Got Control request %s\n", sentMessage.c_str());
if (sentMessage == "quit")
{
QMetaObject::invokeMethod(parent(), "QuitRequested", Qt::QueuedConnection);
}
else if (sentMessage == "ping")
{
incoming->write("pong");
}
else if (sentMessage == "isidle")
{
bool isIdle = m_applicationManager->IsAssetProcessorManagerIdle();
incoming->write(isIdle ? "true" : "false");
}
else if (sentMessage == "waitforidle")
{
bool isIdle = m_applicationManager->CheckFullIdle();
if (isIdle)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Control request responding idle\n");
incoming->write("idle");
}
else
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Control request adding wait idle waiter\n");
m_idleWaitSockets.push_back(incoming);
}
}
else if (sentMessage == "signalidle")
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Control request adding signal idle waiter\n");
m_idleWaitSockets.push_back(incoming);
}
}
void ControlRequestHandler::Disconnected()
{
QTcpSocket* incoming = static_cast<QTcpSocket*>(QObject::sender());
m_listenSockets.removeOne(incoming);
incoming->deleteLater();
}
void ControlRequestHandler::AssetManagerIdleStateChange(bool isIdle)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Control Request Got idle state %d with %d waiters\n", isIdle, m_idleWaitSockets.size());
if (!isIdle)
{
// We only currently care when transitioning to idle
return;
}
for (auto& thisConnection : m_idleWaitSockets)
{
if (m_listenSockets.indexOf(thisConnection) != -1)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Control request sending idle state to socket\n");
thisConnection->write("idle");
}
}
m_idleWaitSockets.clear();
}
@@ -0,0 +1,52 @@
/*
* 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 <QList>
#include <QAbstractSocket>
#endif
class QTcpSocket;
class QTcpServer;
class ApplicationManagerBase;
/** This Class is responsible for listening and getting new connections and
* responding to text queries and commands from the socket. The original purpose
* is to enable writing more reliable and better performing tests which launch
* AP as a subprocess such as our python test modules.
*/
class ControlRequestHandler : public QObject
{
Q_OBJECT
public:
explicit ControlRequestHandler(ApplicationManagerBase* parent = 0);
~ControlRequestHandler();
public slots:
void GotConnection();
void SocketStateUpdate(QAbstractSocket::SocketState newSocketState);
void DataReceived();
void Disconnected();
void AssetManagerIdleStateChange(bool isIdle);
protected:
bool StartListening(unsigned short port = 0);
void ReadData(QTcpSocket* incoming);
private:
QList<QTcpSocket*> m_listenSockets;
QList<QTcpSocket*> m_idleWaitSockets;
QTcpServer* m_tcpServer{ nullptr };
ApplicationManagerBase* m_applicationManager{ nullptr };
};
@@ -0,0 +1,217 @@
/*
* 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 "FileStateCache.h"
#include "native/utilities/assetUtils.h"
#include <AssetProcessor_Traits_Platform.h>
#include <QDir>
namespace AssetProcessor
{
bool FileStateCache::GetFileInfo(const QString& absolutePath, FileStateInfo* foundFileInfo) const
{
LockGuardType scopeLock(m_mapMutex);
auto itr = m_fileInfoMap.find(PathToKey(absolutePath));
if (itr != m_fileInfoMap.end())
{
*foundFileInfo = itr.value();
return true;
}
return false;
}
bool FileStateCache::Exists(const QString& absolutePath) const
{
LockGuardType scopeLock(m_mapMutex);
auto itr = m_fileInfoMap.find(PathToKey(absolutePath));
return itr != m_fileInfoMap.end();
}
bool FileStateCache::GetHash(const QString& absolutePath, FileHash* foundHash)
{
LockGuardType scopeLock(m_mapMutex);
auto fileInfoItr = m_fileInfoMap.find(PathToKey(absolutePath));
if(fileInfoItr == m_fileInfoMap.end())
{
// No info on this file, return false
return false;
}
auto itr = m_fileHashMap.find(PathToKey(absolutePath));
if (itr != m_fileHashMap.end())
{
*foundHash = itr.value();
return true;
}
// There's no hash stored yet or its been invalidated, calculate it
*foundHash = AssetUtilities::GetFileHash(absolutePath.toUtf8().constData(), true);
m_fileHashMap[PathToKey(absolutePath)] = *foundHash;
return true;
}
void FileStateCache::AddInfoSet(QSet<AssetFileInfo> infoSet)
{
LockGuardType scopeLock(m_mapMutex);
for (const AssetFileInfo& info : infoSet)
{
m_fileInfoMap[PathToKey(info.m_filePath)] = FileStateInfo(info);
}
}
void FileStateCache::AddFile(const QString& absolutePath)
{
QFileInfo fileInfo(absolutePath);
LockGuardType scopeLock(m_mapMutex);
AddOrUpdateFileInternal(fileInfo);
InvalidateHash(absolutePath);
if(fileInfo.isDir())
{
ScanFolder(absolutePath);
}
}
void FileStateCache::UpdateFile(const QString& absolutePath)
{
QFileInfo fileInfo(absolutePath);
LockGuardType scopeLock(m_mapMutex);
AddOrUpdateFileInternal(fileInfo);
InvalidateHash(absolutePath);
}
void FileStateCache::RemoveFile(const QString& absolutePath)
{
LockGuardType scopeLock(m_mapMutex);
auto itr = m_fileInfoMap.find(PathToKey(absolutePath));
if (itr != m_fileInfoMap.end())
{
bool isDirectory = itr.value().m_isDirectory;
QString parentPath = itr.value().m_absolutePath;
m_fileInfoMap.erase(itr);
if (isDirectory)
{
for (itr = m_fileInfoMap.begin(); itr != m_fileInfoMap.end(); )
{
if (itr.value().m_absolutePath.startsWith(parentPath))
{
itr = m_fileInfoMap.erase(itr);
continue;
}
++itr;
}
}
}
InvalidateHash(absolutePath);
}
void FileStateCache::InvalidateHash(const QString& absolutePath)
{
auto fileHashItr = m_fileHashMap.find(PathToKey(absolutePath));
if (fileHashItr != m_fileHashMap.end())
{
m_fileHashMap.erase(fileHashItr);
}
}
//////////////////////////////////////////////////////////////////////////
QString FileStateCache::PathToKey(const QString& absolutePath) const
{
QString normalized = AssetUtilities::NormalizeFilePath(absolutePath);
if constexpr (!ASSETPROCESSOR_TRAIT_CASE_SENSITIVE_FILESYSTEM)
{
return normalized.toLower();
}
return normalized;
}
void FileStateCache::AddOrUpdateFileInternal(QFileInfo fileInfo)
{
m_fileInfoMap[PathToKey(fileInfo.absoluteFilePath())] = FileStateInfo(fileInfo.absoluteFilePath(), fileInfo.lastModified(), fileInfo.size(), fileInfo.isDir());
}
void FileStateCache::ScanFolder(const QString& absolutePath)
{
QDir inputFolder(absolutePath);
QFileInfoList entries = inputFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files);
for (const QFileInfo& entry : entries)
{
AddOrUpdateFileInternal(entry);
if (entry.isDir())
{
ScanFolder(entry.absoluteFilePath());
}
}
}
//////////////////////////////////////////////////////////////////////////
bool FileStatePassthrough::GetFileInfo(const QString& absolutePath, FileStateInfo* foundFileInfo) const
{
QFileInfo fileInfo(absolutePath);
if (fileInfo.exists())
{
*foundFileInfo = FileStateInfo(fileInfo.absoluteFilePath(), fileInfo.lastModified(), fileInfo.size(), fileInfo.isDir());
return true;
}
return false;
}
bool FileStatePassthrough::Exists(const QString& absolutePath) const
{
return QFile(absolutePath).exists();
}
bool FileStatePassthrough::GetHash(const QString& absolutePath, FileHash* foundHash)
{
if(!Exists(absolutePath))
{
return false;
}
*foundHash = AssetUtilities::GetFileHash(absolutePath.toUtf8().constData(), true);
return true;
}
bool FileStateInfo::operator==(const FileStateInfo& rhs) const
{
return m_absolutePath == rhs.m_absolutePath
&& m_modTime == rhs.m_modTime
&& m_fileSize == rhs.m_fileSize
&& m_isDirectory == rhs.m_isDirectory;
}
} // namespace AssetProcessor
@@ -0,0 +1,139 @@
/*
* 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/AssetManager/assetScanFolderInfo.h>
#include <QString>
#include <QSet>
#include <QFileInfo>
#include <AzCore/Interface/Interface.h>
namespace AssetProcessor
{
struct FileStateInfo
{
FileStateInfo() = default;
FileStateInfo(QString filePath, QDateTime modTime, AZ::u64 fileSize, bool isDirectory)
: m_absolutePath(filePath), m_modTime(modTime), m_fileSize(fileSize), m_isDirectory(isDirectory) {}
explicit FileStateInfo(const AssetFileInfo& assetFileInfo)
: m_absolutePath(assetFileInfo.m_filePath), m_fileSize(assetFileInfo.m_fileSize), m_isDirectory(assetFileInfo.m_isDirectory), m_modTime(assetFileInfo.m_modTime)
{
}
bool operator==(const FileStateInfo& rhs) const;
QString m_absolutePath{};
QDateTime m_modTime{};
AZ::u64 m_fileSize{};
bool m_isDirectory{};
};
struct IFileStateRequests
{
AZ_RTTI(IFileStateRequests, "{2D883B3A-DCA3-4CE0-976C-4511C3277371}");
IFileStateRequests() = default;
virtual ~IFileStateRequests() = default;
using FileHash = AZ::u64;
/// Fetches info on the file/directory if it exists. Returns true if it exists, false otherwise
virtual bool GetFileInfo(const QString& absolutePath, FileStateInfo* foundFileInfo) const = 0;
/// Convenience function to check if a file or directory exists.
virtual bool Exists(const QString& absolutePath) const = 0;
virtual bool GetHash(const QString& absolutePath, FileHash* foundHash) = 0;
AZ_DISABLE_COPY_MOVE(IFileStateRequests);
};
class FileStateBase
: public IFileStateRequests
{
public:
FileStateBase()
{
AZ::Interface<IFileStateRequests>::Register(this);
}
virtual ~FileStateBase()
{
AZ::Interface<IFileStateRequests>::Unregister(this);
}
/// Bulk adds file state to the cache
virtual void AddInfoSet(QSet<AssetFileInfo> /*infoSet*/) {}
/// Adds a single file to the cache. This will query the OS for the current state
virtual void AddFile(const QString& /*absolutePath*/) {}
/// Updates a single file in the cache. This will query the OS for the current state
virtual void UpdateFile(const QString& /*absolutePath*/) {}
/// Removes a file from the cache
virtual void RemoveFile(const QString& /*absolutePath*/) {}
};
/// Caches file state information retrieved by the file scanner and file watcher
/// Profiling has shown it is faster (at least on windows) compared to asking the OS for file information every time
class FileStateCache final :
public FileStateBase
{
public:
// FileStateRequestBus implementation
bool GetFileInfo(const QString& absolutePath, FileStateInfo* foundFileInfo) const override;
bool Exists(const QString& absolutePath) const override;
bool GetHash(const QString& absolutePath, FileHash* foundHash) override;
void AddInfoSet(QSet<AssetFileInfo> infoSet) override;
void AddFile(const QString& absolutePath) override;
void UpdateFile(const QString& absolutePath) override;
void RemoveFile(const QString& absolutePath) override;
private:
/// Invalidates the hash for a file so it will be re-computed next time it's requested
void InvalidateHash(const QString& absolutePath);
/// Handles converting a file path into a uniform format for use as a map key
QString PathToKey(const QString& absolutePath) const;
/// Add/Update a single file
void AddOrUpdateFileInternal(QFileInfo fileInfo);
/// Recursively collects all the files contained in the directory specified by absolutePath
void ScanFolder(const QString& absolutePath);
mutable AZStd::recursive_mutex m_mapMutex;
QHash<QString, FileStateInfo> m_fileInfoMap;
QHash<QString, FileHash> m_fileHashMap;
using LockGuardType = AZStd::lock_guard<decltype(m_mapMutex)>;
};
/// Pass through version of the FileStateCache which does not cache anything. Every request is redirected to the OS
class FileStatePassthrough final :
public FileStateBase
{
public:
// FileStateRequestBus implementation
bool GetFileInfo(const QString& absolutePath, FileStateInfo* foundFileInfo) const override;
bool Exists(const QString& absolutePath) const override;
bool GetHash(const QString& absolutePath, FileHash* foundHash) override;
};
} // namespace AssetProcessor
@@ -0,0 +1,619 @@
/*
* 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 "PathDependencyManager.h"
#include <AzCore/std/string/wildcard.h>
#include <AzCore/Asset/AssetCommon.h>
#include <utilities/PlatformConfiguration.h>
#include <utilities/assetUtils.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace AssetProcessor
{
void SanitizeForDatabase(AZStd::string& str)
{
// Not calling normalize because wildcards should be preserved.
AZStd::to_lower(str.begin(), str.end());
AZStd::replace(str.begin(), str.end(), AZ_WRONG_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR);
AzFramework::StringFunc::Replace(str, AZ_DOUBLE_CORRECT_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR_STRING);
}
PathDependencyManager::PathDependencyManager(AZStd::shared_ptr<AssetDatabaseConnection> stateData, PlatformConfiguration* platformConfig)
: m_stateData(stateData), m_platformConfig(platformConfig)
{
}
void PathDependencyManager::SaveUnresolvedDependenciesToDatabase(AssetBuilderSDK::ProductPathDependencySet& unresolvedDependencies, const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& productEntry, const AZStd::string& platform)
{
using namespace AzToolsFramework::AssetDatabase;
ProductDependencyDatabaseEntryContainer dependencyContainer;
for (const auto& unresolvedPathDep : unresolvedDependencies)
{
auto dependencyType = unresolvedPathDep.m_dependencyType == AssetBuilderSDK::ProductPathDependencyType::SourceFile ?
ProductDependencyDatabaseEntry::ProductDep_SourceFile :
ProductDependencyDatabaseEntry::ProductDep_ProductFile;
ProductDependencyDatabaseEntry placeholderDependency(
productEntry.m_productID,
AZ::Uuid::CreateNull(),
0,
AZStd::bitset<64>(),
platform,
0,
// Use a string that will make it easy to route errors back here correctly. An empty string can be a symptom of many
// other problems. This string says that something went wrong in this function.
AZStd::string("INVALID_PATH"),
dependencyType);
AZStd::string path = AssetUtilities::NormalizeFilePath(unresolvedPathDep.m_dependencyPath.c_str()).toUtf8().constData();
bool isExactDependency = IsExactDependency(path);
if (isExactDependency && unresolvedPathDep.m_dependencyType == AssetBuilderSDK::ProductPathDependencyType::SourceFile)
{
QString relativePath, scanFolder;
if (!AzFramework::StringFunc::Path::IsRelative(path.c_str()))
{
if (m_platformConfig->ConvertToRelativePath(QString::fromUtf8(path.c_str()), relativePath, scanFolder, true))
{
auto* scanFolderInfo = m_platformConfig->GetScanFolderByPath(scanFolder);
path = ToScanFolderPrefixedPath(aznumeric_cast<int>(scanFolderInfo->ScanFolderID()), relativePath.toUtf8().constData());
}
}
}
SanitizeForDatabase(path);
placeholderDependency.m_unresolvedPath = path;
dependencyContainer.push_back(placeholderDependency);
}
if(!m_stateData->UpdateProductDependencies(dependencyContainer))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to save unresolved dependencies to database for product %d (%s)",
productEntry.m_productID, productEntry.m_productName.c_str());
}
}
void PathDependencyManager::SetDependencyResolvedCallback(const DependencyResolvedCallback& callback)
{
m_dependencyResolvedCallback = callback;
}
bool PathDependencyManager::IsExactDependency(AZStd::string_view path)
{
return path.find('*') == AZStd::string_view::npos;
}
void PathDependencyManager::GetMatchedExclusions(
const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry,
const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& productEntry,
AZStd::vector<AZStd::pair<DependencyProductIdInfo, bool>>& excludedDependencies,
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry::DependencyType dependencyType,
const MapSet& exclusionMaps) const
{
bool handleProductDependencies = dependencyType == AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry::DependencyType::ProductDep_ProductFile;
AZStd::string_view assetName = handleProductDependencies ? productEntry.m_productName : sourceEntry.m_sourceName;
const DependencyProductMap& excludedPathDependencyIds = handleProductDependencies ? exclusionMaps.m_productPathDependencyIds : exclusionMaps.m_sourcePathDependencyIds;
const DependencyProductMap& excludedWildcardPathDependencyIds = handleProductDependencies ? exclusionMaps.m_wildcardProductPathDependencyIds : exclusionMaps.m_wildcardSourcePathDependencyIds;
// strip path of /<platform>/<project>/
AZStd::string strippedPath = handleProductDependencies ? StripPlatformAndProject(assetName) : sourceEntry.m_sourceName;
SanitizeForDatabase(strippedPath);
auto unresolvedIter = excludedPathDependencyIds.find(ExcludedDependenciesSymbol + strippedPath);
if (unresolvedIter != excludedPathDependencyIds.end())
{
for (const auto& dependencyProductIdInfo : unresolvedIter->second)
{
excludedDependencies.emplace_back(dependencyProductIdInfo, true); // true = is exact dependency
}
}
for (const auto& pair : excludedWildcardPathDependencyIds)
{
AZStd::string filter = pair.first.substr(1);
if (wildcard_match(filter, strippedPath))
{
for (const auto& dependencyProductIdInfo : pair.second)
{
excludedDependencies.emplace_back(dependencyProductIdInfo, false); // false = wildcard dependency
}
}
}
}
AZStd::string PathDependencyManager::StripPlatformAndProject(AZStd::string_view productName)
{
auto nextSlash = productName.find('/'); // platform/
nextSlash = productName.find('/', nextSlash + 1) + 1; // project/
return productName.substr(nextSlash, productName.size() - nextSlash);
}
PathDependencyManager::DependencyProductMap& PathDependencyManager::SelectMap(MapSet& mapSet, bool wildcard, AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry::DependencyType type)
{
const bool isSource = type == AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry::DependencyType::ProductDep_SourceFile;
if (wildcard)
{
if (isSource)
{
return mapSet.m_wildcardSourcePathDependencyIds;
}
return mapSet.m_wildcardProductPathDependencyIds;
}
if (isSource)
{
return mapSet.m_sourcePathDependencyIds;
}
return mapSet.m_productPathDependencyIds;
}
PathDependencyManager::MapSet PathDependencyManager::PopulateExclusionMaps() const
{
using namespace AzToolsFramework::AssetDatabase;
MapSet mapSet;
m_stateData->QueryProductDependencyExclusions([&mapSet](ProductDependencyDatabaseEntry& unresolvedDep)
{
DependencyProductIdInfo idPair;
idPair.m_productDependencyId = unresolvedDep.m_productDependencyID;
idPair.m_productId = unresolvedDep.m_productPK;
idPair.m_platform = unresolvedDep.m_platform;
AZStd::string path = unresolvedDep.m_unresolvedPath;
AZStd::to_lower(path.begin(), path.end());
const bool isExactDependency = IsExactDependency(path);
auto& map = SelectMap(mapSet, !isExactDependency, unresolvedDep.m_dependencyType);
map[path].push_back(AZStd::move(idPair));
return true;
});
return mapSet;
}
void PathDependencyManager::NotifyResolvedDependencies(const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencyContainer) const
{
if(!m_dependencyResolvedCallback)
{
return;
}
for (const auto& dependency : dependencyContainer)
{
AzToolsFramework::AssetDatabase::ProductDatabaseEntry productEntry;
if (!m_stateData->GetProductByProductID(dependency.m_productPK, productEntry))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to get existing product with productId %i from the database", dependency.m_productPK);
}
AzToolsFramework::AssetDatabase::SourceDatabaseEntry dependentSource;
if (!m_stateData->GetSourceByJobID(productEntry.m_jobPK, dependentSource))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to get existing product from job ID of product %i from the database", dependency.m_productPK);
}
m_dependencyResolvedCallback(AZ::Data::AssetId(dependentSource.m_sourceGuid, productEntry.m_subID), dependency);
}
}
void PathDependencyManager::SaveResolvedDependencies(const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry, const MapSet& exclusionMaps, const AZStd::string& sourceNameWithScanFolder,
const AZStd::vector<AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry>& dependencyEntries,
AZStd::string_view matchedPath, bool isSourceDependency, const AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& matchedProducts,
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencyContainer) const
{
for (const auto& productDependencyDatabaseEntry : dependencyEntries)
{
const bool isExactDependency = IsExactDependency(productDependencyDatabaseEntry.m_unresolvedPath);
AZ::s64 dependencyId = isExactDependency ? productDependencyDatabaseEntry.m_productDependencyID : AzToolsFramework::AssetDatabase::InvalidEntryId;
if(isSourceDependency && !isExactDependency && matchedPath == sourceNameWithScanFolder)
{
// Since we did a search for the source 2 different ways, filter one out
// Scanfolder-prefixes are only for exact dependencies
break;
}
for (const auto& matchedProduct : matchedProducts)
{
// Check if this match is excluded before continuing
AZStd::vector<AZStd::pair<DependencyProductIdInfo, bool>> exclusions; // bool = is exact dependency
GetMatchedExclusions(sourceEntry, matchedProduct, exclusions, productDependencyDatabaseEntry.m_dependencyType, exclusionMaps);
if(!exclusions.empty())
{
bool isExclusionForThisProduct = false;
bool isExclusionExact = false;
for (const auto& exclusionPair : exclusions)
{
if(exclusionPair.first.m_productId == productDependencyDatabaseEntry.m_productPK && exclusionPair.first.m_platform == productDependencyDatabaseEntry.m_platform)
{
isExclusionExact = exclusionPair.second;
isExclusionForThisProduct = true;
break;
}
}
if(isExclusionForThisProduct)
{
if (isExactDependency && isExclusionExact)
{
AZ_Error("PathDependencyManager", false, "Dependency exclusion found for an exact dependency. It is not valid to both include and exclude a file by the same rule. File: %s", isSourceDependency ? sourceEntry.m_sourceName.c_str() : matchedProduct.m_productName.c_str());
}
continue;
}
}
// We need to make sure this product is for the same platform the dependency is for
AzToolsFramework::AssetDatabase::JobDatabaseEntry jobEntry;
if (!m_stateData->GetJobByJobID(matchedProduct.m_jobPK, jobEntry))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to get job entry for product %s", matchedProduct.ToString().c_str());
}
if (jobEntry.m_platform != productDependencyDatabaseEntry.m_platform)
{
continue;
}
// All checks passed, this is a valid dependency we need to save to the db
dependencyContainer.push_back();
auto& entry = dependencyContainer.back();
entry.m_productDependencyID = dependencyId;
entry.m_productPK = productDependencyDatabaseEntry.m_productPK;
entry.m_dependencySourceGuid = sourceEntry.m_sourceGuid;
entry.m_dependencySubID = matchedProduct.m_subID;
entry.m_platform = productDependencyDatabaseEntry.m_platform;
// If there's more than 1 product, reset the ID so further products create new db entries
dependencyId = AzToolsFramework::AssetDatabase::InvalidEntryId;
}
}
}
void PathDependencyManager::RetryDeferredDependencies(const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry)
{
MapSet exclusionMaps = PopulateExclusionMaps();
// Gather a list of all the products this source file produced
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
if (!m_stateData->GetProductsBySourceName(sourceEntry.m_sourceName.c_str(), products))
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Source %s did not have any products. Skipping dependency processing.\n", sourceEntry.m_sourceName.c_str());
return;
}
AZStd::unordered_map<AZStd::string, AZStd::vector<AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry>> map;
// Build up a list of all the paths we need to search for: products + 2 variations of the source path
AZStd::vector<AZStd::string> searchPaths;
for (const auto& productEntry : products)
{
const AZStd::string& productName = productEntry.m_productName;
// strip path of /<platform>/<project>/
AZStd::string strippedPath = StripPlatformAndProject(productName);
SanitizeForDatabase(strippedPath);
searchPaths.push_back(strippedPath);
}
AZStd::string sourceNameWithScanFolder = ToScanFolderPrefixedPath(aznumeric_cast<int>(sourceEntry.m_scanFolderPK), sourceEntry.m_sourceName.c_str());
AZStd::string sanitizedSourceName = sourceEntry.m_sourceName;
SanitizeForDatabase(sourceNameWithScanFolder);
SanitizeForDatabase(sanitizedSourceName);
searchPaths.push_back(sourceNameWithScanFolder);
searchPaths.push_back(sanitizedSourceName);
m_stateData->QueryProductDependenciesUnresolvedAdvanced(searchPaths, [&map](AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry, const AZStd::string& matchedPath)
{
map[matchedPath].push_back(AZStd::move(entry));
return true;
});
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
// Go through all the matched dependencies
for (const auto& pair : map)
{
AZStd::string_view matchedPath = pair.first;
const bool isSourceDependency = matchedPath == sanitizedSourceName || matchedPath == sourceNameWithScanFolder;
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer matchedProducts;
// Figure out the list of products to work with, for a source match, use all the products, otherwise just use the matched products
if(isSourceDependency)
{
matchedProducts = products;
}
else
{
for (const auto& productEntry : products)
{
const AZStd::string& productName = productEntry.m_productName;
// strip path of /<platform>/<project>/
AZStd::string strippedPath = StripPlatformAndProject(productName);
SanitizeForDatabase(strippedPath);
if(strippedPath == matchedPath)
{
matchedProducts.push_back(productEntry);
}
}
}
// Go through each dependency we're resolving and create a db entry for each product that resolved it (wildcard/source dependencies will generally create more than 1)
SaveResolvedDependencies(sourceEntry, exclusionMaps, sourceNameWithScanFolder, pair.second, matchedPath, isSourceDependency, matchedProducts, dependencyContainer);
}
// Save everything to the db
if(!m_stateData->UpdateProductDependencies(dependencyContainer))
{
AZ_Error("PathDependencyManager", false, "Failed to update product dependencies");
}
else
{
// Send a notification for each dependency that has been resolved
NotifyResolvedDependencies(dependencyContainer);
}
}
void CleanupPathDependency(AssetBuilderSDK::ProductPathDependency& pathDependency)
{
if(pathDependency.m_dependencyType == AssetBuilderSDK::ProductPathDependencyType::SourceFile)
{
// Nothing to cleanup if the dependency type was already pointing at source.
return;
}
// Many workflows use source and product extensions for textures interchangeably, assuming that a later system will clean up the path.
// Multiple systems use the AZ Serialization system to reference assets and collect these asset references. Not all of these systems
// check if the references are to source or product asset types.
// Instead requiring each of these systems to handle this (and failing in hard to track down ways later when they don't), check here, and clean things up.
const AZStd::vector<AZStd::string> sourceImageExtensions = { ".tif", ".tiff", ".bmp", ".gif", ".jpg", ".jpeg", ".tga", ".png" };
for (const AZStd::string& sourceImageExtension : sourceImageExtensions)
{
if (AzFramework::StringFunc::Path::IsExtension(pathDependency.m_dependencyPath.c_str(), sourceImageExtension.c_str()))
{
// This was a source format image reported initially as a product file dependency. Fix that to be a source file dependency.
pathDependency.m_dependencyType = AssetBuilderSDK::ProductPathDependencyType::SourceFile;
break;
}
}
}
void PathDependencyManager::ResolveDependencies(AssetBuilderSDK::ProductPathDependencySet& pathDeps, AZStd::vector<AssetBuilderSDK::ProductDependency>& resolvedDeps, const AZStd::string& platform, [[maybe_unused]] const AZStd::string& productName)
{
const AZ::Data::ProductDependencyInfo::ProductDependencyFlags productDependencyFlags =
AZ::Data::ProductDependencyInfo::CreateFlags(AZ::Data::AssetLoadBehavior::NoLoad);
const QString gameName = AssetUtilities::ComputeGameName();
AZStd::vector<AssetBuilderSDK::ProductDependency> excludedDeps;
// Check the path dependency set and find any conflict (include and exclude the same path dependency)
AssetBuilderSDK::ProductPathDependencySet conflicts;
for (const AssetBuilderSDK::ProductPathDependency& pathDep : pathDeps)
{
auto conflictItr = find_if(pathDeps.begin(), pathDeps.end(),
[&pathDep](const AssetBuilderSDK::ProductPathDependency& pathDepForComparison)
{
return (pathDep.m_dependencyPath == ExcludedDependenciesSymbol + pathDepForComparison.m_dependencyPath ||
pathDepForComparison.m_dependencyPath == ExcludedDependenciesSymbol + pathDep.m_dependencyPath) &&
pathDep.m_dependencyType == pathDepForComparison.m_dependencyType;
});
if (conflictItr != pathDeps.end())
{
conflicts.insert(pathDep);
}
}
auto pathIter = pathDeps.begin();
while (pathIter != pathDeps.end())
{
if (conflicts.find(*pathIter) != conflicts.end())
{
// Ignore conflicted path dependencies
AZ_Error(AssetProcessor::DebugChannel, false,
"Cannot resolve path dependency %s for product %s since there's a conflict\n",
pathIter->m_dependencyPath.c_str(), productName.c_str());
++pathIter;
continue;
}
AssetBuilderSDK::ProductPathDependency cleanedupDependency(*pathIter);
CleanupPathDependency(cleanedupDependency);
AZStd::string dependencyPathSearch = cleanedupDependency.m_dependencyPath;
bool isExcludedDependency = dependencyPathSearch.starts_with(ExcludedDependenciesSymbol);
dependencyPathSearch = isExcludedDependency ? dependencyPathSearch.substr(1) : dependencyPathSearch;
bool isExactDependency = !AzFramework::StringFunc::Replace(dependencyPathSearch, '*', '%');
SanitizeForDatabase(dependencyPathSearch);
if (cleanedupDependency.m_dependencyType == AssetBuilderSDK::ProductPathDependencyType::ProductFile)
{
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer productInfoContainer;
QString productNameWithPlatform = QString("%1%2%3").arg(platform.c_str(), AZ_CORRECT_DATABASE_SEPARATOR_STRING, dependencyPathSearch.c_str());
QString productNameWithPlatformAndGameName = QString("%1%2%3%2%4").arg(platform.c_str(), AZ_CORRECT_DATABASE_SEPARATOR_STRING, gameName, dependencyPathSearch.c_str());
if (AzFramework::StringFunc::Equal(productNameWithPlatformAndGameName.toUtf8().data(), productName.c_str()))
{
AZ_Warning(AssetProcessor::ConsoleChannel, false,
"Invalid dependency: Product Asset ( %s ) has listed itself as one of its own Product Dependencies.",
productName.c_str());
pathIter = pathDeps.erase(pathIter);
continue;
}
if (isExactDependency)
{
m_stateData->GetProductsByProductName(productNameWithPlatformAndGameName, productInfoContainer);
// Not all products will be in the game subfolder.
// Items in dev, like bootstrap.cfg, end up in just the root platform folder.
// These two checks search for products in both location.
// Example: If a path dependency was just "bootstrap.cfg" in SamplesProject on PC, this would search both
// "cache/SamplesProject/pc/bootstrap.cfg" and "cache/SamplesProject/pc/SamplesProject/bootstrap.cfg".
m_stateData->GetProductsByProductName(productNameWithPlatform, productInfoContainer);
}
else
{
m_stateData->GetProductsLikeProductName(productNameWithPlatformAndGameName, AzToolsFramework::AssetDatabase::AssetDatabaseConnection::LikeType::Raw, productInfoContainer);
}
// See if path matches any product files
if (!productInfoContainer.empty())
{
AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceDatabaseEntry;
for (const auto& productDatabaseEntry : productInfoContainer)
{
if (m_stateData->GetSourceByJobID(productDatabaseEntry.m_jobPK, sourceDatabaseEntry))
{
AZStd::vector<AssetBuilderSDK::ProductDependency>& productDependencyList = isExcludedDependency ? excludedDeps : resolvedDeps;
productDependencyList.emplace_back(AZ::Data::AssetId(sourceDatabaseEntry.m_sourceGuid, productDatabaseEntry.m_subID), productDependencyFlags);
}
else
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Source for JobID %i not found (from product %s)", productDatabaseEntry.m_jobPK, dependencyPathSearch.c_str());
}
// For exact dependencies we expect that there is only 1 match. Even if we processed more than 1, the results could be inconsistent since the other assets may not be finished processing yet
if (isExactDependency)
{
break;
}
}
// Wildcard and excluded dependencies never get removed since they can be fulfilled by a future product
if (isExactDependency && !isExcludedDependency)
{
pathIter = pathDeps.erase(pathIter);
continue;
}
}
}
else
{
// See if path matches any source files
AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer sourceInfoContainer;
if (isExactDependency)
{
QString databaseName;
QString scanFolder;
if (ProcessInputPathToDatabasePathAndScanFolder(dependencyPathSearch.c_str(), databaseName, scanFolder))
{
m_stateData->GetSourcesBySourceNameScanFolderId(databaseName, m_platformConfig->GetScanFolderByPath(scanFolder)->ScanFolderID(), sourceInfoContainer);
}
}
else
{
m_stateData->GetSourcesLikeSourceName(dependencyPathSearch.c_str(), AzToolsFramework::AssetDatabase::AssetDatabaseConnection::LikeType::Raw, sourceInfoContainer);
}
if (!sourceInfoContainer.empty())
{
bool productsAvailable = false;
for (const auto& sourceDatabaseEntry : sourceInfoContainer)
{
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer productInfoContainer;
if (m_stateData->GetProductsBySourceID(sourceDatabaseEntry.m_sourceID, productInfoContainer, AZ::Uuid::CreateNull(), "", platform.c_str()))
{
productsAvailable = true;
// Add a dependency on every product of this source file
for (const auto& productDatabaseEntry : productInfoContainer)
{
AZStd::vector<AssetBuilderSDK::ProductDependency>& productDependencyList = isExcludedDependency ? excludedDeps : resolvedDeps;
productDependencyList.emplace_back(AZ::Data::AssetId(sourceDatabaseEntry.m_sourceGuid, productDatabaseEntry.m_subID), productDependencyFlags);
}
}
// For exact dependencies we expect that there is only 1 match. Even if we processed more than 1, the results could be inconsistent since the other assets may not be finished processing yet
if (isExactDependency)
{
break;
}
}
if (isExactDependency && productsAvailable && !isExcludedDependency)
{
pathIter = pathDeps.erase(pathIter);
continue;
}
}
}
pathIter->m_dependencyPath = cleanedupDependency.m_dependencyPath;
pathIter->m_dependencyType = cleanedupDependency.m_dependencyType;
++pathIter;
}
// Remove the excluded dependency from the resolved dependency list and leave them unresolved
resolvedDeps.erase(AZStd::remove_if(resolvedDeps.begin(), resolvedDeps.end(),
[&excludedDeps](const AssetBuilderSDK::ProductDependency& resolvedDependency)
{
auto excludedDependencyItr = AZStd::find_if(excludedDeps.begin(), excludedDeps.end(),
[&resolvedDependency](const AssetBuilderSDK::ProductDependency& excludedDependency)
{
return resolvedDependency.m_dependencyId == excludedDependency.m_dependencyId &&
resolvedDependency.m_flags == excludedDependency.m_flags;
});
return excludedDependencyItr != excludedDeps.end();
}), resolvedDeps.end());
}
bool PathDependencyManager::ProcessInputPathToDatabasePathAndScanFolder(const char* dependencyPathSearch, QString& databaseName, QString& scanFolder) const
{
if (!AzFramework::StringFunc::Path::IsRelative(dependencyPathSearch))
{
// absolute paths just get converted directly
return m_platformConfig->ConvertToRelativePath(QString::fromUtf8(dependencyPathSearch), databaseName, scanFolder);
}
else
{
// relative paths get the first matching asset, and then they get the usual call.
QString absolutePath = m_platformConfig->FindFirstMatchingFile(QString::fromUtf8(dependencyPathSearch));
if (!absolutePath.isEmpty())
{
return m_platformConfig->ConvertToRelativePath(absolutePath, databaseName, scanFolder);
}
}
return false;
}
AZStd::string PathDependencyManager::ToScanFolderPrefixedPath(int scanFolderId, const char* relativePath) const
{
static constexpr char ScanFolderSeparator = '$';
return AZStd::string::format("%c%d%c%s", ScanFolderSeparator, scanFolderId, ScanFolderSeparator, relativePath);
}
} // namespace AssetProcessor
@@ -0,0 +1,104 @@
/*
* 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/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/string.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
#include <native/AssetManager/assetProcessorManager.h>
class QString;
namespace AssetProcessor
{
class PlatformConfiguration;
class AssetDatabaseConnection;
const char ExcludedDependenciesSymbol = ':';
/// Handles resolving and saving product path dependencies
class PathDependencyManager
{
public:
// The two Ids needed for a ProductDependency entry, and platform. Used for saving ProductDependencies that are pending resolution
struct DependencyProductIdInfo
{
AZ::s64 m_productId{};
AZ::s64 m_productDependencyId{};
AZStd::string m_platform;
};
using DependencyProductMap = AZStd::unordered_map<AZStd::string, AZStd::vector<DependencyProductIdInfo>>;
PathDependencyManager(AZStd::shared_ptr<AssetDatabaseConnection> stateData, PlatformConfiguration* platformConfig);
/// This function is responsible for looking up existing, unresolved dependencies that the current asset satisfies.
/// These can be dependencies on either the source asset or one of the product assets
void RetryDeferredDependencies(const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry);
/// This function is responsible for taking the path dependencies output by the current asset and trying to resolve them to AssetIds
/// This does not look for dependencies that the current asset satisfies.
void ResolveDependencies(AssetBuilderSDK::ProductPathDependencySet& pathDeps, AZStd::vector<AssetBuilderSDK::ProductDependency>& resolvedDeps, const AZStd::string& platform, const AZStd::string& productName);
/// Saves a product's unresolved dependencies to the database
void SaveUnresolvedDependenciesToDatabase(AssetBuilderSDK::ProductPathDependencySet& unresolvedDependencies, const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& productEntry, const AZStd::string& platform);
using DependencyResolvedCallback = AZStd::function<void(const AZ::Data::AssetId&, const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry&)>;
void SetDependencyResolvedCallback(const DependencyResolvedCallback& callback);
private:
struct MapSet
{
DependencyProductMap m_sourcePathDependencyIds;
DependencyProductMap m_productPathDependencyIds;
DependencyProductMap m_wildcardSourcePathDependencyIds;
DependencyProductMap m_wildcardProductPathDependencyIds;
};
MapSet PopulateExclusionMaps() const;
void NotifyResolvedDependencies(const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencyContainer) const;
void SaveResolvedDependencies(const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry, const MapSet& exclusionMaps, const AZStd::string& sourceNameWithScanFolder, const AZStd::vector<AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry>& dependencyEntries, AZStd::string_view matchedPath, bool isSourceDependency, const AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& matchedProducts, AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencyContainer) const;
static DependencyProductMap& SelectMap(MapSet& mapSet, bool wildcard, AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry::DependencyType type);
/// Returns false if a path contains wildcards, true otherwise
static bool IsExactDependency(AZStd::string_view path);
/// Removes /platform/project/ from the start of a product path
static AZStd::string StripPlatformAndProject(AZStd::string_view relativeProductPath);
/// Prefixes the scanFolderId to the relativePath
AZStd::string ToScanFolderPrefixedPath(int scanFolderId, const char* relativePath) const;
/// Takes a path and breaks it into a database-prefixed relative path and scanFolder path
/// This function can accept an absolute source path, an un-prefixed relative path, and a prefixed relative path
/// The file returned will be the first one matched based on scanfolder priority
bool ProcessInputPathToDatabasePathAndScanFolder(const char* dependencyPathSearch, QString& databaseName, QString& scanFolder) const;
/// Gets any matched dependency exclusions
/// @param sourceEntry source database entry corresponds to the newly finished product
/// @param productEntry product database entry corresponds to the newly finished product
/// @param excludedDependencies dependencies that should be ignored even if their file paths match any existing wildcard pattern
/// @param dependencyType type of the dependencies we are handling
/// @param exclusionMaps MapSet containing all the path dependency exclusions
void GetMatchedExclusions(const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry, const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& productEntry,
AZStd::vector<AZStd::pair<DependencyProductIdInfo, bool>>& excludedDependencies, AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry::DependencyType dependencyType,
const MapSet& exclusionMaps) const;
AZStd::shared_ptr<AssetDatabaseConnection> m_stateData;
PlatformConfiguration* m_platformConfig{};
DependencyResolvedCallback m_dependencyResolvedCallback{};
};
} // namespace AssetProcessor
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,241 @@
/*
* 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 <QHash>
#include <AzCore/base.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <utility>
#include "AzCore/EBus/EBus.h"
#include "AzCore/Interface/Interface.h"
#include "AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h"
#include <utilities/PlatformConfiguration.h>
#include "AssetDatabase/AssetDatabase.h"
// This needs to be up here so it is declared before the hash, which needs to be declared before the first usage
namespace AssetProcessor
{
struct FileUpdateTask
{
FileUpdateTask(AZStd::vector<AZStd::string> oldString, AZStd::vector<AZStd::string> newString, AZStd::string absPathFileToUpdate, bool isAssetIdReference, bool skipTask)
: m_oldStrings(AZStd::move(oldString)),
m_newStrings(AZStd::move(newString)),
m_absPathFileToUpdate(AZStd::move(absPathFileToUpdate)),
m_isAssetIdReference(isAssetIdReference),
m_skipTask(skipTask)
{
}
bool operator==(const FileUpdateTask& rhs) const
{
return m_isAssetIdReference == rhs.m_isAssetIdReference
&& m_absPathFileToUpdate == rhs.m_absPathFileToUpdate
&& m_oldStrings == rhs.m_oldStrings
&& m_newStrings == rhs.m_newStrings;
}
AZStd::vector<AZStd::string> m_oldStrings; // The old path or uuid strings to search for
AZStd::vector<AZStd::string> m_newStrings; // The new path or uuid strings to replace
AZStd::string m_absPathFileToUpdate;
bool m_isAssetIdReference = false;
bool m_succeeded = false;
bool m_skipTask = false;
};
}
namespace AZStd
{
template<>
struct hash<AssetProcessor::FileUpdateTask>
{
size_t operator()(const AssetProcessor::FileUpdateTask& obj) const
{
size_t h = 0;
hash_combine(h, obj.m_isAssetIdReference);
hash_combine(h, obj.m_absPathFileToUpdate);
hash_range(h, obj.m_oldStrings.begin(), obj.m_oldStrings.end());
hash_range(h, obj.m_newStrings.begin(), obj.m_newStrings.end());
return h;
}
};
}
namespace AssetProcessor
{
enum class SourceFileRelocationStatus
{
None,
Failed,
Succeeded
};
static constexpr int SourceFileRelocationInvalidIndex = -1;
struct SourceFileRelocationInfo
{
SourceFileRelocationInfo(AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry, AZStd::unordered_map<int, AzToolsFramework::AssetDatabase::ProductDatabaseEntry> products, const AZStd::string& oldRelativePath, const ScanFolderInfo* scanFolder)
: m_sourceEntry(AZStd::move(sourceEntry)),
m_products(AZStd::move(products)),
m_oldRelativePath(oldRelativePath)
{
AzFramework::StringFunc::Path::ConstructFull(scanFolder->ScanPath().toUtf8().constData(), m_oldRelativePath.c_str(), m_oldAbsolutePath, false);
m_oldAbsolutePath = AssetUtilities::NormalizeFilePath(m_oldAbsolutePath.c_str()).toUtf8().constData();
}
SourceFileRelocationInfo(const AZStd::string& filePath, const ScanFolderInfo* scanFolder)
{
QString relFilePath;
PlatformConfiguration::ConvertToRelativePath(filePath.c_str(), scanFolder, relFilePath, true);
m_oldRelativePath = relFilePath.toUtf8().data();
AzFramework::StringFunc::Path::ConstructFull(scanFolder->ScanPath().toUtf8().constData(), m_oldRelativePath.c_str(), m_oldAbsolutePath, false);
m_oldAbsolutePath = AssetUtilities::NormalizeFilePath(m_oldAbsolutePath.c_str()).toUtf8().constData();
}
AzToolsFramework::AssetDatabase::SourceDatabaseEntry m_sourceEntry;
AZStd::unordered_map<int, AzToolsFramework::AssetDatabase::ProductDatabaseEntry> m_products; // Key = product SubId
AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer m_sourceDependencyEntries;
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer m_productDependencyEntries;
AZ::Uuid m_newUuid;
AZStd::string m_oldRelativePath;
AZStd::string m_newRelativePath;
AZStd::string m_oldAbsolutePath;
AZStd::string m_newAbsolutePath;
bool m_hasPathDependencies = false;
SourceFileRelocationStatus m_operationStatus = SourceFileRelocationStatus::None;
bool m_isMetaDataFile = false;
// This is a cached index of the SourceFile in the SourceFileRelocationContainer.
// This is only used by the metadata file to determine the destination path if needed.
int m_sourceFileIndex = AssetProcessor::SourceFileRelocationInvalidIndex;
};
using SourceFileRelocationContainer = AZStd::vector<SourceFileRelocationInfo>;
using FileUpdateTasks = AZStd::unordered_set<FileUpdateTask>;
struct MoveFailure
{
MoveFailure(AZStd::string reason, bool dependencyFailure)
: m_reason(AZStd::move(reason)),
m_dependencyFailure(dependencyFailure)
{
}
AZStd::string m_reason;
bool m_dependencyFailure{};
};
struct RelocationSuccess
{
RelocationSuccess() = default;
RelocationSuccess(int moveSuccessCount, int moveFailureCount, int moveTotalCount, int updateSuccessCount, int updateFailureCount, int updateTotalCount, SourceFileRelocationContainer sourceFileRelocationInfos, FileUpdateTasks fileUpdateTasks)
: m_moveSuccessCount(moveSuccessCount),
m_moveFailureCount(moveFailureCount),
m_moveTotalCount(moveTotalCount),
m_updateSuccessCount(updateSuccessCount),
m_updateFailureCount(updateFailureCount),
m_updateTotalCount(updateTotalCount),
m_relocationContainer(AZStd::move(sourceFileRelocationInfos)),
m_updateTasks(AZStd::move(fileUpdateTasks))
{
}
int m_moveSuccessCount{};
int m_moveFailureCount{};
int m_moveTotalCount{};
int m_updateSuccessCount{};
int m_updateFailureCount{};
int m_updateTotalCount{};
SourceFileRelocationContainer m_relocationContainer;
FileUpdateTasks m_updateTasks;
};
class ISourceFileRelocation
{
public:
AZ_RTTI(ISourceFileRelocation, "{FEDD188E-D5FF-4852-B945-F82F7CC1CA5F}");
ISourceFileRelocation() = default;
virtual ~ISourceFileRelocation() = default;
//! Moves source files or renames a file. Source and destination can be absolute paths or scanfolder relative paths. Wildcards are supported for source.
//! By default no changes are made to the disk. Set previewOnly to false to actually move files.
//! If allowDependencyBreaking is false, the move will fail if moving any files will break existing dependencies. Set to true to ignore and move anyway.
virtual AZ::Outcome<RelocationSuccess, MoveFailure> Move(const AZStd::string& source, const AZStd::string& destination, bool previewOnly = true, bool allowDependencyBreaking = false, bool removeEmptyFolders = true, bool updateReferences = false, bool excludeMetaDataFiles = false) = 0;
//! Deletes source files. Source can be an absolute path or a scanfolder relative path. Wildcards are supported.
//! By default no changes are made to the disk. Set previewOnly to false to actually delete files.
//! If allowDependencyBreaking is false, the delete will fail if deleting any file breaks existing dependencies. Set to true to ignore and delete anyway.
virtual AZ::Outcome<RelocationSuccess, AZStd::string> Delete(const AZStd::string& source, bool previewOnly = true, bool allowDependencyBreaking = false, bool removeEmptyFolders = true, bool excludeMetaDataFiles = false) = 0;
//! Takes a relocation set and builds a string report to output the result of what files will change and what dependencies will break
virtual AZStd::string BuildReport(const SourceFileRelocationContainer& relocationEntries, const FileUpdateTasks& updateTasks, bool isMove, bool updateReference) const = 0;
AZ_DISABLE_COPY_MOVE(ISourceFileRelocation);
};
class SourceFileRelocator
: public ISourceFileRelocation
{
public:
SourceFileRelocator(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> stateData, PlatformConfiguration* platformConfiguration);
~SourceFileRelocator();
static AZStd::string RemoveDatabasePrefix(const ScanFolderInfo* scanFolder, AZStd::string sourceName);
static void MakePathRelative(const AZStd::string& parentPath, const AZStd::string& childPath, AZStd::string& parentRelative, AZStd::string& childRelative);
static AZ::Outcome<AZStd::string, AZStd::string> HandleWildcard(AZStd::string_view absFile, AZStd::string_view absSearch, AZStd::string destination);
static void FixDestinationMissingFilename(AZStd::string& destination, const AZStd::string& source);
// Takes a relocation set, scanfolder, source, and destination and calculates the new file path of every file
AZ::Outcome<void, AZStd::string> ComputeDestination(SourceFileRelocationContainer& relocationContainer, const ScanFolderInfo* sourceScanFolder, const AZStd::string& source, AZStd::string destination, const ScanFolderInfo*& destinationScanFolderOut) const;
// Takes a QStringList of paths and populates sources with all the corresponding source database entries
QHash<QString, int> GetSources(QStringList pathMatches, const ScanFolderInfo* scanFolderInfo, SourceFileRelocationContainer& sources) const;
// Takes a QStringList of paths and populates metadata files.
void HandleMetaDataFiles(QStringList pathMatches, QHash<QString, int>& pathIndexMap, const ScanFolderInfo* scanFolderInfo, SourceFileRelocationContainer& metadataFiles, bool excludeMetaDataFiles) const;
// Returns a map of SubId -> ProductEntry for all the products of a source
AZStd::unordered_map<int, AzToolsFramework::AssetDatabase::ProductDatabaseEntry> GetProductMapForSource(AZ::s64 sourceId) const;
bool GetFilesFromSourceControl(SourceFileRelocationContainer& sources, const ScanFolderInfo* scanFolderInfo, QString absolutePath, bool excludeMetaDataFiles = false) const;
// Populates a relocation set with all direct source and product dependency database entries for every file
void PopulateDependencies(SourceFileRelocationContainer& relocationContainer) const;
// Gets the scanfolder and relative path given an input of an absolute or relative path (wildcard paths not supported). Fails if the source path is not within a scanfolder or can't be made relative
AZ::Outcome<void, AZStd::string> GetScanFolderAndRelativePath(const AZStd::string& normalizedSource, bool allowNonexistentPath, const ScanFolderInfo*& scanFolderInfo, AZStd::string& relativePath) const;
// Given a path, populates a relocation set with all source files that match. Will fail if a scanfolder itself is selected or the source string matches files from multiple scanfolders
AZ::Outcome<void, AZStd::string> GetSourcesByPath(const AZStd::string& normalizedSource, SourceFileRelocationContainer& sources, const ScanFolderInfo*& scanFolderInfoOut, bool excludeMetaDataFiles = false) const;
int DoSourceControlMoveFiles(AZStd::string normalizedSource, AZStd::string normalizedDestination, SourceFileRelocationContainer& relocationContainer, const ScanFolderInfo* sourceScanFolderInfo, const ScanFolderInfo* destinationScanFolderInfo, bool removeEmptyFolders) const;
int DoSourceControlDeleteFiles(AZStd::string normalizedSource, SourceFileRelocationContainer& relocationContainer, const ScanFolderInfo* sourceScanFolderInfo, bool removeEmptyFolders) const;
static bool UpdateFileReferences(const FileUpdateTask& updateTask);
bool ComputeProductDependencyUpdatePaths(const SourceFileRelocationInfo& relocationInfo, const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& productDependency, AZStd::vector<AZStd::string>& oldPaths, AZStd::vector<AZStd::string>& newPaths, AZStd::string& absPathFileToUpdate) const;
FileUpdateTasks UpdateReferences(const SourceFileRelocationContainer& relocationContainer, bool useSourceControl) const;
// ISourceFileRelocation implementation
AZ::Outcome<RelocationSuccess, MoveFailure> Move(const AZStd::string& source, const AZStd::string& destination, bool previewOnly = true, bool allowDependencyBreaking = false, bool removeEmptyFolders = true, bool updateReferences = false, bool excludeMetaDataFiles = false) override;
AZ::Outcome<RelocationSuccess, AZStd::string> Delete(const AZStd::string& source, bool previewOnly = true, bool allowDependencyBreaking = false, bool removeEmptyFolders = true, bool excludeMetaDataFiles = false) override;
AZStd::string BuildReport(const SourceFileRelocationContainer& relocationEntries, const FileUpdateTasks& updateTasks, bool isMove, bool updateReference) const override;
private:
AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> m_stateData;
PlatformConfiguration* m_platformConfig;
AZStd::unordered_map<AZStd::string, AZStd::string> m_additionalHelpTextMap;
};
} // namespace AssetProcessor
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,552 @@
/*
* 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 <QString>
#include <QByteArray>
#include <QQueue>
#include <QVector>
#include <QHash>
#include <QDir>
#include <QSet>
#include <QMap>
#include <QPair>
#include <QMutex>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
#include "native/assetprocessor.h"
#include "native/utilities/AssetUtilEBusHelper.h"
#include "native/utilities/MissingDependencyScanner.h"
#include "native/utilities/ThreadHelper.h"
#include "native/AssetManager/AssetCatalog.h"
#include "native/AssetDatabase/AssetDatabase.h"
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/containers/map.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include "AssetRequestHandler.h"
#include "native/utilities/JobDiagnosticTracker.h"
#include "SourceFileRelocator.h"
#endif
class FileWatcher;
namespace AzFramework
{
namespace AssetSystem
{
class BaseAssetProcessorMessage;
class GetRelativeProductPathFromFullSourceOrProductPathRequest;
class GetRelativeProductPathFromFullSourceOrProductPathResponse;
class GetFullSourcePathFromRelativeProductPathRequest;
class GetFullSourcePathFromRelativeProductPathResponse;
class AssetNotificationMessage;
} // namespace AssetSystem
} // namespace AzFramework
namespace AzToolsFramework
{
namespace AssetSystem
{
class AssetJobLogRequest;
class AssetJobLogResponse;
class AssetJobsInfoRequest;
class AssetJobsInfoResponse;
class GetAbsoluteAssetDatabaseLocationRequest;
class GetAbsoluteAssetDatabaseLocationResponse;
} // namespace AssetSystem
} // namespace AzToolsFramework
namespace AssetProcessor
{
class AssetProcessingStateData;
struct AssetRecognizer;
class PlatformConfiguration;
class ScanFolderInfo;
class PathDependencyManager;
//! The Asset Processor Manager is the heart of the pipeline
//! It is what makes the critical decisions about what should and should not be processed
//! It emits signals when jobs need to be performed and when assets are complete or have failed.
class AssetProcessorManager
: public QObject
, public AssetProcessor::ProcessingJobInfoBus::Handler
{
using BaseAssetProcessorMessage = AzFramework::AssetSystem::BaseAssetProcessorMessage;
using AssetJobsInfoRequest = AzToolsFramework::AssetSystem::AssetJobsInfoRequest;
using AssetJobsInfoResponse = AzToolsFramework::AssetSystem::AssetJobsInfoResponse;
using JobInfo = AzToolsFramework::AssetSystem::JobInfo;
using JobStatus = AzToolsFramework::AssetSystem::JobStatus;
using AssetJobLogRequest = AzToolsFramework::AssetSystem::AssetJobLogRequest;
using AssetJobLogResponse = AzToolsFramework::AssetSystem::AssetJobLogResponse;
using GetAbsoluteAssetDatabaseLocationRequest = AzToolsFramework::AssetSystem::GetAbsoluteAssetDatabaseLocationRequest;
using GetAbsoluteAssetDatabaseLocationResponse = AzToolsFramework::AssetSystem::GetAbsoluteAssetDatabaseLocationResponse;
using GetRelativeProductPathFromFullSourceOrProductPathRequest = AzFramework::AssetSystem::GetRelativeProductPathFromFullSourceOrProductPathRequest;
using GetRelativeProductPathFromFullSourceOrProductPathResponse = AzFramework::AssetSystem::GetRelativeProductPathFromFullSourceOrProductPathResponse;
using GetFullSourcePathFromRelativeProductPathRequest = AzFramework::AssetSystem::GetFullSourcePathFromRelativeProductPathRequest;
using GetFullSourcePathFromRelativeProductPathResponse = AzFramework::AssetSystem::GetFullSourcePathFromRelativeProductPathResponse;
Q_OBJECT
private:
struct FileEntry
{
QString m_fileName;
bool m_isDelete = false;
bool m_isFromScanner = false;
FileEntry() = default;
FileEntry(const QString& fileName, bool isDelete, bool isFromScanner=false)
: m_fileName(fileName)
, m_isDelete(isDelete)
, m_isFromScanner(isFromScanner)
{
}
};
struct AssetProcessedEntry
{
JobEntry m_entry;
AssetBuilderSDK::ProcessJobResponse m_response;
AssetProcessedEntry() = default;
AssetProcessedEntry(JobEntry& entry, AssetBuilderSDK::ProcessJobResponse& response)
: m_entry(AZStd::move(entry))
, m_response(AZStd::move(response))
{
}
AssetProcessedEntry(const AssetProcessedEntry& other) = default;
AssetProcessedEntry(AssetProcessedEntry&& other)
: m_entry(AZStd::move(other.m_entry))
, m_response(AZStd::move(other.m_response))
{
}
AssetProcessedEntry& operator=(AssetProcessedEntry&& other)
{
if (this != &other)
{
m_entry = AZStd::move(other.m_entry);
m_response = AZStd::move(other.m_response);
}
return *this;
}
};
//! Internal structure that will hold all the necessary source info
struct SourceFileInfo
{
QString m_databasePath; // clarification: this is the database path (ie, includes outputprefix)
QString m_pathRelativeToScanFolder;
AZ::Uuid m_uuid;
const ScanFolderInfo* m_scanFolder{ nullptr };
};
public:
explicit AssetProcessorManager(AssetProcessor::PlatformConfiguration* config, QObject* parent = nullptr);
virtual ~AssetProcessorManager();
bool IsIdle();
bool HasProcessedCriticalAssets() const;
//////////////////////////////////////////////////////////////////////////
// ProcessingJobInfoBus::Handler overrides
void BeginCacheFileUpdate(const char* productPath) override;
void EndCacheFileUpdate(const char* productPath, bool queueAgainForDeletion) override;
AZ::u32 GetJobFingerprint(const AssetProcessor::JobIndentifier& jobIndentifier) override;
//////////////////////////////////////////////////////////////////////////
//! Controls whether or not we are allowed to skip analysis on a file when the source files modtimes have not changed
//! and neither have any builders.
void SetEnableModtimeSkippingFeature(bool enable);
//! Query logging will log every asset database query.
void SetQueryLogging(bool enableLogging);
void SetBuilderDebugFlag(bool enabled);
//! Scans assets that match the given pattern for content that looks like a missing product dependency.
//! Note that the database pattern is used as an SQL query, so use SQL syntax for the search (wildcard is %, not *).
//! FilePattern is just a normal wildcard pattern that can be used to filter files in the provided scan folders.
void ScanForMissingProductDependencies(QString dbPattern, QString filePattern, const AZStd::vector<AZStd::string>& dependencyAdditionalScanFolders, int maxScanIteration=AssetProcessor::MissingDependencyScanner::DefaultMaxScanIteration);
AZStd::shared_ptr<AssetDatabaseConnection> GetDatabaseConnection() const;
void EmitResolvedDependency(const AZ::Data::AssetId& assetId, const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry);
//! Internal structure that will hold all the necessary information to process jobs later.
//! We need to hold these jobs because they have declared either source dependency on other sources
//! or a job dependency and we can only resolve these dependencies once all the create jobs are completed.
struct JobToProcessEntry
{
SourceFileInfo m_sourceFileInfo;
AZStd::vector<JobDetails> m_jobsToAnalyze;
// a vector of pairs of <builder which emitted it, the dependency>
AZStd::vector<AZStd::pair<AZ::Uuid, AssetBuilderSDK::SourceFileDependency>> m_sourceFileDependencies;
};
//! Request to invalidate and reprocess a source asset or folder containing source assets
AZ::u64 RequestReprocess(const QString& sourcePath);
Q_SIGNALS:
void NumRemainingJobsChanged(int newNumJobs);
void AssetToProcess(JobDetails jobDetails);
//! Emit whenever a new asset is found or an existing asset is updated
void AssetMessage(AzFramework::AssetSystem::AssetNotificationMessage message);
// InputAssetProcessed - uses absolute asset path of input file - no outputprefix
void InputAssetProcessed(QString fullAssetPath, QString platform);
void RequestInputAssetStatus(QString inputAssetPath, QString platform, QString jobDescription);
void RequestPriorityAssetCompile(QString inputAssetPath, QString platform, QString jobDescription);
//! AssetProcessorManagerIdleState is emitted when APM idle state changes, we emit true when
//! APM is waiting for outside stimulus i.e its has eaten through all of its queues and is only waiting for
//! responses back from other systems (like its waiting for responses back from the compiler)
void AssetProcessorManagerIdleState(bool state);
void ReadyToQuit(QObject* source);
void CreateAssetsRequest(unsigned int nonce, QString name, QString platform, bool onlyExactMatch = true, bool syncRequest = false);
void SendAssetExistsResponse(NetworkRequestID groupID, bool exists);
void FenceFileDetected(unsigned int fenceId);
void EscalateJobs(AssetProcessor::JobIdEscalationList jobIdEscalationList);
void SourceDeleted(QString relSourceFile);
void SourceFolderDeleted(QString folderPath);
void SourceQueued(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid, QString rootPath, QString relativeFilePath);
void SourceFinished(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid);
void JobRemoved(AzToolsFramework::AssetSystem::JobInfo jobInfo);
void JobComplete(JobEntry jobEntry, AzToolsFramework::AssetSystem::JobStatus status);
//! Send a message when a new path dependency is resolved, so that downstream tools know the AssetId of the resolved dependency.
void PathDependencyResolved(const AZ::Data::AssetId& assetId, const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry);
void AddedToCatalog(JobEntry jobEntry);
public Q_SLOTS:
void AssetProcessed(JobEntry jobEntry, AssetBuilderSDK::ProcessJobResponse response);
void AssetProcessed_Impl();
void AssetFailed(JobEntry jobEntry);
void AssetCancelled(JobEntry jobEntry);
void AssessFilesFromScanner(QSet<AssetFileInfo> filePaths);
void AssessModifiedFile(QString filePath);
void AssessAddedFile(QString filePath);
void AssessDeletedFile(QString filePath);
void OnAssetScannerStatusChange(AssetProcessor::AssetScanningStatus status);
void OnJobStatusChanged(JobEntry jobEntry, JobStatus status);
void CheckAssetProcessorIdleState();
void QuitRequested();
//! A network request came in asking, for a given input asset, what the status is of any jobs related to that request
AssetJobsInfoResponse ProcessGetAssetJobsInfoRequest(MessageData<AssetJobsInfoRequest> messageData);
//! A network request came in, Given a JOB ID (from the above Job Request), asking for the actual log for that job.
AssetJobLogResponse ProcessGetAssetJobLogRequest(MessageData<AssetJobLogRequest> messageData);
//! A network request came in asking for asset database location
GetAbsoluteAssetDatabaseLocationResponse ProcessGetAbsoluteAssetDatabaseLocationRequest(MessageData<GetAbsoluteAssetDatabaseLocationRequest> messageData);
//! This request comes in and is expected to do whatever heuristic is required in order to determine if an asset actually exists in the database.
void OnRequestAssetExists(NetworkRequestID requestId, QString platform, QString searchTerm, AZ::Data::AssetId assetId);
//! Searches the product and source asset tables to try and find a match
QString GuessProductOrSourceAssetName(QString searchTerm, QString platform, bool useLikeSearch);
void ProcessFilesToExamineQueue();
void CheckForIdle();
void CheckMissingFiles();
void ProcessGetAssetJobsInfoRequest(AssetJobsInfoRequest& request, AssetJobsInfoResponse& response);
void ProcessGetAssetJobLogRequest(const AssetJobLogRequest& request, AssetJobLogResponse& response);
void ScheduleNextUpdate();
void ProcessJobs();
void RemoveEmptyFolders();
void OnBuildersRegistered();
private:
template <class R>
bool Recv(unsigned int connId, QByteArray payload, R& request);
void AssessFileInternal(QString fullFile, bool isDelete, bool fromScanner = false);
void CheckSource(const FileEntry& source);
void CheckMissingJobs(QString relativeSourceFile, const ScanFolderInfo* scanFolder, const AZStd::vector<JobDetails>& jobsThisTime);
void CheckDeletedProductFile(QString normalizedPath);
void CheckDeletedSourceFile(QString normalizedPath, QString relativePath, QString databaseSourceFile);
void CheckModifiedSourceFile(QString normalizedPath, QString databaseSourceFile, const ScanFolderInfo* scanFolderInfo);
bool AnalyzeJob(JobDetails& details);
void CheckDeletedCacheFolder(QString normalizedPath);
void CheckDeletedSourceFolder(QString normalizedPath, QString relativePath, const ScanFolderInfo* scanFolderInfo);
void CheckCreatedSourceFolder(QString normalizedPath);
void CheckMetaDataRealFiles(QString relativePath);
bool DeleteProducts(const AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& products);
void DispatchFileChange();
bool InitializeCacheRoot();
void PopulateJobStateCache();
void AutoFailJob(const AZStd::string& consoleMsg, const AZStd::string& autoFailReason, const AZStd::vector<AssetProcessedEntry>::iterator& assetIter);
using ProductInfoList = AZStd::vector<AZStd::pair<AzToolsFramework::AssetDatabase::ProductDatabaseEntry, const AssetBuilderSDK::JobProduct*>>;
void WriteProductTableInfo(AZStd::pair<AzToolsFramework::AssetDatabase::ProductDatabaseEntry, const AssetBuilderSDK::JobProduct*>& pair, AZStd::vector<AZ::u32>& subIds, AZStd::unordered_set<AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry>& dependencyContainer, const AZStd::string& platform);
//! given a full absolute path to a file, add any metadata files you find that apply.
void AddMetadataFilesForFingerprinting(QString absolutePathToFileToCheck, SourceFilesForFingerprintingContainer& outFilesToFingerprint);
// given a file name and a root to not go beyond, add the parent folder and its parent folders recursively
// to the list of known folders.
void AddKnownFoldersRecursivelyForFile(QString file, QString root);
void CleanEmptyFolder(QString folder, QString root);
void ProcessBuilders(QString normalizedPath, QString relativePathToFile, const ScanFolderInfo* scanFolder, const AssetProcessor::BuilderInfoList& builderInfoList);
struct SourceInfo
{
QString m_watchFolder;
QString m_sourceRelativeToWatchFolder;
QString m_sourceDatabaseName;
};
struct SourceInfoWithFingerprints
{
QString m_watchFolder;
QString m_sourceRelativeToWatchFolder;
QString m_sourceDatabaseName;
QString m_analysisFingerprint;
};
//! Search the database and the the source dependency maps for the the sourceUuid. if found returns the cached info
bool SearchSourceInfoBySourceUUID(const AZ::Uuid& sourceUuid, AssetProcessorManager::SourceInfo& result);
//! Adds the source to the database and returns the corresponding sourceDatabase Entry
void AddSourceToDatabase(AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceDatabaseEntry, const ScanFolderInfo* scanFolder, QString relativeSourceFilePath);
protected:
// Checks whether or not a file can be skipped for processing (ie, file content hasn't changed, builders haven't been added/removed, builders for the file haven't changed)
bool CanSkipProcessingFile(const AssetFileInfo &fileInfo, AZ::u64& fileHash);
AZ::s64 GenerateNewJobRunKey();
// Attempt to erase a log file. Failing to erase it is not a critical problem, but should be logged.
// returns true if there is no log file there after this operation completes
bool EraseLogFile(const char* fileName);
// Load the old scan folders and match them up with new scan folders. Make sure they're
bool MigrateScanFolders();
//! Checks whether the AP is aware of any source file that has indicated the inputted
//! source file as its dependency, and if found do we need to put that file back in the asset pipeline queue again
QStringList GetSourceFilesWhichDependOnSourceFile(const QString& sourcePath);
/** Given a BuilderSDK SourceFileDependency, try to find out what actual database source name is.
* If it cannot be resolved but a UUID is available, the string result will contain the UUID (and we will return true).
* If there's a problem that makes it unusable (such as no fields being filled in), the string will be blank
* and this function will return false.
*/
bool ResolveSourceFileDependencyPath(const AssetBuilderSDK::SourceFileDependency& sourceDependency, QString& resultDatabaseSourceNames, QStringList& resolvedDependencyList);
//! Updates the database with all the changes related to source dependency / job dependency:
void UpdateSourceFileDependenciesDatabase(JobToProcessEntry& entry);
//! Analyze JobDetail for every hold jobs
void AnalyzeJobDetail(JobToProcessEntry& jobEntry);
void UpdateJobDependency(JobDetails& jobDetails);
void QueueIdleCheck();
void UpdateWildcardDependencies(JobDetails& job, size_t jobDependencySlot, QStringList& resolvedDependencyList);
//! Check whether the job can be analyzed by APM,
//! A job cannot be analyzed if any of its dependent job hasn't been fingerprinted
bool CanAnalyzeJob(const JobDetails& jobDetails);
//! Analyzes and forward the job to the RCController if the job requires processing
void ProcessJob(JobDetails& jobDetails);
AssetProcessor::PlatformConfiguration* m_platformConfig = nullptr;
bool m_queuedExamination = false;
bool m_hasProcessedCriticalAssets = false;
QQueue<FileEntry> m_activeFiles;
QSet<QString> m_alreadyActiveFiles; // a simple optimization to only do the exhaustive search if we know its there.
AZStd::vector<AssetProcessedEntry> m_assetProcessedList;
AZStd::shared_ptr<AssetDatabaseConnection> m_stateData;
ThreadController<AssetCatalog>* m_assetCatalog;
typedef QHash<QString, FileEntry> FileExamineContainer;
FileExamineContainer m_filesToExamine; // order does not actually matter in this (yet)
// this map contains a list of source files that were discovered in the database before asset scanning began.
// (so files from a previous run).
// as asset scanning encounters files, it will remove them from this map, and when its done,
// it will thus contain only the files that were in the database from last time, but were NOT found during file scan
// in other words, files that have been deleted from disk since last run.
// the key to this map is the absolute path of the file from last run, but with the current scan folder setup
QMap<QString, SourceInfoWithFingerprints> m_sourceFilesInDatabase;
// this map contains modtimes of all files AP processed last time it ran
AZStd::unordered_map<AZStd::string, AZ::u64> m_fileModTimes;
// this map contains hashes of all files AP processed last time it ran
AZStd::unordered_map<AZStd::string, AZ::u64> m_fileHashes;
QSet<QString> m_knownFolders; // a cache of all known folder names, normalized to have forward slashes.
typedef AZStd::unordered_map<AZ::u64, AzToolsFramework::AssetSystem::JobInfo> JobRunKeyToJobInfoMap; // for when network requests come in about the jobInfo
JobRunKeyToJobInfoMap m_jobRunKeyToJobInfoMap;
AZStd::multimap<AZStd::string, AZ::u64> m_jobKeyToJobRunKeyMap;
using SourceUUIDToSourceInfoMap = AZStd::unordered_map<AZ::Uuid, SourceInfo>;
SourceUUIDToSourceInfoMap m_sourceUUIDToSourceInfoMap; // contains UUID -> SourceInfo, which includes database name and relative to watch folder:
AZStd::mutex m_sourceUUIDToSourceInfoMapMutex;
QString m_normalizedCacheRootPath;
char m_absoluteDevFolderPath[AZ_MAX_PATH_LEN];
char m_absoluteDevGameFolderPath[AZ_MAX_PATH_LEN];
QDir m_cacheRootDir;
bool m_isCurrentlyScanning = false;
bool m_quitRequested = false;
bool m_processedQueued = false;
bool m_AssetProcessorIsBusy = true;
bool m_alreadyScheduledUpdate = false;
QMutex m_processingJobMutex;
AZStd::unordered_set<AZStd::string> m_processingProductInfoList;
AZ::s64 m_highestJobRunKeySoFar = 0;
AZStd::vector<JobToProcessEntry> m_jobEntries;
AZStd::unordered_set<JobDetails> m_jobsToProcess;
//! This map is required to prevent multiple sourceFile modified events been send by the APM
AZStd::unordered_map<AZ::Uuid, qint64> m_sourceFileModTimeMap;
AZStd::unordered_map<JobIndentifier, AZ::u32> m_jobFingerprintMap;
AZStd::unordered_map<JobDesc, AZStd::unordered_set<AZ::Uuid>> m_jobDescToBuilderUuidMap;
AZStd::unique_ptr<PathDependencyManager> m_pathDependencyManager;
AZStd::unique_ptr<SourceFileRelocator> m_sourceFileRelocator;
JobDiagnosticTracker m_jobDiagnosticTracker{};
QSet<QString> m_checkFoldersToRemove; //!< List of folders that needs to be checked for removal later by AP
//! List of all scanfolders that are present in the database but not currently watched by AP
AZStd::unordered_map<AZStd::string, AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry> m_scanFoldersInDatabase;
int m_numOfJobsToAnalyze = 0;
bool m_alreadyQueuedCheckForIdle = false;
//////////////////// Analysis Early-Out feature ///////////////////
// ComputeBuilderDirty builds the maps of which builders are dirty and how they have changed.
// note that until ComputeBuilderDirty is called, it is assumed that *all* are dirty, to be conservative.
// The data we actually care about for this feature:
struct BuilderData
{
AZ::u8 m_flags = 0; // the flags from the builder registration
AZ::Uuid m_fingerprint; // a hash of the fingerprint and version info
bool m_isDirty = false;
};
void ComputeBuilderDirty();
AZStd::unordered_map<AZ::Uuid, BuilderData> m_builderDataCache;
bool m_buildersAddedOrRemoved = true; //< true if any new builders exist. If this happens we actually need to re-analyze everything.
bool m_anyBuilderChange = true;
// Checks whether any of the builders specified have changed their fingerprint
bool AreBuildersUnchanged(AZStd::string_view builderEntries, int& numBuildersEmittingSourceDependencies);
/** Utility function: Given the input database row (from sources table), return an (ordered) set of all dependencies
* including dependencies-of-dependencies. These will be absolute paths to the dependency file on disk.
* Note that the output also includes the initial inputDatabasePath asset (but expanded to be absolute)
* if a file does not exist, it will still in the list at the absolute path to where it may appear, so that
* this result set can still use that for hashing.
* if a source file is missing from disk, it will not be included in the result set, since this returns
* full absolute paths.
*/
void QueryAbsolutePathDependenciesRecursive(QString inputDatabasePath, SourceFilesForFingerprintingContainer& finalDependencyList, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::TypeOfDependency dependencyType, bool reverseQuery);
// we can't write a job to the database as not needing analysis the next time around,
// until all jobs related to it are finished. This is becuase the jobs themselves are not written to the database
// so until all jobs are finished, we need to re-analyze the source file next time.
// (since if you terminate the asset processor while its still processing, we don't want it to skip over those
// source files next time). So we keep a map of how many remaining outstanding jobs exist for a given
// source file. Once the outstanding jobs hit zero we compute a final source fingerprint for analysis and save it.
struct AnalysisTracker
{
int m_remainingJobsSpawned = 0;
AZ::s64 m_databaseScanFolderId = -1;
AZStd::string m_databaseSourceName;
AZStd::set<AZ::Uuid> m_buildersInvolved; // this is intentionally a sorted set, since its used to generate a stable hash
bool failedStatus = false; // if it fails, we avoid writing anything to the database, so that next time around, we reprocess the file.
};
// maps "absolute source path to file (normalized)" to tracking infomation struct above.
using JobCounter = AZStd::unordered_map<AZStd::string, AnalysisTracker> ;
JobCounter m_remainingJobsForEachSourceFile;
// utility function: finds the source in the above map and updates it.
enum class AnalysisTrackerUpdateType
{
JobFailed,
JobStarted,
JobFinished,
};
// ideally you would already have the absolute path to the file, and call this function with it:
void UpdateAnalysisTrackerForFile(const char* fullPathToFile, AnalysisTrackerUpdateType updateType);
// convenience overload of the above function when you have a jobEntry but no absolute path to the file.
void UpdateAnalysisTrackerForFile(const JobEntry &entry, AnalysisTrackerUpdateType updateType);
// Used to scan through products for anything that looks like a missing product dependency;
MissingDependencyScanner m_missingDependencyScanner;
// Metrics
int m_numTotalSourcesFound = 0;
int m_numSourcesNeedingFullAnalysis = 0;
int m_numSourcesNotHandledByAnyBuilder = 0;
bool m_reportedAnalysisMetrics = false;
// cache these so we don't have to check them each time during analysis:
QSet<QString> m_metaFilesWhichActuallyExistOnDisk;
bool m_cachedMetaFilesExistMap = false;
// when true, only processes files if their modtime or builder(s) have changed
// defaults to true (in the settings) for GUI mode, false for batch mode
bool m_allowModtimeSkippingFeature = false;
// when true, a flag will be sent to builders process job indicating debug output/mode should be used
bool m_builderDebugFlag = false;
protected Q_SLOTS:
void FinishAnalysis(AZStd::string fileToCheck);
//////////////////////////////////////////////////////////
};
} // namespace AssetProcessor
@@ -0,0 +1,151 @@
/*
* 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 ASSETSCANFOLDERINFO_H
#define ASSETSCANFOLDERINFO_H
#include <QString>
#include <QDateTime>
#include <AzCore/base.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
namespace AssetProcessor
{
/** This Class contains information about the folders to be scanned
* */
class ScanFolderInfo
{
public:
ScanFolderInfo(
QString path,
QString displayName,
QString portableKey,
QString prefix,
bool isRoot,
bool recurseSubFolders,
AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms = AZStd::vector<AssetBuilderSDK::PlatformInfo>{},
int order = 0,
AZ::s64 scanFolderID = 0,
bool canSaveNewAssets = false)
: m_scanPath(path)
, m_displayName(displayName)
, m_portableKey (portableKey)
, m_outputPrefix(prefix)
, m_isRoot(isRoot)
, m_recurseSubFolders(recurseSubFolders)
, m_order(order)
, m_scanFolderID(scanFolderID)
, m_platforms(platforms)
, m_canSaveNewAssets(canSaveNewAssets)
{
// note that m_scanFolderID is 0 unless its filled in from the DB.
}
ScanFolderInfo() = default;
ScanFolderInfo(const ScanFolderInfo& other) = default;
QString ScanPath() const
{
return m_scanPath;
}
QString GetDisplayName() const
{
return m_displayName;
}
QString GetOutputPrefix() const
{
return m_outputPrefix;
}
bool IsRoot() const
{
return m_isRoot;
}
bool RecurseSubFolders() const
{
return m_recurseSubFolders;
}
bool CanSaveNewAssets() const
{
return m_canSaveNewAssets;
}
int GetOrder() const
{
return m_order;
}
AZ::s64 ScanFolderID() const
{
return m_scanFolderID;
}
QString GetPortableKey() const
{
return m_portableKey;
}
const AZStd::vector<AssetBuilderSDK::PlatformInfo>& GetPlatforms() const
{
return m_platforms;
}
void SetScanFolderID(AZ::s64 scanFolderID)
{
m_scanFolderID = scanFolderID;
}
private:
QString m_scanPath; // the local path to scan ("C:\\whatever")
QString m_displayName; // the display name to show in GUIs that show it.
QString m_outputPrefix; // the output prefix to target results into (eg, put things in a certain subfolder of @assets@ rather than the relative to assets itself)
QString m_portableKey; // a key that remains the same even if the asset database is moved from computer to computer.
bool m_isRoot = false; // is it 'the' root folder?
bool m_recurseSubFolders = true;
bool m_canSaveNewAssets = false; // Tracks if it is safe to save new assets in this folder.
int m_order = 0;
AZ::s64 m_scanFolderID = 0; // this is filled in by the database - don't modify it.
AZStd::vector<AssetBuilderSDK::PlatformInfo> m_platforms; // This contains the list of platforms that are enabled for the particular scanfolder
};
struct AssetFileInfo
{
AssetFileInfo() = default;
AssetFileInfo(QString filePath, QDateTime modTime, AZ::u64 fileSize, const ScanFolderInfo* scanFolder, bool isDirectory)
: m_filePath(filePath), m_modTime(modTime), m_fileSize(fileSize), m_scanFolder(scanFolder), m_isDirectory(isDirectory) {}
bool operator==(const AssetFileInfo& rhs) const
{
return m_filePath == rhs.m_filePath
&& m_modTime == rhs.m_modTime
&& m_fileSize == rhs.m_fileSize
&& m_isDirectory == rhs.m_isDirectory;
// m_scanFolder ignored since m_filePath will already ensure this is the same file
}
QString m_filePath{}; // Absolute path of the file
QDateTime m_modTime{};
AZ::u64 m_fileSize{};
const ScanFolderInfo* m_scanFolder{};
bool m_isDirectory{};
};
inline uint qHash(const AssetFileInfo& item)
{
return qHash(item.m_filePath);
}
} // end namespace AssetProcessor
#endif //ASSETSCANFOLDERINFO_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 "native/AssetManager/assetScanner.h"
namespace AssetProcessor
{
AssetScanner::AssetScanner(PlatformConfiguration* config, QObject* parent)
: QObject(parent)
, m_assetScannerWorker(config)
, m_status(AssetScanningStatus::Unknown)
{
m_assetScannerWorker.moveToThread( &m_assetWorkerScannerThread );
QObject::connect(&m_assetScannerWorker, &AssetScannerWorker::FilesFound, this, &AssetScanner::FilesFound);
QObject::connect(&m_assetScannerWorker, &AssetScannerWorker::FoldersFound, this, &AssetScanner::FoldersFound);
QObject::connect(&m_assetScannerWorker, &AssetScannerWorker::ExcludedFound, this, &AssetScanner::ExcludedFound);
QObject::connect(&m_assetScannerWorker, &AssetScannerWorker::ScanningStateChanged, this,
[this](AssetProcessor::AssetScanningStatus status)
{
if (m_status == status)
{
return;
}
m_status = status;
Q_EMIT AssetScanningStatusChanged(status);
});
}
AssetScanner::~AssetScanner()
{
StopScan();
m_assetWorkerScannerThread.quit();
m_assetWorkerScannerThread.wait();
}
void AssetScanner::StartScan()
{
if (!m_workerCreated)
{
m_workerCreated = true;
m_assetWorkerScannerThread.setObjectName("AssetScannerWorker");
m_assetWorkerScannerThread.start();
}
QMetaObject::invokeMethod(&m_assetScannerWorker, "StartScan", Qt::QueuedConnection);
}
void AssetScanner::StopScan()
{
QMetaObject::invokeMethod(&m_assetScannerWorker, "StopScan", Qt::DirectConnection);
}
AssetProcessor::AssetScanningStatus AssetScanner::status() const
{
return m_status;
}
}
#include "native/AssetManager/moc_assetScanner.cpp"
@@ -0,0 +1,61 @@
/*
* 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 ASSETSCANNER_H
#define ASSETSCANNER_H
#if !defined(Q_MOC_RUN)
#include "native/assetprocessor.h"
#include "assetScannerWorker.h"
#include "assetScanFolderInfo.h"
#include <QString>
#include <QThread>
#include <QList>
#endif
namespace AssetProcessor
{
class PlatformConfiguration;
/** This Class is responsible for scanning for assets at startup
*/
class AssetScanner
: public QObject
{
Q_OBJECT
public:
explicit AssetScanner(PlatformConfiguration* config, QObject* parent = nullptr);
virtual ~AssetScanner();
void StartScan();//Should be called to start a scan
void StopScan();//Should be called to stop a scan
Q_INVOKABLE AssetScanningStatus status() const;
Q_SIGNALS:
void AssetScanningStatusChanged(AssetScanningStatus status);
void FilesFound(QSet<AssetFileInfo> files);
void FoldersFound(QSet<AssetFileInfo> folders);
void ExcludedFound(QSet<AssetFileInfo> excluded);
private:
QThread m_assetWorkerScannerThread;
AssetScannerWorker m_assetScannerWorker;
bool m_workerCreated = false;
AZStd::atomic<AssetScanningStatus> m_status;
};
}// end namespace AssetProcessor
#endif // ASSETSCANNER_H
@@ -0,0 +1,142 @@
/*
* 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/AssetManager/assetScannerWorker.h"
#include "native/AssetManager/assetScanner.h"
#include "native/utilities/PlatformConfiguration.h"
#include <QDir>
using namespace AssetProcessor;
AssetScannerWorker::AssetScannerWorker(PlatformConfiguration* config, QObject* parent)
: QObject(parent)
, m_platformConfiguration(config)
{
}
void AssetScannerWorker::StartScan()
{
// this must be called from the thread operating it and not the main thread.
Q_ASSERT(QThread::currentThread() == this->thread());
m_fileList.clear();
m_folderList.clear();
m_doScan = true;
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Scanning file system for changes...\n");
Q_EMIT ScanningStateChanged(AssetProcessor::AssetScanningStatus::Started);
Q_EMIT ScanningStateChanged(AssetProcessor::AssetScanningStatus::InProgress);
for (int idx = 0; idx < m_platformConfiguration->GetScanFolderCount(); idx++)
{
const ScanFolderInfo& scanFolderInfo = m_platformConfiguration->GetScanFolderAt(idx);
ScanForSourceFiles(scanFolderInfo, scanFolderInfo);
}
// we want not to emit any signals until we're finished scanning
// so that we don't interleave directory tree walking (IO access to the file table)
// with file access (IO access to file data) caused by sending signals to other classes.
if (!m_doScan)
{
m_fileList.clear();
m_folderList.clear();
Q_EMIT ScanningStateChanged(AssetProcessor::AssetScanningStatus::Stopped);
return;
}
else
{
EmitFiles();
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "File system scan done.\n");
Q_EMIT ScanningStateChanged(AssetProcessor::AssetScanningStatus::Completed);
}
// note: Call this directly from the main thread!
// do not queue this call.
// Join the thread if you intend to wait until its stopped
void AssetScannerWorker::StopScan()
{
m_doScan = false;
}
void AssetScannerWorker::ScanForSourceFiles(const ScanFolderInfo& scanFolderInfo, const ScanFolderInfo& rootScanFolder)
{
if (!m_doScan)
{
return;
}
QDir dir(scanFolderInfo.ScanPath());
QFileInfoList entries;
//Only scan sub folders if recurseSubFolders flag is set
if (!scanFolderInfo.RecurseSubFolders())
{
entries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files);
}
else
{
entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files);
}
for (const QFileInfo& entry : entries)
{
if (!m_doScan) // scan was cancelled!
{
return;
}
QString absPath = entry.absoluteFilePath();
const bool isDirectory = entry.isDir();
QDateTime modTime = entry.lastModified();
AZ::u64 fileSize = isDirectory ? 0 : entry.size();
AssetFileInfo assetFileInfo(absPath, modTime, fileSize, &rootScanFolder, isDirectory);
// Filtering out excluded files
if (m_platformConfiguration->IsFileExcluded(absPath))
{
m_excludedList.insert(AZStd::move(assetFileInfo));
continue;
}
if (isDirectory)
{
//Entry is a directory
m_folderList.insert(AZStd::move(assetFileInfo));
ScanFolderInfo tempScanFolderInfo(absPath, "", "", "", false, true);
ScanForSourceFiles(tempScanFolderInfo, rootScanFolder);
}
else
{
//Entry is a file
m_fileList.insert(AZStd::move(assetFileInfo));
}
}
}
void AssetScannerWorker::EmitFiles()
{
//Loop over all source asset files and send them up the chain:
Q_EMIT FilesFound(m_fileList);
m_fileList.clear();
Q_EMIT FoldersFound(m_folderList);
m_folderList.clear();
Q_EMIT ExcludedFound(m_excludedList);
m_excludedList.clear();
}
@@ -0,0 +1,64 @@
/*
* 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 ASSETSCANNERWORKER_H
#define ASSETSCANNERWORKER_H
#if !defined(Q_MOC_RUN)
#include "native/assetprocessor.h"
#include "assetScanFolderInfo.h"
#include <QString>
#include <QSet>
#include <QObject>
#endif
namespace AssetProcessor
{
class PlatformConfiguration;
/** This Class is actually responsible for scanning the game folder
* and finding file of interest files.
* Its created on the main thread and then moved to the worker thread
* so it should contain no QObject-based classes at construction time (it can make them later)
*/
class AssetScannerWorker
: public QObject
{
Q_OBJECT
public:
explicit AssetScannerWorker(PlatformConfiguration* config, QObject* parent = 0);
Q_SIGNALS:
void ScanningStateChanged(AssetProcessor::AssetScanningStatus status);
void FilesFound(QSet<AssetFileInfo> files); // QSet<QString> is a refcounted copy-on-write object, do not pass by ref.
void FoldersFound(QSet<AssetFileInfo> folders); // QSet<QString> is a refcounted copy-on-write object, do not pass by ref.
void ExcludedFound(QSet<AssetFileInfo> excluded); // QSet<QString> is a refcounted copy-on-write object, do not pass by ref.
public Q_SLOTS:
void StartScan();
void StopScan();
protected:
// scanFolderInfo - the folder we're currently scanning (this will sometimes be a fake scanfolder created when recursing through directories)
// rootScanFolder - the actual scan folder we started with, which will either be the same as scanFolderInfo or a parent folder
void ScanForSourceFiles(const ScanFolderInfo& scanFolderInfo, const ScanFolderInfo& rootScanFolder);
void EmitFiles();
private:
volatile bool m_doScan = true;
QSet<AssetFileInfo> m_fileList; // note: neither QSet nor QString are qobject-derived
QSet<AssetFileInfo> m_folderList;
QSet<AssetFileInfo> m_excludedList;
PlatformConfiguration* m_platformConfiguration;
};
} // end namespace AssetProcessor
#endif // ASSETSCANNERWORKER_H
@@ -0,0 +1,20 @@
/*
* 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 "AssetData.h"
#include <QHash>
#include <QFileInfo>
namespace AssetProcessor
{
}
@@ -0,0 +1,25 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/std/string/string_view.h>
namespace AssetProcessorBuildTarget
{
//! This file is to be added only to the AssetProcessorBatch build target
//! This function returns the build system target name
AZStd::string_view GetBuildTargetName()
{
#if !defined (LY_CMAKE_TARGET)
#error "LY_CMAKE_TARGET must be defined in order to add this source file to a CMake executable target"
#endif
return AZStd::string_view{ LY_CMAKE_TARGET };
}
}
@@ -0,0 +1,25 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/std/string/string_view.h>
namespace AssetProcessorBuildTarget
{
//! This file is to be added only to the AssetProcessor build target
//! This function returns the build system target name
AZStd::string_view GetBuildTargetName()
{
#if !defined (LY_CMAKE_TARGET)
#error "LY_CMAKE_TARGET must be defined in order to add this source file to a CMake executable target"
#endif
return AZStd::string_view{ LY_CMAKE_TARGET };
}
}
@@ -0,0 +1,301 @@
/*
* 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/FileProcessor/FileProcessor.h>
#include <native/utilities/PlatformConfiguration.h>
#include <QDir>
namespace FileProcessorPrivate
{
bool FinishedScanning(AssetProcessor::AssetScanningStatus status)
{
return status == AssetProcessor::AssetScanningStatus::Completed ||
status == AssetProcessor::AssetScanningStatus::Stopped;
}
QString GenerateUniqueFileKey(AZ::s64 scanFolder, const char* fileName)
{
return QString("%1:%2").arg(scanFolder).arg(fileName);
}
}
namespace AssetProcessor
{
using namespace FileProcessorPrivate;
FileProcessor::FileProcessor(PlatformConfiguration* config)
: m_platformConfig(config)
{
m_connection = AZStd::shared_ptr<AssetDatabaseConnection>(aznew AssetDatabaseConnection());
m_connection->OpenDatabase();
QDir cacheRootDir;
if (!AssetUtilities::ComputeProjectCacheRoot(cacheRootDir))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to compute cache root folder");
}
m_normalizedCacheRootPath = AssetUtilities::NormalizeDirectoryPath(cacheRootDir.absolutePath());
}
FileProcessor::~FileProcessor() = default;
void FileProcessor::OnAssetScannerStatusChange(AssetScanningStatus status)
{
//when AssetScanner finished processing, synchronize Files table
if (FileProcessorPrivate::FinishedScanning(status))
{
QMetaObject::invokeMethod(this, "Sync", Qt::QueuedConnection);
}
}
void FileProcessor::AssessFilesFromScanner(QSet<AssetFileInfo> files)
{
for (const AssetFileInfo& file : files)
{
m_filesInAssetScanner.append(file);
}
}
void FileProcessor::AssessFoldersFromScanner(QSet<AssetFileInfo> folders)
{
for (const AssetFileInfo& folder : folders)
{
m_filesInAssetScanner.append(folder);
}
}
void FileProcessor::AssessAddedFile(QString filePath)
{
using namespace AzToolsFramework;
if (m_shutdownSignalled)
{
return;
}
QString relativeFileName;
QString scanFolderPath;
if (!GetRelativePath(filePath, relativeFileName, scanFolderPath))
{
return;
}
const ScanFolderInfo* scanFolderInfo = m_platformConfig->GetScanFolderByPath(scanFolderPath);
if (!scanFolderInfo)
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to find the scan folder for file %s", filePath.toUtf8().constData());
return;
}
AssetDatabase::FileDatabaseEntry file;
file.m_scanFolderPK = scanFolderInfo->ScanFolderID();
file.m_fileName = relativeFileName.toUtf8().constData();
file.m_isFolder = QFileInfo(filePath).isDir();
bool entryAlreadyExists;
if (m_connection->InsertFile(file, entryAlreadyExists) && !entryAlreadyExists)
{
AssetSystem::FileInfosNotificationMessage message;
message.m_type = AssetSystem::FileInfosNotificationMessage::FileAdded;
message.m_fileID = file.m_fileID;
ConnectionBus::Broadcast(&ConnectionBusTraits::Send, 0, message);
}
if (file.m_isFolder)
{
QDir folder(filePath);
for (const QFileInfo& subFile : folder.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot))
{
AssessAddedFile(subFile.absoluteFilePath());
}
}
}
void FileProcessor::AssessDeletedFile(QString filePath)
{
using namespace AzToolsFramework;
if (m_shutdownSignalled)
{
return;
}
QString relativeFileName;
QString scanFolderPath;
if (!GetRelativePath(filePath, relativeFileName, scanFolderPath))
{
return;
}
const ScanFolderInfo* scanFolderInfo = m_platformConfig->GetScanFolderByPath(scanFolderPath);
if (!scanFolderInfo)
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to find the scan folder for file %s", filePath.toUtf8().constData());
return;
}
AssetDatabase::FileDatabaseEntry file;
if (m_connection->GetFileByFileNameAndScanFolderId(relativeFileName, scanFolderInfo->ScanFolderID(), file) && DeleteFileRecursive(file))
{
AssetSystem::FileInfosNotificationMessage message;
message.m_type = AssetSystem::FileInfosNotificationMessage::FileRemoved;
message.m_fileID = file.m_fileID;
ConnectionBus::Broadcast(&ConnectionBusTraits::Send, 0, message);
}
}
void FileProcessor::Sync()
{
using namespace AzToolsFramework;
if (m_shutdownSignalled)
{
return;
}
QMap<QString, AZ::s64> filesInDatabase;
//query all current files from Files table
auto filesFunction = [&filesInDatabase](AzToolsFramework::AssetDatabase::FileDatabaseEntry& entry)
{
QString uniqueKey = GenerateUniqueFileKey(entry.m_scanFolderPK, entry.m_fileName.c_str());
filesInDatabase[uniqueKey] = entry.m_fileID;
return true;
};
m_connection->QueryFilesTable(filesFunction);
//first collect all fileIDs in Files table
QSet<AZ::s64> missingFileIDs;
for (AZ::s64 fileID : filesInDatabase.values())
{
missingFileIDs.insert(fileID);
}
AzToolsFramework::AssetDatabase::FileDatabaseEntryContainer filesToInsert;
for (const AssetFileInfo& fileInfo : m_filesInAssetScanner)
{
bool isDir = fileInfo.m_isDirectory;
QString scanFolderName;
QString relativeFileName;
if (!m_platformConfig->ConvertToRelativePath(fileInfo.m_filePath, relativeFileName, scanFolderName))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to convert full path to relative for file %s", fileInfo.m_filePath.toUtf8().constData());
continue;
}
const ScanFolderInfo* scanFolderInfo = m_platformConfig->GetScanFolderForFile(fileInfo.m_filePath);
if (!scanFolderInfo)
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to find the scan folder for file %s", fileInfo.m_filePath.toUtf8().constData());
continue;
}
AssetDatabase::FileDatabaseEntry file;
file.m_scanFolderPK = scanFolderInfo->ScanFolderID();
file.m_fileName = relativeFileName.toUtf8().constData();
file.m_isFolder = isDir;
file.m_modTime = 0;
//when file is found by AssetScanner, remove it from the "missing" set
QString uniqueKey = GenerateUniqueFileKey(file.m_scanFolderPK, relativeFileName.toUtf8().constData());
if (filesInDatabase.contains(uniqueKey))
{
// found it, its not missing anymore. (Its also already in the db)
missingFileIDs.remove(filesInDatabase[uniqueKey]);
}
else
{
// its a new file we were previously unaware of.
filesToInsert.push_back(AZStd::move(file));
}
}
m_connection->InsertFiles(filesToInsert);
// remove remaining files from the database as they no longer exist on hard drive
for (AZ::s64 fileID : missingFileIDs)
{
m_connection->RemoveFile(fileID);
}
AssetSystem::FileInfosNotificationMessage message;
ConnectionBus::Broadcast(&ConnectionBusTraits::Send, 0, message);
// It's important to clear this out since rescanning will end up filling this up with duplicates otherwise
QList<AssetFileInfo> emptyList;
m_filesInAssetScanner.swap(emptyList);
}
// note that this function normalizes the path and also returns true only if the file is 'relevant'
// meaning something we care about tracking (ignore list/ etc taken into account).
bool FileProcessor::GetRelativePath(QString& filePath, QString& relativeFileName, QString& scanFolderPath) const
{
filePath = AssetUtilities::NormalizeFilePath(filePath);
if (filePath.startsWith(m_normalizedCacheRootPath, Qt::CaseInsensitive))
{
// modifies/adds to the cache are irrelevant. Deletions are all we care about
return false;
}
if (m_platformConfig->IsFileExcluded(filePath))
{
return false; // we don't care about this kind of file.
}
if (!m_platformConfig->ConvertToRelativePath(filePath, relativeFileName, scanFolderPath))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to convert full path to relative for file %s", filePath.toUtf8().constData());
return false;
}
return true;
}
bool FileProcessor::DeleteFileRecursive(const AzToolsFramework::AssetDatabase::FileDatabaseEntry& file) const
{
using namespace AzToolsFramework;
if (m_shutdownSignalled)
{
return false;
}
if (file.m_isFolder)
{
AssetDatabase::FileDatabaseEntryContainer container;
AZStd::string searchStr = file.m_fileName + AZ_CORRECT_DATABASE_SEPARATOR;
m_connection->GetFilesLikeFileName(
searchStr.c_str(),
AssetDatabaseConnection::LikeType::StartsWith,
container);
for (const auto& subFile : container)
{
DeleteFileRecursive(subFile);
}
}
return m_connection->RemoveFile(file.m_fileID);
}
void FileProcessor::QuitRequested()
{
m_shutdownSignalled = true;
Q_EMIT ReadyToQuit(this);
}
} // namespace AssetProcessor
#include "native/FileProcessor/moc_FileProcessor.cpp"
@@ -0,0 +1,77 @@
/*
* 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 <QObject>
#include <QString>
#include <QMap>
#include <native/AssetDatabase/AssetDatabase.h>
#include <native/assetprocessor.h>
#endif
namespace AzToolsFramework
{
namespace AssetDatabase
{
class FileDatabaseEntry;
}
}
namespace AssetProcessor
{
class PlatformConfiguration;
class FileProcessor
: public QObject
{
Q_OBJECT
public:
explicit FileProcessor(PlatformConfiguration* config);
~FileProcessor();
public Q_SLOTS:
//! AssetScanner changed its status
void OnAssetScannerStatusChange(AssetScanningStatus status);
//! AssetScanner found a file
void AssessFilesFromScanner(QSet<AssetFileInfo> files);
//! AssetScanner found a folder
void AssessFoldersFromScanner(QSet<AssetFileInfo> folders);
//! FileWatcher detected added file
void AssessAddedFile(QString fileName);
//! FileWatcher detected removed file
void AssessDeletedFile(QString fileName);
//! Synchronize AssetScanner data with Files table
void Sync();
//! its time to shut down!
void QuitRequested();
Q_SIGNALS:
void ReadyToQuit(QObject* source); //After receiving QuitRequested, you must send this when its safe
private:
PlatformConfiguration* m_platformConfig = nullptr;
AZStd::shared_ptr<AssetDatabaseConnection> m_connection;
//! Files and folders located by AssetScanner during a scan
QList<AssetFileInfo> m_filesInAssetScanner;
QString m_normalizedCacheRootPath;
bool m_shutdownSignalled = false;
bool GetRelativePath(QString& filePath, QString& relativeFileName, QString& scanFolder) const;
bool DeleteFileRecursive(const AzToolsFramework::AssetDatabase::FileDatabaseEntry& file) const;
};
} // namespace AssetProcessor
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,247 @@
/*
* 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 FILESERVER_H
#define FILESERVER_H
#if !defined(Q_MOC_RUN)
#include <QByteArray>
#include <QDir>
#include <QString>
#include <QHash>
#include <memory>
// currently these headers are there to provide OS 'HANDLE' of the lock-files
#include <AzCore/PlatformIncl.h>
#endif
namespace AZ
{
namespace IO
{
class FileIOBase;
typedef uint32_t HandleType;
}
}
class Connection;
class FileServer
: public QObject
{
Q_OBJECT
Q_PROPERTY(QString rootFolder MEMBER m_displayRoot NOTIFY RootFolderChanged)
Q_PROPERTY(bool realtimeMetrics MEMBER m_realtimeMetrics NOTIFY RealtimeMetricsChanged)
//metrics
Q_PROPERTY(qint64 numOpenRequests MEMBER m_numOpenRequests NOTIFY NumOpenRequestsChanged)
Q_PROPERTY(qint64 numCloseRequests MEMBER m_numCloseRequests NOTIFY NumCloseRequestsChanged)
Q_PROPERTY(qint64 numOpened MEMBER m_numOpened NOTIFY NumOpenedChanged)
Q_PROPERTY(qint64 numClosed MEMBER m_numClosed NOTIFY NumClosedChanged)
Q_PROPERTY(qint64 numReadRequests MEMBER m_numReadRequests NOTIFY NumReadRequestsChanged)
Q_PROPERTY(qint64 numWriteRequests MEMBER m_numWriteRequests NOTIFY NumWriteRequestsChanged)
Q_PROPERTY(qint64 numSeekRequests MEMBER m_numSeekRequests NOTIFY NumSeekRequestsChanged)
Q_PROPERTY(qint64 numTellRequests MEMBER m_numTellRequests NOTIFY NumTellRequestsChanged)
Q_PROPERTY(qint64 numIsReadOnlyRequests MEMBER m_numIsReadOnlyRequests NOTIFY NumIsReadOnlyRequestsChanged)
Q_PROPERTY(qint64 numIsDirectoryRequests MEMBER m_numIsDirectoryRequests NOTIFY NumIsDirectoryRequestsChanged)
Q_PROPERTY(qint64 numSizeRequests MEMBER m_numSizeRequests NOTIFY NumSizeRequestsChanged)
Q_PROPERTY(qint64 numModificationTimeRequests MEMBER m_numModificationTimeRequests NOTIFY NumModificationTimeRequestsChanged)
Q_PROPERTY(qint64 numExistsRequests MEMBER m_numExistsRequests NOTIFY NumExistsRequestsChanged)
Q_PROPERTY(qint64 numFlushRequests MEMBER m_numFlushRequests NOTIFY NumFlushRequestsChanged)
Q_PROPERTY(qint64 numCreatePathRequests MEMBER m_numCreatePathRequests NOTIFY NumCreatePathRequestsChanged)
Q_PROPERTY(qint64 numDestroyPathRequests MEMBER m_numDestroyPathRequests NOTIFY NumDestroyPathRequestsChanged)
Q_PROPERTY(qint64 numRemoveRequests MEMBER m_numRemoveRequests NOTIFY NumRemoveRequestsChanged)
Q_PROPERTY(qint64 numCopyRequests MEMBER m_numCopyRequests NOTIFY NumCopyRequestsChanged)
Q_PROPERTY(qint64 numRenameRequests MEMBER m_numRenameRequests NOTIFY NumRenameRequestsChanged)
Q_PROPERTY(qint64 numFindFileNamesRequests MEMBER m_numFindFileNamesRequests NOTIFY NumFindFileNamesRequestsChanged)
Q_PROPERTY(qint64 bytesRead MEMBER m_bytesRead NOTIFY BytesReadChanged)
Q_PROPERTY(qint64 bytesWritten MEMBER m_bytesWritten NOTIFY BytesWrittenChanged)
Q_PROPERTY(qint64 bytesSent MEMBER m_bytesSent NOTIFY BytesSentChanged)
Q_PROPERTY(qint64 bytesReceived MEMBER m_bytesReceived NOTIFY BytesReceivedChanged)
Q_PROPERTY(qint64 numOpenFiles MEMBER m_numOpenFiles NOTIFY NumOpenFilesChanged)
Q_SIGNALS:
void RootFolderChanged();
void RealtimeMetricsChanged();
//metrics
void NumOpenRequestsChanged();
void NumCloseRequestsChanged();
void NumOpenedChanged();
void NumClosedChanged();
void NumReadRequestsChanged();
void NumWriteRequestsChanged();
void NumSeekRequestsChanged();
void NumTellRequestsChanged();
void NumIsReadOnlyRequestsChanged();
void NumIsDirectoryRequestsChanged();
void NumSizeRequestsChanged();
void NumModificationTimeRequestsChanged();
void NumExistsRequestsChanged();
void NumFlushRequestsChanged();
void NumCreatePathRequestsChanged();
void NumDestroyPathRequestsChanged();
void NumRemoveRequestsChanged();
void NumCopyRequestsChanged();
void NumRenameRequestsChanged();
void NumFindFileNamesRequestsChanged();
void BytesReadChanged();
void BytesWrittenChanged();
void BytesSentChanged();
void BytesReceivedChanged();
void NumOpenFilesChanged();
//per connection metrics
void AddBytesReceived(unsigned int connId, qint64 add, bool update);
void AddBytesSent(unsigned int connId, qint64 add, bool update);
void AddBytesRead(unsigned int connId, qint64 add, bool update);
void AddBytesWritten(unsigned int connId, qint64 add, bool update);
void AddOpenRequest(unsigned int connId, bool update);
void AddCloseRequest(unsigned int connId, bool update);
void AddOpened(unsigned int connId, bool update);
void AddClosed(unsigned int connId, bool update);
void AddReadRequest(unsigned int connId, bool update);
void AddWriteRequest(unsigned int connId, bool update);
void AddTellRequest(unsigned int connId, bool update);
void AddSeekRequest(unsigned int connId, bool update);
void AddIsReadOnlyRequest(unsigned int connId, bool update);
void AddIsDirectoryRequest(unsigned int connId, bool update);
void AddSizeRequest(unsigned int connId, bool update);
void AddModificationTimeRequest(unsigned int connId, bool update);
void AddExistsRequest(unsigned int connId, bool update);
void AddFlushRequest(unsigned int connId, bool update);
void AddCreatePathRequest(unsigned int connId, bool update);
void AddDestroyPathRequest(unsigned int connId, bool update);
void AddRemoveRequest(unsigned int connId, bool update);
void AddCopyRequest(unsigned int connId, bool update);
void AddRenameRequest(unsigned int connId, bool update);
void AddFindFileNamesRequest(unsigned int connId, bool update);
void UpdateBytesReceived(unsigned int connId);
void UpdateBytesSent(unsigned int connId);
void UpdateBytesRead(unsigned int connId);
void UpdateBytesWritten(unsigned int connId);
void UpdateOpenRequest(unsigned int connId);
void UpdateCloseRequest(unsigned int connId);
void UpdateOpened(unsigned int connId);
void UpdateClosed(unsigned int connId);
void UpdateReadRequest(unsigned int connId);
void UpdateWriteRequest(unsigned int connId);
void UpdateTellRequest(unsigned int connId);
void UpdateSeekRequest(unsigned int connId);
void UpdateIsReadOnlyRequest(unsigned int connId);
void UpdateIsDirectoryRequest(unsigned int connId);
void UpdateSizeRequest(unsigned int connId);
void UpdateModificationTimeRequest(unsigned int connId);
void UpdateExistsRequest(unsigned int connId);
void UpdateFlushRequest(unsigned int connId);
void UpdateCreatePathRequest(unsigned int connId);
void UpdateDestroyPathRequest(unsigned int connId);
void UpdateRemoveRequest(unsigned int connId);
void UpdateCopyRequest(unsigned int connId);
void UpdateRenameRequest(unsigned int connId);
void UpdateFindFileNamesRequest(unsigned int connId);
void UpdateConnectionMetrics();
public:
explicit FileServer(QObject* parent = 0);
virtual ~FileServer();
void SetSystemRoot(const QDir& systemRoot);
Q_INVOKABLE void setRealTimeMetrics(bool enable);
public Q_SLOTS:
void ProcessOpenRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessCloseRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessReadRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessWriteRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessTellRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessSeekRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessIsReadOnlyRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessIsDirectoryRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessSizeRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessModificationTimeRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessExistsRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessFlushRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessCreatePathRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessDestroyPathRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessRemoveRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessCopyRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessRenameRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessFindFileNamesRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ProcessFileTreeRequest(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void UpdateMetrics();
void ConnectionAdded(unsigned int connId, Connection* connection);
void ConnectionRemoved(unsigned int connId);
protected:
template <class R>
void Send(unsigned int connId, unsigned int serial, const R& response);
template <class R>
bool Recv(unsigned int connId, QByteArray payload, R& request);
void RecordFileOp(AZ::IO::FileIOBase* fileIO, const char* op, const AZ::IO::HandleType& fileHandle, const char* moreInfo);
void RecordFileOp(AZ::IO::FileIOBase* fileIO, const char* op, const char* filePath, const char* moreInfo);
void RecordFileOp(AZ::IO::FileIOBase* fileIO, const char* op, const char* sourceFile, const char* destFile, const char* moreInfo);
//! This makes sure that the cache folder exists but is conservative, it only should do this if the game actually makes file requests
//! So we only create a cache folder for VFS-based runs.
void EnsureCacheFolderExists(int connId);
private:
//metrics
qint64 m_numOpenRequests;
qint64 m_numCloseRequests;
qint64 m_numOpened;
qint64 m_numClosed;
qint64 m_numReadRequests;
qint64 m_numWriteRequests;
qint64 m_numTellRequests;
qint64 m_numSeekRequests;
qint64 m_numIsReadOnlyRequests;
qint64 m_numIsDirectoryRequests;
qint64 m_numSizeRequests;
qint64 m_numModificationTimeRequests;
qint64 m_numExistsRequests;
qint64 m_numFlushRequests;
qint64 m_numCreatePathRequests;
qint64 m_numDestroyPathRequests;
qint64 m_numRemoveRequests;
qint64 m_numCopyRequests;
qint64 m_numRenameRequests;
qint64 m_numFindFileNamesRequests;
qint64 m_bytesRead;
qint64 m_bytesWritten;
qint64 m_bytesSent;
qint64 m_bytesReceived;
qint64 m_numOpenFiles;
//root
QString m_displayRoot;
QDir m_systemRoot;
bool m_realtimeMetrics;
// maps connection ID -> LocalFileIO
QHash<unsigned int, std::shared_ptr<AZ::IO::FileIOBase> > m_fileIOs;
#if defined(AZ_PLATFORM_WINDOWS)
QHash<unsigned int, HANDLE> m_locks;
#endif // lockFiles. do NOT use QLockFile, it won't work if other platforms are locking it, it only works for other users of QLockFile
};
#endif // FILESERVER_H
@@ -0,0 +1,197 @@
/*
* 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 "FileWatcher.h"
#include <native/assetprocessor.h>
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
void FolderRootWatch::ProcessNewFileEvent(const QString& file)
{
FileChangeInfo info;
info.m_action = FileAction::FileAction_Added;
info.m_filePath = file;
const bool invoked = QMetaObject::invokeMethod(m_fileWatcher, "AnyFileChange", Qt::QueuedConnection, Q_ARG(FileChangeInfo, info));
Q_ASSERT(invoked);
}
void FolderRootWatch::ProcessDeleteFileEvent(const QString& file)
{
FileChangeInfo info;
info.m_action = FileAction::FileAction_Removed;
info.m_filePath = file;
const bool invoked = QMetaObject::invokeMethod(m_fileWatcher, "AnyFileChange", Qt::QueuedConnection, Q_ARG(FileChangeInfo, info));
Q_ASSERT(invoked);
}
void FolderRootWatch::ProcessModifyFileEvent(const QString& file)
{
FileChangeInfo info;
info.m_action = FileAction::FileAction_Modified;
info.m_filePath = file;
const bool invoked = QMetaObject::invokeMethod(m_fileWatcher, "AnyFileChange", Qt::QueuedConnection, Q_ARG(FileChangeInfo, info));
Q_ASSERT(invoked);
}
//////////////////////////////////////////////////////////////////////////
/// FileWatcher
FileWatcher::FileWatcher()
: m_nextHandle(0)
{
qRegisterMetaType<FileChangeInfo>("FileChangeInfo");
}
FileWatcher::~FileWatcher()
{
}
int FileWatcher::AddFolderWatch(FolderWatchBase* pFolderWatch)
{
if (!pFolderWatch)
{
return -1;
}
FolderRootWatch* pFolderRootWatch = nullptr;
//see if this a sub folder of an already watched root
for (auto rootsIter = m_folderWatchRoots.begin(); !pFolderRootWatch && rootsIter != m_folderWatchRoots.end(); ++rootsIter)
{
if (FolderWatchBase::IsSubfolder(pFolderWatch->m_folder, (*rootsIter)->m_root))
{
pFolderRootWatch = *rootsIter;
}
}
bool bCreatedNewRoot = false;
//if its not a sub folder
if (!pFolderRootWatch)
{
//create a new root and start listening for changes
pFolderRootWatch = new FolderRootWatch(pFolderWatch->m_folder);
//make sure the folder watcher(s) get deleted before this
pFolderRootWatch->setParent(this);
bCreatedNewRoot = true;
}
pFolderRootWatch->m_fileWatcher = this;
QObject::connect(this, &FileWatcher::AnyFileChange, pFolderWatch, &FolderWatchBase::OnAnyFileChange);
if (bCreatedNewRoot)
{
if (m_startedWatching)
{
pFolderRootWatch->Start();
}
//since we created a new root, see if the new root is a super folder
//of other roots, if it is then then fold those roots into the new super root
for (auto rootsIter = m_folderWatchRoots.begin(); rootsIter != m_folderWatchRoots.end(); )
{
if (FolderWatchBase::IsSubfolder((*rootsIter)->m_root, pFolderWatch->m_folder))
{
//union the sub folder map over to the new root
pFolderRootWatch->m_subFolderWatchesMap.insert((*rootsIter)->m_subFolderWatchesMap);
//clear the old root sub folders map so they don't get deleted when we
//delete the old root as they are now pointed to by the new root
(*rootsIter)->m_subFolderWatchesMap.clear();
//delete the empty old root, deleting a root will call Stop()
//automatically which kills the thread
delete *rootsIter;
//remove the old root pointer form the watched list
rootsIter = m_folderWatchRoots.erase(rootsIter);
}
else
{
++rootsIter;
}
}
//add the new root to the watched roots
m_folderWatchRoots.push_back(pFolderRootWatch);
}
//add to the root
pFolderRootWatch->m_subFolderWatchesMap.insert(m_nextHandle, pFolderWatch);
m_nextHandle++;
return m_nextHandle - 1;
}
void FileWatcher::RemoveFolderWatch(int handle)
{
for (auto rootsIter = m_folderWatchRoots.begin(); rootsIter != m_folderWatchRoots.end(); )
{
//find an element by the handle
auto foundIter = (*rootsIter)->m_subFolderWatchesMap.find(handle);
if (foundIter != (*rootsIter)->m_subFolderWatchesMap.end())
{
//remove the element
(*rootsIter)->m_subFolderWatchesMap.erase(foundIter);
//we removed a folder watch, if it's empty then there is no reason to keep watching it.
if ((*rootsIter)->m_subFolderWatchesMap.empty())
{
delete(*rootsIter);
rootsIter = m_folderWatchRoots.erase(rootsIter);
}
else
{
++rootsIter;
}
}
else
{
++rootsIter;
}
}
}
void FileWatcher::StartWatching()
{
if (m_startedWatching)
{
AZ_Warning("FileWatcher", false, "StartWatching() called when already watching for file changes.");
return;
}
for (FolderRootWatch* root : m_folderWatchRoots)
{
root->Start();
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "File Change Monitoring started.\n");
m_startedWatching = true;
}
void FileWatcher::StopWatching()
{
if (!m_startedWatching)
{
AZ_Warning("FileWatcher", false, "StartWatching() called when is not watching for file changes.");
return;
}
for (FolderRootWatch* root : m_folderWatchRoots)
{
root->Stop();
}
m_startedWatching = false;
}
#include "native/FileWatcher/moc_FileWatcher.cpp"
#include "native/FileWatcher/moc_FileWatcherAPI.cpp"
@@ -0,0 +1,99 @@
/*
* 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 FILEWATCHER_COMPONENT_H
#define FILEWATCHER_COMPONENT_H
//////////////////////////////////////////////////////////////////////////
#if !defined(Q_MOC_RUN)
#include "FileWatcherAPI.h"
#include <AzCore/std/containers/vector.h>
#include <QMap>
#include <QVector>
#include <QString>
#include <thread>
#endif
class FileWatcher;
//////////////////////////////////////////////////////////////////////////
//! FolderRootWatch
/*! Class used for holding a point in the files system from which file changes are tracked.
* */
class FolderRootWatch
: public QObject
{
Q_OBJECT
friend class FileWatcher;
public:
FolderRootWatch(const QString rootFolder);
virtual ~FolderRootWatch();
void ProcessNewFileEvent(const QString& file);
void ProcessDeleteFileEvent(const QString& file);
void ProcessModifyFileEvent(const QString& file);
void ProcessRenameFileEvent(const QString& fileOld, const QString& fileNew);
public Q_SLOTS:
bool Start();
void Stop();
private:
void WatchFolderLoop();
private:
std::thread m_thread;
QString m_root;
QMap<int, FolderWatchBase*> m_subFolderWatchesMap;
volatile bool m_shutdownThreadSignal;
FileWatcher* m_fileWatcher;
// Can't use unique_ptr because this is a QObject and Qt's magic sauce is
// unable to determine the size of the unique_ptr and so fails to compile
struct PlatformImplementation;
PlatformImplementation* m_platformImpl;
};
//////////////////////////////////////////////////////////////////////////
//! FileWatcher
/*! Class that handles creation and deletion of FolderRootWatches based on
*! the given FolderWatches, and forwards file change signals to them.
* */
class FileWatcher
: public QObject
{
Q_OBJECT
public:
FileWatcher();
virtual ~FileWatcher();
//////////////////////////////////////////////////////////////////////////
virtual int AddFolderWatch(FolderWatchBase* pFolderWatch);
virtual void RemoveFolderWatch(int handle);
//////////////////////////////////////////////////////////////////////////
void StartWatching();
void StopWatching();
Q_SIGNALS:
void AnyFileChange(FileChangeInfo info);
private:
int m_nextHandle;
AZStd::vector<FolderRootWatch*> m_folderWatchRoots;
bool m_startedWatching = false;
};
#endif//FILEWATCHER_COMPONENT_H

Some files were not shown because too many files have changed in this diff Show More