3dec5d3b71
* LYN-2537 Moved the Engine and Editor folder to be within the EngineAssets folder * Fixed Documentation in bootstrap.cfg to correct the path to the user project specific registry file * Adding a newline to the output of AssetCatalog 'Registering asset..., but type is not set' message * Updating the AssetProcessorPlatformConfig.setreg Scan Folder to detect the @ENGINEROOT@/EngineAssets/Engine path for engine runtime assets and @ENGINEROOT@/EngineAssets/Editor path for engine tool assets * Updating references to Icons and other assets to account for moving the Engine and Editor folder under a single EngineAssets folder * Moving the Engine Settings Registry folder from Engine/Registry -> Registry * Removed the LY_PROJECT_CMAKE_PATH define as it is not portable to other locations. It is hard coded to the project location that was used for the CMake configuration. Furthermore it paths with backslashes within it are treated as escape characters and not a path separator * Updated the LyTestTools asset_processor.py script to copy the exclude.filetag from the EngineAssets/Engine directory now * Fixed Atom Shader Preprocessing when running using an External Project * Updated the TSGenerateAction.cpp to fix the build error with using a renamed variable * Updated the Install_Common.cmake ly_setup_others function to install the EngineAssets directory and the each of the Gem's Assets directory while maintaining the relative directory structure to the Engine Root Also updated the install step to install the Registry folder at the engine root * Fixed the copying of the Registry folder to be in the install root, instead of under a second 'Registry' folder * Moving the AssetProcessorPlatformConfig.setreg file over to the Registry folder * Updated the LyTestTools and C++ code to point that the new location of the AssetProcessorPlatformConfig.setreg file inside of the Registry folder * Renamed Test AssetProcessor*Config.ini files to have the .setreg extension * Converted the AssetProcessor test setreg files from ini format to json format using the SerializeContextTools convert-ini command * Updated the AssetProcessor CMakeLists.txt to copy over the test setreg files to the build folder * Updated the assetprocessor test file list to point at the renamed AsssetProcessor*Config setreg filenames * Removed the Output Prefix code from the AssetProcessor. The complexity that it brought to the AP code is not needed, as users can replicate the behavior by just moving there assets underneath a another folder, underneath the scan folder * Adding back support to read the AssetProcessorPlatformConfig.setreg file from the asset root. This is only needed for C++ UnitTests as they run in an environment where the accessing the Engine Settings Registry is not available * Updating the Install_common.cmake logic to copy any "Assets" folder to the install layout. The Script has also been updated to copy over the "Assets" folder in the Engine Root to the install layout instead of an "EngineAssets" folder * Updating References to EngineAssets source asset folder in code to be the Assets source folder * Moved the Engine Source Asset folder of 'EngineAssets' to a new folder name of 'Assets'. This is inline with the naming scheme we use for Gem asset folders * Adding the EngineFinder.cmake to the AutomatedTesting project to allow it to work in a project centric manner * Updating the LyTestTools copy_assets_to_project function to be able to copy assets with folders to the temporary project root Fixed an issue in LyTestTools where the temporary log directory could have shutil.rmtree being called twice on it leading to an exception which fails an automated test Updated the asset_procesor_gui_tests_2 AddScanFolder test to not use the output prefix, but instead place the source asset root into a subdirectory * Correct the AssetProcessorPlatformConfig Scan Folders for the EngineAssets directory to point at the Assets directory * Updated the asset procesor batch dependency test scan folder to point at the 'Assets' folder instead of 'EngineAssets'
1624 lines
69 KiB
C++
1624 lines
69 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
|
|
#include "native/AssetManager/AssetCatalog.h"
|
|
|
|
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
|
#include <AzCore/std/string/wildcard.h>
|
|
#include <AzFramework/API/ApplicationAPI.h>
|
|
#include <AzFramework/FileTag/FileTagBus.h>
|
|
#include <AzFramework/FileTag/FileTag.h>
|
|
#include <AzToolsFramework/API/AssetDatabaseBus.h>
|
|
|
|
#include <QElapsedTimer>
|
|
#include "PathDependencyManager.h"
|
|
|
|
namespace AssetProcessor
|
|
{
|
|
AssetCatalog::AssetCatalog(QObject* parent, AssetProcessor::PlatformConfiguration* platformConfiguration)
|
|
: QObject(parent)
|
|
, m_platformConfig(platformConfiguration)
|
|
, m_registryBuiltOnce(false)
|
|
, m_registriesMutex(QMutex::Recursive)
|
|
{
|
|
|
|
for (const AssetBuilderSDK::PlatformInfo& info : m_platformConfig->GetEnabledPlatforms())
|
|
{
|
|
m_platforms.push_back(QString::fromUtf8(info.m_identifier.c_str()));
|
|
}
|
|
|
|
[[maybe_unused]] bool computedCacheRoot = AssetUtilities::ComputeProjectCacheRoot(m_cacheRoot);
|
|
AZ_Assert(computedCacheRoot, "Could not compute cache root for AssetCatalog");
|
|
|
|
// save 30mb for this. Really large projects do get this big (and bigger)
|
|
// if you don't do this, things get fragmented very fast.
|
|
m_saveBuffer.reserve(1024 * 1024 * 30);
|
|
|
|
m_absoluteDevFolderPath[0] = 0;
|
|
m_absoluteDevGameFolderPath[0] = 0;
|
|
|
|
AZStd::string engineRoot;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
|
|
azstrcpy(m_absoluteDevFolderPath, AZ_MAX_PATH_LEN, engineRoot.c_str());
|
|
|
|
AZStd::string gameFolderPath{AssetUtilities::ComputeProjectPath().toUtf8().constData()};
|
|
azstrcpy(m_absoluteDevGameFolderPath, AZ_MAX_PATH_LEN, gameFolderPath.c_str());
|
|
|
|
AssetUtilities::ComputeProjectCacheRoot(m_cacheRootDir);
|
|
|
|
if (!ConnectToDatabase())
|
|
{
|
|
AZ_Error("AssetCatalog", false, "Failed to connect to sqlite database");
|
|
}
|
|
|
|
AssetRegistryRequestBus::Handler::BusConnect();
|
|
AzToolsFramework::AssetSystemRequestBus::Handler::BusConnect();
|
|
AzToolsFramework::ToolsAssetSystemBus::Handler::BusConnect();
|
|
AZ::Data::AssetCatalogRequestBus::Handler::BusConnect();
|
|
}
|
|
|
|
AssetCatalog::~AssetCatalog()
|
|
{
|
|
AzToolsFramework::ToolsAssetSystemBus::Handler::BusDisconnect();
|
|
AzToolsFramework::AssetSystemRequestBus::Handler::BusDisconnect();
|
|
AssetRegistryRequestBus::Handler::BusDisconnect();
|
|
AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect();
|
|
SaveRegistry_Impl();
|
|
}
|
|
|
|
void AssetCatalog::OnAssetMessage(AzFramework::AssetSystem::AssetNotificationMessage message)
|
|
{
|
|
using namespace AzFramework::AssetSystem;
|
|
if (message.m_type == AssetNotificationMessage::AssetChanged)
|
|
{
|
|
//get the full product path to determine file size
|
|
AZ::Data::AssetInfo assetInfo;
|
|
assetInfo.m_assetId = message.m_assetId;
|
|
assetInfo.m_assetType = message.m_assetType;
|
|
assetInfo.m_relativePath = message.m_data.c_str();
|
|
assetInfo.m_sizeBytes = message.m_sizeBytes;
|
|
QString assetPlatform{ QString::fromUtf8(message.m_platform.c_str()) };
|
|
|
|
AZ_Assert(assetInfo.m_assetId.IsValid(), "AssetID is not valid!!!");
|
|
AZ_Assert(!assetInfo.m_relativePath.empty(), "Product path is empty");
|
|
AZ_Assert(!assetPlatform.isEmpty(), "Product platform is empty");
|
|
|
|
m_catalogIsDirty = true;
|
|
{
|
|
QMutexLocker locker(&m_registriesMutex);
|
|
m_registries[message.m_platform.c_str()].RegisterAsset(assetInfo.m_assetId, assetInfo);
|
|
for (const AZ::Data::AssetId& mapping : message.m_legacyAssetIds)
|
|
{
|
|
if (mapping != assetInfo.m_assetId)
|
|
{
|
|
m_registries[assetPlatform].RegisterLegacyAssetMapping(mapping, assetInfo.m_assetId);
|
|
}
|
|
}
|
|
|
|
m_registries[assetPlatform].SetAssetDependencies(message.m_assetId, message.m_dependencies);
|
|
|
|
using namespace AzFramework::FileTag;
|
|
|
|
// We are checking preload Dependency only for runtime assets
|
|
AZStd::vector<AZStd::string> excludedTagsList = { FileTags[static_cast<unsigned int>(FileTagsIndex::EditorOnly)] };
|
|
|
|
bool editorOnlyAsset = false;
|
|
QueryFileTagsEventBus::EventResult(editorOnlyAsset, FileTagType::Exclude,
|
|
&QueryFileTagsEventBus::Events::Match, message.m_data.c_str(), excludedTagsList);
|
|
|
|
if (!editorOnlyAsset)
|
|
{
|
|
for (auto& productDependency : message.m_dependencies)
|
|
{
|
|
auto loadBehavior = AZ::Data::ProductDependencyInfo::LoadBehaviorFromFlags(productDependency.m_flags);
|
|
if (loadBehavior == AZ::Data::AssetLoadBehavior::PreLoad)
|
|
{
|
|
m_preloadAssetList.emplace_back(AZStd::make_pair(message.m_assetId, message.m_platform.c_str()));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_registryBuiltOnce)
|
|
{
|
|
Q_EMIT SendAssetMessage(message);
|
|
}
|
|
}
|
|
else if (message.m_type == AssetNotificationMessage::AssetRemoved)
|
|
{
|
|
QMutexLocker locker(&m_registriesMutex);
|
|
|
|
QString assetPlatform{ QString::fromUtf8(message.m_platform.c_str()) };
|
|
AZ_Assert(!assetPlatform.isEmpty(), "Product platform is empty");
|
|
|
|
auto found = m_registries[assetPlatform].m_assetIdToInfo.find(message.m_assetId);
|
|
|
|
if (found != m_registries[assetPlatform].m_assetIdToInfo.end())
|
|
{
|
|
m_catalogIsDirty = true;
|
|
|
|
m_registries[assetPlatform].UnregisterAsset(message.m_assetId);
|
|
|
|
for (const AZ::Data::AssetId& mapping : message.m_legacyAssetIds)
|
|
{
|
|
if (mapping != message.m_assetId)
|
|
{
|
|
m_registries[assetPlatform].UnregisterLegacyAssetMapping(mapping);
|
|
}
|
|
}
|
|
|
|
if (m_registryBuiltOnce)
|
|
{
|
|
Q_EMIT SendAssetMessage(message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AssetCatalog::CheckValidatedAssets(AZ::Data::AssetId assetId, const QString& platform)
|
|
{
|
|
auto found = m_cachedNoPreloadDependenyAssetList.equal_range(assetId);
|
|
|
|
for (auto platformIter = found.first; platformIter != found.second; ++platformIter)
|
|
{
|
|
if (platformIter->second == platform)
|
|
{
|
|
// we have already verified this asset for this run and it does not have any preload dependency for the specified platform, therefore we can safely skip it
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
void AssetCatalog::ValidatePreLoadDependency()
|
|
{
|
|
if (m_currentlyValidatingPreloadDependency)
|
|
{
|
|
return;
|
|
}
|
|
m_currentlyValidatingPreloadDependency = true;
|
|
|
|
for (auto iter = m_preloadAssetList.begin(); iter != m_preloadAssetList.end(); iter++)
|
|
{
|
|
if (!CheckValidatedAssets(iter->first, iter->second))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AZStd::stack<AZStd::pair<AZ::Data::AssetId, AZ::Data::AssetId>> assetStack;
|
|
AZStd::vector<AZ::Data::AssetId> currentAssetTree; // this is used to determine the hierarchy of asset loads.
|
|
AZStd::unordered_set<AZ::Data::AssetId> currentVisitedAssetsTree;
|
|
AZStd::unordered_set<AZ::Data::AssetId> allVisitedAssets;
|
|
|
|
assetStack.push(AZStd::make_pair(iter->first, AZ::Data::AssetId()));
|
|
|
|
bool cyclicDependencyFound = false;
|
|
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_databaseMutex);
|
|
while (!assetStack.empty())
|
|
{
|
|
AZ::Data::AssetId assetId = assetStack.top().first;
|
|
AZ::Data::AssetId parentAssetId = assetStack.top().second;
|
|
assetStack.pop();
|
|
allVisitedAssets.insert(assetId);
|
|
|
|
while (currentAssetTree.size() && parentAssetId != currentAssetTree.back())
|
|
{
|
|
currentVisitedAssetsTree.erase(currentAssetTree.back());
|
|
currentAssetTree.pop_back();
|
|
};
|
|
|
|
currentVisitedAssetsTree.insert(assetId);
|
|
currentAssetTree.emplace_back(assetId);
|
|
|
|
m_db->QueryProductDependencyBySourceGuidSubId(assetId.m_guid, assetId.m_subId, iter->second.toUtf8().constData(), [&](const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry)
|
|
{
|
|
auto loadBehavior = AZ::Data::ProductDependencyInfo::LoadBehaviorFromFlags(entry.m_dependencyFlags);
|
|
if (loadBehavior == AZ::Data::AssetLoadBehavior::PreLoad)
|
|
{
|
|
AZ::Data::AssetId dependentAssetId(entry.m_dependencySourceGuid, entry.m_dependencySubID);
|
|
if (currentVisitedAssetsTree.find(dependentAssetId) == currentVisitedAssetsTree.end())
|
|
{
|
|
if (!CheckValidatedAssets(dependentAssetId, iter->second))
|
|
{
|
|
// we have already verified that this asset does not have any preload dependency
|
|
return true;
|
|
}
|
|
|
|
assetStack.push(AZStd::make_pair(dependentAssetId, assetId));
|
|
}
|
|
else
|
|
{
|
|
cyclicDependencyFound = true;
|
|
|
|
AZStd::string cyclicPreloadDependencyTreeString;
|
|
for (const auto& assetIdEntry : currentAssetTree)
|
|
{
|
|
AzToolsFramework::AssetDatabase::ProductDatabaseEntry productDatabaseEntry;
|
|
m_db->GetProductBySourceGuidSubId(assetIdEntry.m_guid, assetIdEntry.m_subId, productDatabaseEntry);
|
|
cyclicPreloadDependencyTreeString = cyclicPreloadDependencyTreeString + AZStd::string::format("%s ->", productDatabaseEntry.m_productName.c_str());
|
|
};
|
|
|
|
AzToolsFramework::AssetDatabase::ProductDatabaseEntry productDatabaseEntry;
|
|
m_db->GetProductBySourceGuidSubId(dependentAssetId.m_guid, dependentAssetId.m_subId, productDatabaseEntry);
|
|
|
|
cyclicPreloadDependencyTreeString = cyclicPreloadDependencyTreeString + AZStd::string::format(" %s ", productDatabaseEntry.m_productName.c_str());
|
|
|
|
AzToolsFramework::AssetDatabase::ProductDatabaseEntry productDatabaseRootEntry;
|
|
m_db->GetProductBySourceGuidSubId(iter->first.m_guid, iter->first.m_subId, productDatabaseRootEntry);
|
|
|
|
AZ_Error(AssetProcessor::ConsoleChannel, false, "Preload circular dependency detected while processing asset (%s).\n Preload hierarchy is %s . Adjust your product dependencies for assets in this chain to break this loop.",
|
|
productDatabaseRootEntry.m_productName.c_str(), cyclicPreloadDependencyTreeString.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
|
|
if (cyclicDependencyFound)
|
|
{
|
|
currentVisitedAssetsTree.clear();
|
|
currentAssetTree.clear();
|
|
AZStd::stack<AZStd::pair<AZ::Data::AssetId, AZ::Data::AssetId>> emptyAssetStack;
|
|
assetStack.swap(emptyAssetStack);
|
|
}
|
|
};
|
|
|
|
if (!cyclicDependencyFound)
|
|
{
|
|
for (const auto& assetId : allVisitedAssets)
|
|
{
|
|
m_cachedNoPreloadDependenyAssetList.emplace(AZStd::make_pair(assetId, iter->second)); // assetid, platform
|
|
}
|
|
}
|
|
}
|
|
|
|
m_preloadAssetList.clear();
|
|
m_cachedNoPreloadDependenyAssetList.clear();
|
|
m_currentlyValidatingPreloadDependency = false;
|
|
}
|
|
|
|
void AssetCatalog::SaveRegistry_Impl()
|
|
{
|
|
bool allCatalogsSaved = true;
|
|
// note that its safe not to save the catalog if the catalog is not dirty
|
|
// because the engine will be accepting updates as long as the update has a higher or equal
|
|
// number to the saveId, not just equal.
|
|
if (m_catalogIsDirty)
|
|
{
|
|
m_catalogIsDirty = false;
|
|
// Reflect registry for serialization.
|
|
AZ::SerializeContext* serializeContext = nullptr;
|
|
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
|
|
AZ_Assert(serializeContext, "Unable to retrieve serialize context.");
|
|
if (nullptr == serializeContext->FindClassData(AZ::AzTypeInfo<AzFramework::AssetRegistry>::Uuid()))
|
|
{
|
|
AzFramework::AssetRegistry::ReflectSerialize(serializeContext);
|
|
}
|
|
|
|
// save out a catalog for each platform
|
|
for (const QString& platform : m_platforms)
|
|
{
|
|
// Serialize out the catalog to a memory buffer, and then dump that memory buffer to stream.
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
m_saveBuffer.clear();
|
|
// allow this to grow by up to 20mb at a time so as not to fragment.
|
|
// we re-use the save buffer each time to further reduce memory load.
|
|
AZ::IO::ByteContainerStream<AZStd::vector<char>> catalogFileStream(&m_saveBuffer, 1024 * 1024 * 20);
|
|
|
|
// these 3 lines are what writes the entire registry to the memory stream
|
|
AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&catalogFileStream, *serializeContext, AZ::ObjectStream::ST_BINARY);
|
|
{
|
|
QMutexLocker locker(&m_registriesMutex);
|
|
objStream->WriteClass(&m_registries[platform]);
|
|
}
|
|
objStream->Finalize();
|
|
|
|
// now write the memory stream out to the temp folder
|
|
QString workSpace;
|
|
if (!AssetUtilities::CreateTempWorkspace(workSpace))
|
|
{
|
|
AZ_Warning(AssetProcessor::ConsoleChannel, false, "Failed to create a temp workspace for catalog writing\n");
|
|
}
|
|
else
|
|
{
|
|
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
|
AZ::SettingsRegistryInterface::FixedValueString cacheRootFolder;
|
|
settingsRegistry->Get(cacheRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder);
|
|
|
|
QString tempRegistryFile = QString("%1/%2").arg(workSpace).arg("assetcatalog.xml.tmp");
|
|
QString platformCacheDir = QString("%1/%2").arg(cacheRootFolder.c_str()).arg(platform);
|
|
QString actualRegistryFile = QString("%1/%2").arg(platformCacheDir).arg("assetcatalog.xml");
|
|
|
|
AZ_TracePrintf(AssetProcessor::DebugChannel, "Creating asset catalog: %s --> %s\n", tempRegistryFile.toUtf8().constData(), actualRegistryFile.toUtf8().constData());
|
|
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
|
|
if (AZ::IO::FileIOBase::GetInstance()->Open(tempRegistryFile.toUtf8().data(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, fileHandle))
|
|
{
|
|
AZ::IO::FileIOBase::GetInstance()->Write(fileHandle, m_saveBuffer.data(), m_saveBuffer.size());
|
|
AZ::IO::FileIOBase::GetInstance()->Close(fileHandle);
|
|
|
|
// Make sure that the destination folder of the registry file exists
|
|
QDir registryDir(platformCacheDir);
|
|
if (!registryDir.exists())
|
|
{
|
|
QString absPath = registryDir.absolutePath();
|
|
[[maybe_unused]] bool makeDirResult = AZ::IO::SystemFile::CreateDir(absPath.toUtf8().constData());
|
|
AZ_Warning(AssetProcessor::ConsoleChannel, makeDirResult, "Failed create folder %s", platformCacheDir.toUtf8().constData());
|
|
}
|
|
|
|
// if we succeeded in doing this, then use "rename" to move the file over the previous copy.
|
|
bool moved = AssetUtilities::MoveFileWithTimeout(tempRegistryFile, actualRegistryFile, 3);
|
|
allCatalogsSaved = allCatalogsSaved && moved;
|
|
|
|
// warn if it failed
|
|
AZ_Warning(AssetProcessor::ConsoleChannel, moved, "Failed to move %s to %s", tempRegistryFile.toUtf8().constData(), actualRegistryFile.toUtf8().constData());
|
|
|
|
if (moved)
|
|
{
|
|
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Saved %s catalog containing %u assets in %fs\n", platform.toUtf8().constData(), m_registries[platform].m_assetIdToInfo.size(), timer.elapsed() / 1000.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AZ_Warning(AssetProcessor::ConsoleChannel, false, "Failed to create catalog file %s", tempRegistryFile.toUtf8().constData());
|
|
allCatalogsSaved = false;
|
|
}
|
|
|
|
AZ::IO::FileIOBase::GetInstance()->DestroyPath(workSpace.toUtf8().data());
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
// scoped to minimize the duration of this mutex lock
|
|
QMutexLocker locker(&m_savingRegistryMutex);
|
|
m_currentlySavingCatalog = false;
|
|
RegistrySaveComplete(m_currentRegistrySaveVersion, allCatalogsSaved);
|
|
AssetRegistryNotificationBus::Broadcast(&AssetRegistryNotifications::OnRegistrySaveComplete, m_currentRegistrySaveVersion, allCatalogsSaved);
|
|
}
|
|
}
|
|
|
|
AzFramework::AssetSystem::GetUnresolvedDependencyCountsResponse AssetCatalog::HandleGetUnresolvedDependencyCountsRequest(MessageData<AzFramework::AssetSystem::GetUnresolvedDependencyCountsRequest> messageData)
|
|
{
|
|
AzFramework::AssetSystem::GetUnresolvedDependencyCountsResponse response;
|
|
|
|
{
|
|
QMutexLocker locker(&m_registriesMutex);
|
|
|
|
const auto& productDependencies = m_registries[messageData.m_platform].GetAssetDependencies(messageData.m_message->m_assetId);
|
|
|
|
for (const AZ::Data::ProductDependency& productDependency : productDependencies)
|
|
{
|
|
if (m_registries[messageData.m_platform].m_assetIdToInfo.find(productDependency.m_assetId)
|
|
== m_registries[messageData.m_platform].m_assetIdToInfo.end())
|
|
{
|
|
++response.m_unresolvedAssetIdReferences;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_databaseMutex);
|
|
|
|
m_db->QueryProductDependencyBySourceGuidSubId(messageData.m_message->m_assetId.m_guid, messageData.m_message->m_assetId.m_subId, messageData.m_platform.toUtf8().constData(), [&response](const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry)
|
|
{
|
|
if (!entry.m_unresolvedPath.empty() && entry.m_unresolvedPath.find('*') == entry.m_unresolvedPath.npos
|
|
&& !entry.m_unresolvedPath.starts_with(ExcludedDependenciesSymbol))
|
|
{
|
|
++response.m_unresolvedPathReferences;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
void AssetCatalog::HandleSaveAssetCatalogRequest(MessageData<AzFramework::AssetSystem::SaveAssetCatalogRequest> messageData)
|
|
{
|
|
int registrySaveVersion = SaveRegistry();
|
|
m_queuedSaveCatalogRequest.insert(registrySaveVersion, messageData.m_key);
|
|
}
|
|
|
|
void AssetCatalog::RegistrySaveComplete(int assetCatalogVersion, bool allCatalogsSaved)
|
|
{
|
|
for (auto iter = m_queuedSaveCatalogRequest.begin(); iter != m_queuedSaveCatalogRequest.end();)
|
|
{
|
|
if (iter.key() <= assetCatalogVersion)
|
|
{
|
|
AssetProcessor::NetworkRequestID& requestId = iter.value();
|
|
AzFramework::AssetSystem::SaveAssetCatalogResponse saveCatalogResponse;
|
|
saveCatalogResponse.m_saved = allCatalogsSaved;
|
|
AssetProcessor::ConnectionBus::Event(requestId.first, &AssetProcessor::ConnectionBus::Events::SendResponse, requestId.second, saveCatalogResponse);
|
|
iter = m_queuedSaveCatalogRequest.erase(iter);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
|
|
int AssetCatalog::SaveRegistry()
|
|
{
|
|
QMutexLocker locker(&m_savingRegistryMutex);
|
|
|
|
if (!m_currentlySavingCatalog)
|
|
{
|
|
m_currentlySavingCatalog = true;
|
|
QMetaObject::invokeMethod(this, "SaveRegistry_Impl", Qt::QueuedConnection);
|
|
return ++m_currentRegistrySaveVersion;
|
|
}
|
|
|
|
return m_currentRegistrySaveVersion;
|
|
}
|
|
|
|
void AssetCatalog::BuildRegistry()
|
|
{
|
|
m_catalogIsDirty = true;
|
|
m_registryBuiltOnce = true;
|
|
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_databaseMutex);
|
|
QMutexLocker locker(&m_registriesMutex);
|
|
|
|
for (QString platform : m_platforms)
|
|
{
|
|
auto inserted = m_registries.insert(platform, AzFramework::AssetRegistry());
|
|
AzFramework::AssetRegistry& currentRegistry = inserted.value();
|
|
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
auto databaseQueryCallback = [&](AzToolsFramework::AssetDatabase::CombinedDatabaseEntry& combined)
|
|
{
|
|
AZ::Data::AssetId assetId(combined.m_sourceGuid, combined.m_subID);
|
|
|
|
// relative file path is gotten by removing the platform and game from the product name
|
|
QString relativeProductPath = AssetUtilities::StripAssetPlatform(combined.m_productName);
|
|
QString fullProductPath = m_cacheRoot.absoluteFilePath(combined.m_productName.c_str());
|
|
|
|
AZ::Data::AssetInfo info;
|
|
info.m_assetType = combined.m_assetType;
|
|
info.m_relativePath = relativeProductPath.toUtf8().data();
|
|
info.m_assetId = assetId;
|
|
info.m_sizeBytes = AZ::IO::SystemFile::Length(fullProductPath.toUtf8().constData());
|
|
|
|
// also register it at the legacy id(s) if its different:
|
|
AZ::Data::AssetId legacyAssetId(combined.m_legacyGuid, 0);
|
|
AZ::Uuid legacySourceUuid = AssetUtilities::CreateSafeSourceUUIDFromName(combined.m_sourceName.c_str(), false);
|
|
AZ::Data::AssetId legacySourceAssetId(legacySourceUuid, combined.m_subID);
|
|
|
|
currentRegistry.RegisterAsset(assetId, info);
|
|
|
|
if (legacyAssetId != assetId)
|
|
{
|
|
currentRegistry.RegisterLegacyAssetMapping(legacyAssetId, assetId);
|
|
}
|
|
|
|
if (legacySourceAssetId != assetId)
|
|
{
|
|
currentRegistry.RegisterLegacyAssetMapping(legacySourceAssetId, assetId);
|
|
}
|
|
|
|
// now include the additional legacies based on the SubIDs by which this asset was previously referred to.
|
|
for (const auto& entry : combined.m_legacySubIDs)
|
|
{
|
|
AZ::Data::AssetId legacySubID(combined.m_sourceGuid, entry.m_subID);
|
|
if ((legacySubID != assetId) && (legacySubID != legacyAssetId) && (legacySubID != legacySourceAssetId))
|
|
{
|
|
currentRegistry.RegisterLegacyAssetMapping(legacySubID, assetId);
|
|
}
|
|
|
|
}
|
|
|
|
return true;//see them all
|
|
};
|
|
|
|
m_db->QueryCombined(
|
|
databaseQueryCallback, AZ::Uuid::CreateNull(),
|
|
nullptr,
|
|
platform.toUtf8().constData(),
|
|
AzToolsFramework::AssetSystem::JobStatus::Any,
|
|
true); /*we still need legacy IDs - hardly anyone else does*/
|
|
|
|
m_db->QueryProductDependenciesTable([this, &platform](AZ::Data::AssetId& assetId, AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry)
|
|
{
|
|
if (AzFramework::StringFunc::Equal(entry.m_platform.c_str(), platform.toUtf8().data()))
|
|
{
|
|
m_registries[platform].RegisterAssetDependency(assetId, AZ::Data::ProductDependency{ AZ::Data::AssetId(entry.m_dependencySourceGuid, entry.m_dependencySubID), entry.m_dependencyFlags });
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
AZ_TracePrintf("Catalog", "Read %u assets from database for %s in %fs\n", currentRegistry.m_assetIdToInfo.size(), platform.toUtf8().constData(), timer.elapsed() / 1000.0f);
|
|
}
|
|
}
|
|
|
|
void AssetCatalog::OnDependencyResolved(const AZ::Data::AssetId& assetId, const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry)
|
|
{
|
|
QString platform(entry.m_platform.c_str());
|
|
if (!m_platforms.contains(platform))
|
|
{
|
|
return;
|
|
}
|
|
|
|
AzFramework::AssetSystem::AssetNotificationMessage message;
|
|
message.m_type = AzFramework::AssetSystem::AssetNotificationMessage::NotificationType::AssetChanged;
|
|
|
|
// Get the existing data from registry.
|
|
AZ::Data::AssetInfo assetInfo = GetAssetInfoById(assetId);
|
|
message.m_data = assetInfo.m_relativePath;
|
|
message.m_sizeBytes = assetInfo.m_sizeBytes;
|
|
message.m_assetId = assetId;
|
|
message.m_assetType = assetInfo.m_assetType;
|
|
message.m_platform = entry.m_platform.c_str();
|
|
|
|
// Get legacyIds from registry to put in message.
|
|
AZStd::unordered_map<AZ::Data::AssetId, AZ::Data::AssetId> legacyIds;
|
|
|
|
// Add the new dependency entry and get the list of all dependencies for the message.
|
|
AZ::Data::ProductDependency newDependency{ AZ::Data::AssetId(entry.m_dependencySourceGuid, entry.m_dependencySubID), entry.m_dependencyFlags };
|
|
{
|
|
QMutexLocker locker(&m_registriesMutex);
|
|
m_registries[platform].RegisterAssetDependency(assetId, newDependency);
|
|
message.m_dependencies = AZStd::move(m_registries[platform].GetAssetDependencies(assetId));
|
|
legacyIds = m_registries[platform].GetLegacyMappingSubsetFromRealIds(AZStd::vector<AZ::Data::AssetId>{ assetId });
|
|
}
|
|
|
|
for (auto& legacyId : legacyIds)
|
|
{
|
|
message.m_legacyAssetIds.emplace_back(legacyId.first);
|
|
}
|
|
|
|
if (m_registryBuiltOnce)
|
|
{
|
|
Q_EMIT SendAssetMessage(message);
|
|
}
|
|
|
|
m_catalogIsDirty = true;
|
|
}
|
|
|
|
void AssetCatalog::OnSourceQueued(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid, QString rootPath, QString relativeFilePath)
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_sourceUUIDToSourceNameMapMutex);
|
|
|
|
SourceInfo sourceInfo = { rootPath, relativeFilePath };
|
|
m_sourceUUIDToSourceNameMap.insert({ sourceUuid, sourceInfo });
|
|
|
|
//adding legacy source uuid as well
|
|
m_sourceUUIDToSourceNameMap.insert({ legacyUuid, sourceInfo });
|
|
|
|
AZStd::string nameForMap(relativeFilePath.toUtf8().constData());
|
|
AZStd::to_lower(nameForMap.begin(), nameForMap.end());
|
|
|
|
m_sourceNameToSourceUUIDMap.insert({ nameForMap, sourceUuid });
|
|
}
|
|
|
|
void AssetCatalog::OnSourceFinished(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid)
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_sourceUUIDToSourceNameMapMutex);
|
|
|
|
auto found = m_sourceUUIDToSourceNameMap.find(sourceUuid);
|
|
if (found != m_sourceUUIDToSourceNameMap.end())
|
|
{
|
|
AZStd::string nameForMap = found->second.m_sourceName.toUtf8().constData();
|
|
AZStd::to_lower(nameForMap.begin(), nameForMap.end());
|
|
m_sourceNameToSourceUUIDMap.erase(nameForMap);
|
|
}
|
|
|
|
m_sourceUUIDToSourceNameMap.erase(sourceUuid);
|
|
m_sourceUUIDToSourceNameMap.erase(legacyUuid);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
const char* AssetCatalog::GetAbsoluteDevGameFolderPath()
|
|
{
|
|
return m_absoluteDevGameFolderPath;
|
|
}
|
|
|
|
const char* AssetCatalog::GetAbsoluteDevRootFolderPath()
|
|
{
|
|
return m_absoluteDevFolderPath;
|
|
}
|
|
|
|
bool AssetCatalog::GetRelativeProductPathFromFullSourceOrProductPath(const AZStd::string& fullSourceOrProductPath, AZStd::string& relativeProductPath)
|
|
{
|
|
ProcessGetRelativeProductPathFromFullSourceOrProductPathRequest(fullSourceOrProductPath, relativeProductPath);
|
|
|
|
if (!relativeProductPath.length())
|
|
{
|
|
// if we are here it means we have failed to determine the assetId we will send back the original path
|
|
AZ_TracePrintf(AssetProcessor::DebugChannel, "GetRelativeProductPath no result, returning original %s...\n", fullSourceOrProductPath.c_str());
|
|
relativeProductPath = fullSourceOrProductPath;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AssetCatalog::GetFullSourcePathFromRelativeProductPath(const AZStd::string& relPath, AZStd::string& fullSourcePath)
|
|
{
|
|
ProcessGetFullSourcePathFromRelativeProductPathRequest(relPath, fullSourcePath);
|
|
|
|
if (!fullSourcePath.length())
|
|
{
|
|
// if we are here it means that we failed to determine the full source path from the relative path and we will send back the original path
|
|
AZ_TracePrintf(AssetProcessor::DebugChannel, "GetFullSourcePath no result, returning original %s...\n", relPath.c_str());
|
|
fullSourcePath = relPath;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AssetCatalog::GetAssetInfoById(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, AZStd::string& rootFilePath)
|
|
{
|
|
assetInfo.m_assetId.SetInvalid();
|
|
assetInfo.m_relativePath.clear();
|
|
assetInfo.m_assetType = AZ::Data::s_invalidAssetType;
|
|
assetInfo.m_sizeBytes = 0;
|
|
|
|
// If the assetType wasn't provided, try to guess it
|
|
if (assetType.IsNull())
|
|
{
|
|
return GetAssetInfoByIdOnly(assetId, platformName, assetInfo, rootFilePath);
|
|
}
|
|
|
|
bool isSourceType;
|
|
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_sourceAssetTypesMutex);
|
|
isSourceType = m_sourceAssetTypes.find(assetType) != m_sourceAssetTypes.end();
|
|
}
|
|
|
|
// If the assetType is registered as a source type, look up the source info
|
|
if (isSourceType)
|
|
{
|
|
AZStd::string relativePath;
|
|
|
|
if (GetSourceFileInfoFromAssetId(assetId, rootFilePath, relativePath))
|
|
{
|
|
AZStd::string sourceFileFullPath;
|
|
AzFramework::StringFunc::Path::Join(rootFilePath.c_str(), relativePath.c_str(), sourceFileFullPath);
|
|
|
|
assetInfo.m_assetId = assetId;
|
|
assetInfo.m_assetType = assetType;
|
|
assetInfo.m_relativePath = relativePath;
|
|
assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(sourceFileFullPath.c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return the product file info
|
|
rootFilePath.clear(); // products don't have root file paths.
|
|
assetInfo = GetProductAssetInfo(platformName.c_str(), assetId);
|
|
|
|
return !assetInfo.m_relativePath.empty();
|
|
}
|
|
|
|
QString AssetCatalog::GetDefaultAssetPlatform()
|
|
{
|
|
// get the first available platform, preferring the host platform.
|
|
if (m_platforms.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()))
|
|
{
|
|
return QString::fromUtf8(AzToolsFramework::AssetSystem::GetHostAssetPlatform());
|
|
}
|
|
|
|
// the GetHostAssetPlatform() "pc" or "osx" is not actually enabled for this compilation (maybe "server" or similar is in a build job).
|
|
// in that case, we'll use the first we find!
|
|
return m_platforms[0];
|
|
}
|
|
|
|
|
|
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> AssetCatalog::GetDirectProductDependencies(
|
|
const AZ::Data::AssetId& id)
|
|
{
|
|
QString platform = GetDefaultAssetPlatform();
|
|
|
|
QMutexLocker locker(&m_registriesMutex);
|
|
|
|
auto itr = m_registries[platform].m_assetDependencies.find(id);
|
|
|
|
if (itr == m_registries[platform].m_assetDependencies.end())
|
|
{
|
|
return AZ::Failure<AZStd::string>("Failed to find asset in dependency map");
|
|
}
|
|
|
|
return AZ::Success(itr->second);
|
|
}
|
|
|
|
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> AssetCatalog::GetAllProductDependencies(const AZ::Data::AssetId& id)
|
|
{
|
|
return GetAllProductDependenciesFilter(id, {}, {});
|
|
}
|
|
|
|
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> AssetCatalog::GetAllProductDependenciesFilter(
|
|
const AZ::Data::AssetId& id,
|
|
const AZStd::unordered_set<AZ::Data::AssetId>& exclusionList,
|
|
const AZStd::vector<AZStd::string>& wildcardPatternExclusionList)
|
|
{
|
|
AZStd::vector<AZ::Data::ProductDependency> dependencyList;
|
|
AZStd::unordered_set<AZ::Data::AssetId> assetSet;
|
|
AZ::Data::PreloadAssetListType preloadList;
|
|
if (exclusionList.find(id) != exclusionList.end())
|
|
{
|
|
return AZ::Success(AZStd::move(dependencyList));
|
|
}
|
|
|
|
for (const AZStd::string& wildcardPattern : wildcardPatternExclusionList)
|
|
{
|
|
if (DoesAssetIdMatchWildcardPattern(id, wildcardPattern))
|
|
{
|
|
return AZ::Success(AZStd::move(dependencyList));
|
|
}
|
|
}
|
|
|
|
AddAssetDependencies(id, assetSet, dependencyList, exclusionList, wildcardPatternExclusionList, preloadList);
|
|
|
|
// dependencyList will be appended to while looping, so use a traditional loop
|
|
for (size_t i = 0; i < dependencyList.size(); ++i)
|
|
{
|
|
AddAssetDependencies(dependencyList[i].m_assetId, assetSet, dependencyList, exclusionList, wildcardPatternExclusionList, preloadList);
|
|
}
|
|
|
|
return AZ::Success(AZStd::move(dependencyList));
|
|
}
|
|
|
|
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> AssetCatalog::GetLoadBehaviorProductDependencies(
|
|
const AZ::Data::AssetId& id, AZStd::unordered_set<AZ::Data::AssetId>& noloadSet,
|
|
AZ::Data::PreloadAssetListType& preloadAssetList)
|
|
{
|
|
AZStd::vector<AZ::Data::ProductDependency> dependencyList;
|
|
AZStd::vector<AZ::Data::ProductDependency> returnList;
|
|
AZStd::unordered_set<AZ::Data::AssetId> assetSet;
|
|
|
|
AddAssetDependencies(id, assetSet, dependencyList, {}, {}, preloadAssetList);
|
|
|
|
// dependencyList will be appended to while looping, so use a traditional loop
|
|
for (size_t i = 0; i < dependencyList.size(); ++i)
|
|
{
|
|
if (AZ::Data::ProductDependencyInfo::LoadBehaviorFromFlags(dependencyList[i].m_flags) == AZ::Data::AssetLoadBehavior::NoLoad)
|
|
{
|
|
noloadSet.insert(dependencyList[i].m_assetId);
|
|
assetSet.erase(dependencyList[i].m_assetId);
|
|
}
|
|
else
|
|
{
|
|
returnList.push_back(dependencyList[i]);
|
|
AddAssetDependencies(dependencyList[i].m_assetId, assetSet, dependencyList, {}, {}, preloadAssetList);
|
|
}
|
|
}
|
|
|
|
return AZ::Success(AZStd::move(returnList));
|
|
}
|
|
|
|
bool AssetCatalog::DoesAssetIdMatchWildcardPattern(const AZ::Data::AssetId& assetId, const AZStd::string& wildcardPattern)
|
|
{
|
|
if (wildcardPattern.empty())
|
|
{
|
|
// pattern is empty, there is nothing to match
|
|
return false;
|
|
}
|
|
|
|
AZStd::string relativePath = GetAssetPathById(assetId);
|
|
if (relativePath.empty())
|
|
{
|
|
// assetId did not resolve to a relative path, cannot be matched
|
|
return false;
|
|
}
|
|
|
|
return AZStd::wildcard_match(wildcardPattern, relativePath);
|
|
}
|
|
|
|
void AssetCatalog::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)
|
|
{
|
|
using namespace AZ::Data;
|
|
|
|
QString platform = GetDefaultAssetPlatform();
|
|
|
|
QMutexLocker locker(&m_registriesMutex);
|
|
|
|
auto itr = m_registries[platform].m_assetDependencies.find(searchAssetId);
|
|
|
|
if (itr != m_registries[platform].m_assetDependencies.end())
|
|
{
|
|
AZStd::vector<ProductDependency>& assetDependencyList = itr->second;
|
|
|
|
for (const ProductDependency& dependency : assetDependencyList)
|
|
{
|
|
if (!dependency.m_assetId.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (exclusionList.find(dependency.m_assetId) != exclusionList.end())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool isWildcardMatch = false;
|
|
for (const AZStd::string& wildcardPattern : wildcardPatternExclusionList)
|
|
{
|
|
isWildcardMatch = DoesAssetIdMatchWildcardPattern(dependency.m_assetId, wildcardPattern);
|
|
if (isWildcardMatch)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (isWildcardMatch)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto loadBehavior = AZ::Data::ProductDependencyInfo::LoadBehaviorFromFlags(dependency.m_flags);
|
|
if (loadBehavior == AZ::Data::AssetLoadBehavior::PreLoad)
|
|
{
|
|
preloadAssetList[searchAssetId].insert(dependency.m_assetId);
|
|
}
|
|
|
|
// Only proceed if this ID is valid and we haven't encountered this assetId before.
|
|
// Invalid IDs usually come from unmet path product dependencies.
|
|
if (assetSet.find(dependency.m_assetId) == assetSet.end())
|
|
{
|
|
assetSet.insert(dependency.m_assetId); // add to the set of already-encountered assets
|
|
dependencyList.push_back(dependency); // put it in the flat list of dependencies we've found
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AssetCatalog::GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder)
|
|
{
|
|
if (!sourcePath)
|
|
{
|
|
assetInfo.m_assetId.SetInvalid();
|
|
return false;
|
|
}
|
|
|
|
// regardless of which way we come into this function we must always use ConvertToRelativePath
|
|
// to convert from whatever the input format is to a database path (which may include output prefix)
|
|
QString databaseName;
|
|
QString scanFolder;
|
|
if (!AzFramework::StringFunc::Path::IsRelative(sourcePath))
|
|
{
|
|
// absolute paths just get converted directly.
|
|
m_platformConfig->ConvertToRelativePath(QString::fromUtf8(sourcePath), databaseName, scanFolder);
|
|
}
|
|
else
|
|
{
|
|
// relative paths get the first matching asset, and then they get the usual call.
|
|
QString absolutePath = m_platformConfig->FindFirstMatchingFile(QString::fromUtf8(sourcePath));
|
|
if (!absolutePath.isEmpty())
|
|
{
|
|
m_platformConfig->ConvertToRelativePath(absolutePath, databaseName, scanFolder);
|
|
}
|
|
}
|
|
|
|
if ((databaseName.isEmpty()) || (scanFolder.isEmpty()))
|
|
{
|
|
assetInfo.m_assetId.SetInvalid();
|
|
return false;
|
|
}
|
|
|
|
// now that we have a database path, we can at least return something.
|
|
// but source info also includes UUID, which we need to hit the database for (or the in-memory map).
|
|
|
|
// Check the database first for the UUID now that we have the "database name" (which includes output prefix)
|
|
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_databaseMutex);
|
|
AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer returnedSources;
|
|
|
|
if (m_db->GetSourcesBySourceName(databaseName, returnedSources))
|
|
{
|
|
if (!returnedSources.empty())
|
|
{
|
|
AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry = returnedSources.front();
|
|
|
|
AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanEntry;
|
|
if (m_db->GetScanFolderByScanFolderID(entry.m_scanFolderPK, scanEntry))
|
|
{
|
|
watchFolder = scanEntry.m_scanFolder;
|
|
// since we are returning the UUID of a source file, as opposed to the full assetId of a product file produced by that source file,
|
|
// the subId part of the assetId will always be set to zero.
|
|
assetInfo.m_assetId = AZ::Data::AssetId(entry.m_sourceGuid, 0);
|
|
|
|
assetInfo.m_relativePath = entry.m_sourceName;
|
|
AZStd::string absolutePath;
|
|
AzFramework::StringFunc::Path::Join(scanEntry.m_scanFolder.c_str(), assetInfo.m_relativePath.c_str(), absolutePath);
|
|
assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(absolutePath.c_str());
|
|
|
|
assetInfo.m_assetType = AZ::Uuid::CreateNull(); // most source files don't have a type!
|
|
|
|
// Go through the list of source assets and see if this asset's file path matches any of the filters
|
|
for (const auto& pair : m_sourceAssetTypeFilters)
|
|
{
|
|
if (AZStd::wildcard_match(pair.first, assetInfo.m_relativePath))
|
|
{
|
|
assetInfo.m_assetType = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Source file isn't in the database yet, see if its in the job queue
|
|
if (GetQueuedAssetInfoByRelativeSourceName(databaseName.toUtf8().data(), assetInfo, watchFolder))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Source file isn't in the job queue yet, source UUID needs to be created
|
|
watchFolder = scanFolder.toUtf8().data();
|
|
return GetUncachedSourceInfoFromDatabaseNameAndWatchFolder(databaseName.toUtf8().data(), watchFolder.c_str(), assetInfo);
|
|
}
|
|
|
|
bool AssetCatalog::GetSourceInfoBySourceUUID(const AZ::Uuid& sourceUuid, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder)
|
|
{
|
|
AZ::Data::AssetId partialId(sourceUuid, 0);
|
|
AZStd::string relativePath;
|
|
|
|
if (GetSourceFileInfoFromAssetId(partialId, watchFolder, relativePath))
|
|
{
|
|
AZStd::string sourceFileFullPath;
|
|
AzFramework::StringFunc::Path::Join(watchFolder.c_str(), relativePath.c_str(), sourceFileFullPath);
|
|
assetInfo.m_assetId = partialId;
|
|
assetInfo.m_assetType = AZ::Uuid::CreateNull(); // most source files don't have a type!
|
|
assetInfo.m_relativePath = relativePath;
|
|
assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(sourceFileFullPath.c_str());
|
|
|
|
// if the type has registered with a typeid, then supply it here
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_sourceAssetTypesMutex);
|
|
|
|
// Go through the list of source assets and see if this asset's file path matches any of the filters
|
|
// if it does, we know what type it is (if not, the above call to CreateNull ensures it is null).
|
|
for (const auto& pair : m_sourceAssetTypeFilters)
|
|
{
|
|
if (AZStd::wildcard_match(pair.first, relativePath))
|
|
{
|
|
assetInfo.m_assetType = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// failed!
|
|
return false;
|
|
}
|
|
|
|
bool AssetCatalog::GetAssetsProducedBySourceUUID(const AZ::Uuid& sourceUuid, AZStd::vector<AZ::Data::AssetInfo>& productsAssetInfo)
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_databaseMutex);
|
|
|
|
AzToolsFramework::AssetDatabase::SourceDatabaseEntry entry;
|
|
|
|
if (m_db->GetSourceBySourceGuid(sourceUuid, entry))
|
|
{
|
|
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
|
|
|
|
if (m_db->GetProductsBySourceID(entry.m_sourceID, products))
|
|
{
|
|
for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
|
|
{
|
|
AZ::Data::AssetInfo assetInfo;
|
|
assetInfo.m_assetId = AZ::Data::AssetId(sourceUuid, product.m_subID);
|
|
assetInfo.m_assetType = product.m_assetType;
|
|
productsAssetInfo.emplace_back(assetInfo);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool AssetCatalog::GetScanFolders(AZStd::vector<AZStd::string>& scanFolders)
|
|
{
|
|
int scanFolderCount = m_platformConfig->GetScanFolderCount();
|
|
for (int i = 0; i < scanFolderCount; ++i)
|
|
{
|
|
scanFolders.push_back(m_platformConfig->GetScanFolderAt(i).ScanPath().toUtf8().constData());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AssetCatalog::GetAssetSafeFolders(AZStd::vector<AZStd::string>& assetSafeFolders)
|
|
{
|
|
int scanFolderCount = m_platformConfig->GetScanFolderCount();
|
|
for (int scanFolderIndex = 0; scanFolderIndex < scanFolderCount; ++scanFolderIndex)
|
|
{
|
|
AssetProcessor::ScanFolderInfo& scanFolder = m_platformConfig->GetScanFolderAt(scanFolderIndex);
|
|
if (scanFolder.CanSaveNewAssets())
|
|
{
|
|
assetSafeFolders.push_back(scanFolder.ScanPath().toUtf8().constData());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AssetCatalog::IsAssetPlatformEnabled(const char* platform)
|
|
{
|
|
const AZStd::vector<AssetBuilderSDK::PlatformInfo>& enabledPlatforms = m_platformConfig->GetEnabledPlatforms();
|
|
for (const AssetBuilderSDK::PlatformInfo& platformInfo : enabledPlatforms)
|
|
{
|
|
if (platformInfo.m_identifier == platform)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int AssetCatalog::GetPendingAssetsForPlatform(const char* /*platform*/)
|
|
{
|
|
AZ_Assert(false, "Call to unsupported Asset Processor function GetPendingAssetsForPlatform on AssetCatalog");
|
|
return -1;
|
|
}
|
|
|
|
AZStd::string AssetCatalog::GetAssetPathById(const AZ::Data::AssetId& id)
|
|
{
|
|
return GetAssetInfoById(id).m_relativePath;
|
|
|
|
}
|
|
|
|
AZ::Data::AssetId AssetCatalog::GetAssetIdByPath(const char* path, const AZ::Data::AssetType& typeToRegister, bool autoRegisterIfNotFound)
|
|
{
|
|
AZ_UNUSED(autoRegisterIfNotFound);
|
|
AZ_Assert(autoRegisterIfNotFound == false, "Auto registration is invalid during asset processing.");
|
|
AZ_UNUSED(typeToRegister);
|
|
AZ_Assert(typeToRegister == AZ::Data::s_invalidAssetType, "Can not register types during asset processing.");
|
|
AZStd::string relProductPath;
|
|
GetRelativeProductPathFromFullSourceOrProductPath(path, relProductPath);
|
|
QString tempPlatformName = GetDefaultAssetPlatform();
|
|
|
|
AZ::Data::AssetId assetId;
|
|
{
|
|
QMutexLocker locker(&m_registriesMutex);
|
|
assetId = m_registries[tempPlatformName].GetAssetIdByPath(relProductPath.c_str());
|
|
}
|
|
return assetId;
|
|
}
|
|
|
|
AZ::Data::AssetInfo AssetCatalog::GetAssetInfoById(const AZ::Data::AssetId& id)
|
|
{
|
|
AZ::Data::AssetType assetType;
|
|
AZ::Data::AssetInfo assetInfo;
|
|
AZStd::string rootFilePath;
|
|
GetAssetInfoById(id, assetType, "", assetInfo, rootFilePath);
|
|
return assetInfo;
|
|
}
|
|
|
|
bool ConvertDatabaseProductPathToProductFilename(AZStd::string_view dbPath, QString& productFileName)
|
|
{
|
|
// Always strip the leading directory from the product path
|
|
// The leading directory can be either an asset platform path or a subfolder
|
|
AZ::StringFunc::TokenizeNext(dbPath, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR);
|
|
if (!dbPath.empty())
|
|
{
|
|
productFileName = QString::fromUtf8(dbPath.data(), aznumeric_cast<int>(dbPath.size()));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void AssetCatalog::ProcessGetRelativeProductPathFromFullSourceOrProductPathRequest(const AZStd::string& fullPath, AZStd::string& relativeProductPath)
|
|
{
|
|
QString sourceOrProductPath = fullPath.c_str();
|
|
QString normalizedSourceOrProductPath = AssetUtilities::NormalizeFilePath(sourceOrProductPath);
|
|
|
|
QString productFileName;
|
|
bool resultCode = false;
|
|
QDir inputPath(normalizedSourceOrProductPath);
|
|
|
|
AZ_TracePrintf(AssetProcessor::DebugChannel, "ProcessGetRelativeProductPath: %s...\n", sourceOrProductPath.toUtf8().constData());
|
|
|
|
if (inputPath.isRelative())
|
|
{
|
|
//if the path coming in is already a relative path,we just send it back
|
|
productFileName = sourceOrProductPath;
|
|
resultCode = true;
|
|
}
|
|
else
|
|
{
|
|
QDir cacheRoot;
|
|
AssetUtilities::ComputeProjectCacheRoot(cacheRoot);
|
|
QString normalizedCacheRoot = AssetUtilities::NormalizeFilePath(cacheRoot.path());
|
|
|
|
bool inCacheFolder = normalizedSourceOrProductPath.startsWith(normalizedCacheRoot, Qt::CaseInsensitive);
|
|
if (inCacheFolder)
|
|
{
|
|
// The path send by the game/editor contains the cache root so we try to find the asset id
|
|
// from the asset database
|
|
normalizedSourceOrProductPath.remove(0, normalizedCacheRoot.length() + 1); // adding 1 for the native separator
|
|
|
|
// If we are here it means that the asset database does not have any knowledge about this file,
|
|
// most probably because AP has not processed the file yet
|
|
// In this case we will try to compute the asset id from the product path
|
|
// Now after removing the cache root,normalizedInputAssetPath can either be $Platform/$Game/xxx/yyy or something like $Platform/zzz
|
|
// and the corresponding assetId have to be either xxx/yyy or zzz
|
|
|
|
resultCode = ConvertDatabaseProductPathToProductFilename(normalizedSourceOrProductPath.toUtf8().data(), productFileName);
|
|
}
|
|
else
|
|
{
|
|
// If we are here it means its a source file, first see whether there is any overriding file and than try to find products
|
|
QString scanFolder;
|
|
QString relativeName;
|
|
if (m_platformConfig->ConvertToRelativePath(normalizedSourceOrProductPath, relativeName, scanFolder))
|
|
{
|
|
QString overridingFile = m_platformConfig->GetOverridingFile(relativeName, scanFolder);
|
|
|
|
if (overridingFile.isEmpty())
|
|
{
|
|
// no overriding file found
|
|
overridingFile = normalizedSourceOrProductPath;
|
|
}
|
|
else
|
|
{
|
|
overridingFile = AssetUtilities::NormalizeFilePath(overridingFile);
|
|
}
|
|
|
|
if (m_platformConfig->ConvertToRelativePath(overridingFile, relativeName, scanFolder))
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_databaseMutex);
|
|
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
|
|
|
|
if (m_db->GetProductsBySourceName(relativeName, products))
|
|
{
|
|
resultCode = ConvertDatabaseProductPathToProductFilename(products[0].m_productName, productFileName);
|
|
}
|
|
else
|
|
{
|
|
productFileName = relativeName;
|
|
resultCode = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!resultCode)
|
|
{
|
|
productFileName = "";
|
|
}
|
|
|
|
relativeProductPath = productFileName.toUtf8().data();
|
|
}
|
|
|
|
void AssetCatalog::ProcessGetFullSourcePathFromRelativeProductPathRequest(const AZStd::string& relPath, AZStd::string& fullSourcePath)
|
|
{
|
|
QString assetPath = relPath.c_str();
|
|
QString normalisedAssetPath = AssetUtilities::NormalizeFilePath(assetPath);
|
|
int resultCode = 0;
|
|
QString fullAssetPath;
|
|
|
|
if (normalisedAssetPath.isEmpty())
|
|
{
|
|
fullSourcePath = "";
|
|
return;
|
|
}
|
|
|
|
QDir inputPath(normalisedAssetPath);
|
|
|
|
if (inputPath.isAbsolute())
|
|
{
|
|
bool inCacheFolder = false;
|
|
QDir cacheRoot;
|
|
AssetUtilities::ComputeProjectCacheRoot(cacheRoot);
|
|
QString normalizedCacheRoot = AssetUtilities::NormalizeFilePath(cacheRoot.path());
|
|
//Check to see whether the path contains the cache root
|
|
inCacheFolder = normalisedAssetPath.startsWith(normalizedCacheRoot, Qt::CaseInsensitive);
|
|
|
|
if (!inCacheFolder)
|
|
{
|
|
// Attempt to convert to relative path
|
|
QString dummy, convertedRelPath;
|
|
if (m_platformConfig->ConvertToRelativePath(assetPath, convertedRelPath, dummy))
|
|
{
|
|
// then find the first matching file to get correct casing
|
|
fullAssetPath = m_platformConfig->FindFirstMatchingFile(convertedRelPath);
|
|
}
|
|
|
|
if (fullAssetPath.isEmpty())
|
|
{
|
|
// if we couldn't find it, just return the passed in path
|
|
fullAssetPath = assetPath;
|
|
}
|
|
|
|
resultCode = 1;
|
|
}
|
|
else
|
|
{
|
|
// The path send by the game/editor contains the cache root ,try to find the productName from it
|
|
normalisedAssetPath.remove(0, normalizedCacheRoot.length() + 1); // adding 1 for the native separator
|
|
}
|
|
}
|
|
|
|
if (!resultCode)
|
|
{
|
|
//remove aliases if present
|
|
normalisedAssetPath = AssetUtilities::NormalizeAndRemoveAlias(normalisedAssetPath);
|
|
|
|
if (!normalisedAssetPath.isEmpty()) // this happens if it comes in as just for example "@assets@/"
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_databaseMutex);
|
|
|
|
//We should have the asset now, we can now find the full asset path
|
|
// we have to check each platform individually until we get a hit.
|
|
const auto& platforms = m_platformConfig->GetEnabledPlatforms();
|
|
QString productName;
|
|
for (const AssetBuilderSDK::PlatformInfo& platformInfo : platforms)
|
|
{
|
|
QString platformName = QString::fromUtf8(platformInfo.m_identifier.c_str());
|
|
productName = AssetUtilities::GuessProductNameInDatabase(normalisedAssetPath, platformName, m_db.get());
|
|
if (!productName.isEmpty())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!productName.isEmpty())
|
|
{
|
|
//Now find the input name for the path,if we are here this should always return true since we were able to find the productName before
|
|
AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer sources;
|
|
if (m_db->GetSourcesByProductName(productName, sources))
|
|
{
|
|
//Once we have found the inputname we will try finding the full path
|
|
fullAssetPath = m_platformConfig->FindFirstMatchingFile(sources[0].m_sourceName.c_str());
|
|
if (!fullAssetPath.isEmpty())
|
|
{
|
|
resultCode = 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we are not able to guess the product name than maybe the asset path is an input name
|
|
fullAssetPath = m_platformConfig->FindFirstMatchingFile(normalisedAssetPath);
|
|
if (!fullAssetPath.isEmpty())
|
|
{
|
|
resultCode = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!resultCode)
|
|
{
|
|
fullSourcePath = "";
|
|
}
|
|
else
|
|
{
|
|
fullSourcePath = fullAssetPath.toUtf8().data();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void AssetCatalog::RegisterSourceAssetType(const AZ::Data::AssetType& assetType, const char* assetFileFilter)
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_sourceAssetTypesMutex);
|
|
m_sourceAssetTypes.insert(assetType);
|
|
AZStd::vector<AZStd::string> tokens;
|
|
AZStd::string semicolonSeperated(assetFileFilter);
|
|
AZStd::tokenize(semicolonSeperated, AZStd::string(";"), tokens);
|
|
|
|
for (const auto& pattern : tokens)
|
|
{
|
|
m_sourceAssetTypeFilters[pattern] = assetType;
|
|
}
|
|
}
|
|
|
|
void AssetCatalog::UnregisterSourceAssetType(const AZ::Data::AssetType& /*assetType*/)
|
|
{
|
|
// For now, this does nothing, because it would just needlessly complicate things for no gain.
|
|
// Unregister is only called when a builder is shut down, which really is only supposed to happen when AssetCatalog is being shutdown
|
|
// Without a way of tracking how many builders have registered the same assetType and being able to perfectly keep track of every builder shutdown, even in the event of a crash,
|
|
// the map would either be cleared prematurely or never get cleared at all
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
bool AssetCatalog::GetSourceFileInfoFromAssetId(const AZ::Data::AssetId &assetId, AZStd::string& watchFolder, AZStd::string& relativePath)
|
|
{
|
|
// Check the database first
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_databaseMutex);
|
|
AzToolsFramework::AssetDatabase::SourceDatabaseEntry entry;
|
|
|
|
if (m_db->GetSourceBySourceGuid(assetId.m_guid, entry))
|
|
{
|
|
AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanEntry;
|
|
if (m_db->GetScanFolderByScanFolderID(entry.m_scanFolderPK, scanEntry))
|
|
{
|
|
relativePath = entry.m_sourceName;
|
|
|
|
watchFolder = scanEntry.m_scanFolder;
|
|
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Source file isn't in the database yet, see if its in the job queue
|
|
return GetQueuedAssetInfoById(assetId.m_guid, watchFolder, relativePath);
|
|
}
|
|
|
|
AZ::Data::AssetInfo AssetCatalog::GetProductAssetInfo(const char* platformName, const AZ::Data::AssetId& assetId)
|
|
{
|
|
// this more or less follows the same algorithm that the game uses to look up products.
|
|
using namespace AZ::Data;
|
|
using namespace AzFramework;
|
|
|
|
if ((!assetId.IsValid()) || (m_platforms.isEmpty()))
|
|
{
|
|
return AssetInfo();
|
|
}
|
|
|
|
// in case no platform name has been given, we are prepared to compute one.
|
|
QString tempPlatformName;
|
|
|
|
// if no platform specified, we'll use the current platform.
|
|
if ((!platformName) || (platformName[0] == 0))
|
|
{
|
|
tempPlatformName = GetDefaultAssetPlatform();
|
|
}
|
|
else
|
|
{
|
|
tempPlatformName = QString::fromUtf8(platformName);
|
|
}
|
|
|
|
// note that m_platforms is not mutated at all during runtime, so we ignore it in the lock
|
|
if (!m_platforms.contains(tempPlatformName))
|
|
{
|
|
return AssetInfo();
|
|
}
|
|
|
|
QMutexLocker locker(&m_registriesMutex);
|
|
|
|
const AssetRegistry& registryToUse = m_registries[tempPlatformName];
|
|
|
|
auto foundIter = registryToUse.m_assetIdToInfo.find(assetId);
|
|
if (foundIter != registryToUse.m_assetIdToInfo.end())
|
|
{
|
|
return foundIter->second;
|
|
}
|
|
|
|
// we did not find it - try the backup mapping!
|
|
AssetId legacyMapping = registryToUse.GetAssetIdByLegacyAssetId(assetId);
|
|
if (legacyMapping.IsValid())
|
|
{
|
|
return GetProductAssetInfo(platformName, legacyMapping);
|
|
}
|
|
|
|
return AssetInfo(); // not found!
|
|
}
|
|
|
|
bool AssetCatalog::GetAssetInfoByIdOnly(const AZ::Data::AssetId& id, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, AZStd::string& rootFilePath)
|
|
{
|
|
AZStd::string relativePath;
|
|
|
|
if (GetSourceFileInfoFromAssetId(id, rootFilePath, relativePath))
|
|
{
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_sourceAssetTypesMutex);
|
|
|
|
// Go through the list of source assets and see if this asset's file path matches any of the filters
|
|
for (const auto& pair : m_sourceAssetTypeFilters)
|
|
{
|
|
if (AZStd::wildcard_match(pair.first, relativePath))
|
|
{
|
|
AZStd::string sourceFileFullPath;
|
|
AzFramework::StringFunc::Path::Join(rootFilePath.c_str(), relativePath.c_str(), sourceFileFullPath);
|
|
|
|
assetInfo.m_assetId = id;
|
|
assetInfo.m_assetType = pair.second;
|
|
assetInfo.m_relativePath = relativePath;
|
|
assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(sourceFileFullPath.c_str());
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we get to here, we're going to assume it's a product type
|
|
rootFilePath.clear();
|
|
assetInfo = GetProductAssetInfo(platformName.c_str(), id);
|
|
|
|
return !assetInfo.m_relativePath.empty();
|
|
}
|
|
|
|
// Asset isn't in the DB or in the APM queue, we don't know what this asset ID is
|
|
return false;
|
|
}
|
|
|
|
bool AssetCatalog::GetQueuedAssetInfoById(const AZ::Uuid& guid, AZStd::string& watchFolder, AZStd::string& relativePath)
|
|
{
|
|
if (!guid.IsNull())
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_sourceUUIDToSourceNameMapMutex);
|
|
|
|
auto foundSource = m_sourceUUIDToSourceNameMap.find(guid);
|
|
if (foundSource != m_sourceUUIDToSourceNameMap.end())
|
|
{
|
|
const SourceInfo& sourceInfo = foundSource->second;
|
|
|
|
watchFolder = sourceInfo.m_watchFolder.toStdString().c_str();
|
|
relativePath = sourceInfo.m_sourceName.toStdString().c_str();
|
|
|
|
return true;
|
|
}
|
|
|
|
AZ_TracePrintf(AssetProcessor::DebugChannel, "GetQueuedAssetInfoById: AssetCatalog unable to find the requested source asset having uuid (%s).\n", guid.ToString<AZStd::string>().c_str());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool AssetCatalog::GetQueuedAssetInfoByRelativeSourceName(const char* sourceName, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder)
|
|
{
|
|
if (sourceName)
|
|
{
|
|
AZStd::string sourceNameForMap = sourceName;
|
|
AZStd::to_lower(sourceNameForMap.begin(), sourceNameForMap.end());
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_sourceUUIDToSourceNameMapMutex);
|
|
|
|
auto foundSourceUUID = m_sourceNameToSourceUUIDMap.find(sourceNameForMap);
|
|
if (foundSourceUUID != m_sourceNameToSourceUUIDMap.end())
|
|
{
|
|
auto foundSource = m_sourceUUIDToSourceNameMap.find(foundSourceUUID->second);
|
|
if (foundSource != m_sourceUUIDToSourceNameMap.end())
|
|
{
|
|
const SourceInfo& sourceInfo = foundSource->second;
|
|
|
|
watchFolder = sourceInfo.m_watchFolder.toStdString().c_str();
|
|
|
|
AZStd::string sourceNameStr(sourceInfo.m_sourceName.toStdString().c_str());
|
|
assetInfo.m_relativePath.swap(sourceNameStr);
|
|
|
|
assetInfo.m_assetId = foundSource->first;
|
|
|
|
AZStd::string sourceFileFullPath;
|
|
AzFramework::StringFunc::Path::Join(watchFolder.c_str(), assetInfo.m_relativePath.c_str(), sourceFileFullPath);
|
|
assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(sourceFileFullPath.c_str());
|
|
|
|
assetInfo.m_assetType = AZ::Uuid::CreateNull(); // most source files don't have a type!
|
|
|
|
// Go through the list of source assets and see if this asset's file path matches any of the filters
|
|
for (const auto& pair : m_sourceAssetTypeFilters)
|
|
{
|
|
if (AZStd::wildcard_match(pair.first, assetInfo.m_relativePath))
|
|
{
|
|
assetInfo.m_assetType = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
assetInfo.m_assetId.SetInvalid();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool AssetCatalog::GetUncachedSourceInfoFromDatabaseNameAndWatchFolder(const char* sourceDatabaseName, const char* watchFolder, AZ::Data::AssetInfo& assetInfo)
|
|
{
|
|
AZ::Uuid sourceUUID = AssetUtilities::CreateSafeSourceUUIDFromName(sourceDatabaseName);
|
|
if (sourceUUID.IsNull())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
AZ::Data::AssetId sourceAssetId(sourceUUID, 0);
|
|
assetInfo.m_assetId = sourceAssetId;
|
|
|
|
// If relative path starts with the output prefix then remove it first
|
|
const ScanFolderInfo* scanFolderInfo = m_platformConfig->GetScanFolderForFile(watchFolder);
|
|
if (!scanFolderInfo)
|
|
{
|
|
return false;
|
|
}
|
|
QString databasePath = QString::fromUtf8(sourceDatabaseName);
|
|
assetInfo.m_relativePath = sourceDatabaseName;
|
|
|
|
AZStd::string absolutePath;
|
|
AzFramework::StringFunc::Path::Join(watchFolder, assetInfo.m_relativePath.c_str(), absolutePath);
|
|
assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(absolutePath.c_str());
|
|
// Make sure the source file exists
|
|
if (assetInfo.m_sizeBytes == 0 && !AZ::IO::SystemFile::Exists(absolutePath.c_str()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
assetInfo.m_assetType = AZ::Uuid::CreateNull();
|
|
|
|
// Go through the list of source assets and see if this asset's file path matches any of the filters
|
|
for (const auto& pair : m_sourceAssetTypeFilters)
|
|
{
|
|
if (AZStd::wildcard_match(pair.first, assetInfo.m_relativePath))
|
|
{
|
|
assetInfo.m_assetType = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AssetCatalog::ConnectToDatabase()
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_databaseMutex);
|
|
|
|
if (!m_db)
|
|
{
|
|
AZStd::string databaseLocation;
|
|
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Broadcast(&AzToolsFramework::AssetDatabase::AssetDatabaseRequests::GetAssetDatabaseLocation, databaseLocation);
|
|
|
|
if (!databaseLocation.empty())
|
|
{
|
|
m_db = AZStd::make_unique<AssetProcessor::AssetDatabaseConnection>();
|
|
m_db->OpenDatabase();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AssetCatalog::AsyncAssetCatalogStatusRequest()
|
|
{
|
|
if (m_catalogIsDirty)
|
|
{
|
|
Q_EMIT AsyncAssetCatalogStatusResponse(AssetCatalogStatus::RequiresSaving);
|
|
}
|
|
else
|
|
{
|
|
Q_EMIT AsyncAssetCatalogStatusResponse(AssetCatalogStatus::UpToDate);
|
|
}
|
|
}
|
|
|
|
}//namespace AssetProcessor
|
|
|
|
|