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
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
{
}