浏览代码

Merge pull request #12637 from aws-lumberyard-dev/AssetProcessor_Prototype_SourceAssetReference

Asset Processor - Use absolute paths internally
amzn-mike 2 年之前
父节点
当前提交
36ace0fff8
共有 52 个文件被更改,包括 1566 次插入824 次删除
  1. 1 1
      AutomatedTesting/test1.lua
  2. 13 12
      Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorAssetSystemAPI.h
  3. 3 2
      Code/Framework/AzToolsFramework/AzToolsFramework/AssetDatabase/AssetDatabaseConnection.cpp
  4. 2 1
      Code/Framework/AzToolsFramework/AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h
  5. 3 0
      Code/Tools/AssetProcessor/assetprocessor_static_files.cmake
  6. 1 0
      Code/Tools/AssetProcessor/assetprocessor_test_files.cmake
  7. 4 4
      Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.cpp
  8. 3 2
      Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.h
  9. 80 116
      Code/Tools/AssetProcessor/native/AssetManager/AssetCatalog.cpp
  10. 10 16
      Code/Tools/AssetProcessor/native/AssetManager/AssetCatalog.h
  11. 135 0
      Code/Tools/AssetProcessor/native/AssetManager/SourceAssetReference.cpp
  12. 89 0
      Code/Tools/AssetProcessor/native/AssetManager/SourceAssetReference.h
  13. 209 197
      Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.cpp
  14. 17 27
      Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.h
  15. 14 20
      Code/Tools/AssetProcessor/native/assetprocessor.h
  16. 38 17
      Code/Tools/AssetProcessor/native/resourcecompiler/JobsModel.cpp
  17. 3 3
      Code/Tools/AssetProcessor/native/resourcecompiler/JobsModel.h
  18. 11 9
      Code/Tools/AssetProcessor/native/resourcecompiler/RCCommon.cpp
  19. 5 7
      Code/Tools/AssetProcessor/native/resourcecompiler/RCCommon.h
  20. 12 5
      Code/Tools/AssetProcessor/native/resourcecompiler/RCQueueSortModel.cpp
  21. 36 18
      Code/Tools/AssetProcessor/native/resourcecompiler/rccontroller.cpp
  22. 3 3
      Code/Tools/AssetProcessor/native/resourcecompiler/rccontroller.h
  23. 7 7
      Code/Tools/AssetProcessor/native/resourcecompiler/rcjob.cpp
  24. 72 32
      Code/Tools/AssetProcessor/native/resourcecompiler/rcjoblistmodel.cpp
  25. 1 1
      Code/Tools/AssetProcessor/native/resourcecompiler/rcjoblistmodel.h
  26. 37 5
      Code/Tools/AssetProcessor/native/tests/AssetCatalog/AssetCatalogUnitTests.cpp
  27. 9 2
      Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.cpp
  28. 8 0
      Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h
  29. 191 0
      Code/Tools/AssetProcessor/native/tests/SourceAssetReferenceTests.cpp
  30. 30 0
      Code/Tools/AssetProcessor/native/tests/UnitTestUtilities.h
  31. 0 3
      Code/Tools/AssetProcessor/native/tests/assetmanager/AssetManagerTestingBase.h
  32. 69 48
      Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp
  33. 136 19
      Code/Tools/AssetProcessor/native/tests/assetmanager/IntermediateAssetTests.cpp
  34. 50 63
      Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.cpp
  35. 2 3
      Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.h
  36. 10 13
      Code/Tools/AssetProcessor/native/tests/resourcecompiler/RCControllerTest.cpp
  37. 2 0
      Code/Tools/AssetProcessor/native/tests/resourcecompiler/RCControllerTest.h
  38. 29 24
      Code/Tools/AssetProcessor/native/tests/utilities/JobModelTest.cpp
  39. 3 1
      Code/Tools/AssetProcessor/native/tests/utilities/JobModelTest.h
  40. 16 14
      Code/Tools/AssetProcessor/native/tests/utilities/assetUtilsTest.cpp
  41. 1 1
      Code/Tools/AssetProcessor/native/ui/BuilderData.cpp
  42. 4 18
      Code/Tools/AssetProcessor/native/ui/MainWindow.cpp
  43. 6 2
      Code/Tools/AssetProcessor/native/unittests/AssetProcessingStateDataUnitTests.cpp
  44. 60 43
      Code/Tools/AssetProcessor/native/unittests/AssetProcessorManagerUnitTests.cpp
  45. 38 34
      Code/Tools/AssetProcessor/native/unittests/RCcontrollerUnitTests.cpp
  46. 2 2
      Code/Tools/AssetProcessor/native/utilities/ApplicationManagerBase.cpp
  47. 6 6
      Code/Tools/AssetProcessor/native/utilities/AssetServerHandler.cpp
  48. 33 0
      Code/Tools/AssetProcessor/native/utilities/IPathConversion.h
  49. 15 2
      Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp
  50. 7 1
      Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.h
  51. 26 17
      Code/Tools/AssetProcessor/native/utilities/assetUtils.cpp
  52. 4 3
      Code/Tools/AssetProcessor/native/utilities/assetUtils.h

+ 1 - 1
AutomatedTesting/test1.lua

@@ -51,4 +51,4 @@ function SpawnerScriptSample:OnDeactivate()
     end
 end
 
-return SpawnerScriptSample
+return SpawnerScriptSample

+ 13 - 12
Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorAssetSystemAPI.h

@@ -41,13 +41,13 @@ namespace AzToolsFramework
 
             // don't lock this bus during dispatch - its mainly just a forwarder of socket-based network requests
             // so when one thread is asking for status of an asset, its okay for another thread to do the same.
-            static const bool LocklessDispatch = true; 
+            static const bool LocklessDispatch = true;
 
             virtual ~AssetSystemRequest() = default;
 
             //! Retrieve the absolute path for the Asset Database Location
             virtual bool GetAbsoluteAssetDatabaseLocation(AZStd::string& /*result*/) { return false; }
-        
+
             /// Convert a full source path like "c:\\dev\\gamename\\blah\\test.tga" into a relative product path.
             /// asset paths never mention their alias and are relative to the asset cache root
             virtual bool GetRelativeProductPathFromFullSourceOrProductPath(const AZStd::string& fullPath, AZStd::string& relativeProductPath) = 0;
@@ -80,7 +80,7 @@ namespace AzToolsFramework
             * returns false if it cannot find the source, true otherwise.
             */
             virtual bool GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder) = 0;
-            
+
             /**
             * Given a UUID of a source file, retrieve its actual watch folder path and other details.
             * @param sourceUUID is the UUID of a source file - If you have an AssetID, its the m_guid member of that assetId
@@ -128,7 +128,7 @@ namespace AzToolsFramework
             */
             virtual bool GetAssetsProducedBySourceUUID(const AZ::Uuid& sourceUuid, AZStd::vector<AZ::Data::AssetInfo>& productsAssetInfo) = 0;
         };
-        
+
 
         //! AssetSystemBusTraits
         //! This bus is for events that concern individual assets and is addressed by file extension
@@ -159,7 +159,7 @@ namespace AzToolsFramework
             Queued,  // its in the queue and will be built shortly
             InProgress,  // its being compiled right now.
             Failed,
-            Failed_InvalidSourceNameExceedsMaxLimit, // use this enum to indicate that the job failed because the source file name length exceeds the maximum length allowed 
+            Failed_InvalidSourceNameExceedsMaxLimit, // use this enum to indicate that the job failed because the source file name length exceeds the maximum length allowed
             Completed, // built successfully (no failure occurred)
             Missing //indicate that the job is not present for example if the source file is not there, or if job key is not there
         };
@@ -191,7 +191,8 @@ namespace AzToolsFramework
 
             AZ::u32 GetHash() const
             {
-                AZ::Crc32 crc(m_sourceFile.c_str());
+                AZ::Crc32 crc(m_watchFolder.c_str());
+                crc.Add(m_sourceFile.c_str());
                 crc.Add(m_platform.c_str());
                 crc.Add(m_jobKey.c_str());
                 crc.Add(m_builderGuid.ToString<AZStd::string>().c_str());
@@ -242,7 +243,7 @@ namespace AzToolsFramework
             //! the same platform, the same builder UUID (since its the UUID of the builder itself)
             //! but would have different job keys.
             AZStd::string m_jobKey;
-            
+
             //random int made to identify this attempt to process this job
             AZ::u64 m_jobRunKey = 0;
 
@@ -262,7 +263,7 @@ namespace AzToolsFramework
             AZ::s64 m_jobID = 0; // this is the actual database row.   Client is unlikely to need this.
         };
 
-        typedef AZStd::vector<JobInfo> JobInfoContainer; 
+        typedef AZStd::vector<JobInfo> JobInfoContainer;
 
         //! This Ebus will be used to retrieve all the job related information from AP
         class AssetSystemJobRequest
@@ -275,14 +276,14 @@ namespace AzToolsFramework
 
             virtual ~AssetSystemJobRequest() = default;
 
-            /// Retrieve Jobs information for the given source file, setting escalteJobs to true will escalate all queued jobs 
+            /// Retrieve Jobs information for the given source file, setting escalteJobs to true will escalate all queued jobs
             virtual AZ::Outcome<JobInfoContainer> GetAssetJobsInfo(const AZStd::string& sourcePath, const bool escalateJobs) = 0;
 
-            /// Retrieve Jobs information for the given assetId, setting escalteJobs to true will escalate all queued jobs 
-            /// you can also specify whether fencing is required  
+            /// Retrieve Jobs information for the given assetId, setting escalteJobs to true will escalate all queued jobs
+            /// you can also specify whether fencing is required
             virtual AZ::Outcome<JobInfoContainer> GetAssetJobsInfoByAssetID(const AZ::Data::AssetId& assetId, const bool escalateJobs, bool requireFencing) = 0;
 
-            /// Retrieve Jobs information for the given jobKey 
+            /// Retrieve Jobs information for the given jobKey
             virtual AZ::Outcome<JobInfoContainer> GetAssetJobsInfoByJobKey(const AZStd::string& jobKey, const bool escalateJobs) = 0;
 
             /// Retrieve Job Status for the given jobKey.

+ 3 - 2
Code/Framework/AzToolsFramework/AzToolsFramework/AssetDatabase/AssetDatabaseConnection.cpp

@@ -2501,12 +2501,13 @@ namespace AzToolsFramework
             return found && succeeded;
         }
 
-        bool AssetDatabaseConnection::QueryJobInfoBySourceName(const char* sourceName, jobInfoHandler handler, AZ::Uuid builderGuid, const char* jobKey, const char* platform, AssetSystem::JobStatus status)
+        bool AssetDatabaseConnection::QueryJobInfoBySourceNameScanFolderId(const char* sourceName, AZ::s64 scanfolderId, jobInfoHandler handler, AZ::Uuid builderGuid, const char* jobKey, const char* platform, AssetSystem::JobStatus status)
         {
             SourceDatabaseEntry source;
 
             bool found = false;
-            bool succeeded = QuerySourceBySourceName(sourceName,
+            bool succeeded = QuerySourceBySourceNameScanFolderID(
+                sourceName, scanfolderId,
                     [&](SourceDatabaseEntry& entry)
                     {
                         found = true;

+ 2 - 1
Code/Framework/AzToolsFramework/AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h

@@ -607,7 +607,8 @@ namespace AzToolsFramework
             bool QueryJobInfoByJobID(AZ::s64 jobID, jobInfoHandler handler);
             bool QueryJobInfoByJobRunKey(AZ::u64 jobRunKey, jobInfoHandler handler);
             bool QueryJobInfoByJobKey(AZStd::string jobKey, jobInfoHandler handler);
-            bool QueryJobInfoBySourceName(const char* sourceName, jobInfoHandler handler, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), const char* jobKey = nullptr, const char* platform = nullptr, AssetSystem::JobStatus status = AssetSystem::JobStatus::Any);
+            bool QueryJobInfoBySourceNameScanFolderId(const char* sourceName, AZ::s64 scanfolderId, jobInfoHandler handler, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), const char* jobKey = nullptr, const char* platform = nullptr, AssetSystem::JobStatus status = AssetSystem::JobStatus::Any);
+
 
             //SourceDependency
             /// direct query - look up table row by row ID

+ 3 - 0
Code/Tools/AssetProcessor/assetprocessor_static_files.cmake

@@ -36,6 +36,8 @@ set(FILES
     native/AssetManager/ExcludedFolderCache.cpp
     native/AssetManager/ExcludedFolderCache.h
     native/AssetManager/ExcludedFolderCacheInterface.h
+    native/AssetManager/SourceAssetReference.h
+    native/AssetManager/SourceAssetReference.cpp
     native/assetprocessor.h
     native/connection/connection.cpp
     native/connection/connection.h
@@ -108,6 +110,7 @@ set(FILES
     native/utilities/SpecializedDependencyScanner.h
     native/utilities/ThreadHelper.cpp
     native/utilities/ThreadHelper.h
+    native/utilities/IPathConversion.h
 )
 
 set(SKIP_UNITY_BUILD_INCLUSION_FILES

+ 1 - 0
Code/Tools/AssetProcessor/assetprocessor_test_files.cmake

@@ -69,6 +69,7 @@ set(FILES
     native/tests/BuilderManagerTests.cpp
     native/tests/BuilderManagerTests.h
     native/tests/MockAssetDatabaseRequestsHandler.h
+    native/tests/SourceAssetReferenceTests.cpp
     native/unittests/AssetCacheServerUnitTests.cpp
     native/unittests/AssetProcessingStateDataUnitTests.cpp
     native/unittests/AssetProcessorUnitTests.h

+ 4 - 4
Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.cpp

@@ -1977,10 +1977,10 @@ namespace AssetProcessor
         return found && succeeded;
     }
 
-    bool AssetDatabaseConnection::GetJobsBySourceName(QString exactSourceName, JobDatabaseEntryContainer& container, AZ::Uuid builderGuid, QString jobKey, QString platform, JobStatus status)
+    bool AssetDatabaseConnection::GetJobsBySourceName(const SourceAssetReference& sourceAsset, JobDatabaseEntryContainer& container, AZ::Uuid builderGuid, QString jobKey, QString platform, JobStatus status)
     {
         bool found = false;
-        bool succeeded = QuerySourceBySourceName(exactSourceName.toUtf8().constData(),
+        bool succeeded = QuerySourceBySourceNameScanFolderID(sourceAsset.RelativePath().c_str(), sourceAsset.ScanFolderId(),
             [&](SourceDatabaseEntry& source)
         {
             succeeded = QueryJobBySourceID(source.m_sourceID,
@@ -2599,10 +2599,10 @@ namespace AssetProcessor
         return found && succeeded;
     }
 
-    bool AssetDatabaseConnection::GetJobInfoBySourceName(QString exactSourceName, JobInfoContainer& container, AZ::Uuid builderGuid, QString jobKey, QString platform, JobStatus status)
+    bool AssetDatabaseConnection::GetJobInfoBySourceNameScanFolderId(QString exactSourceName, AZ::s64 scanfolderId, JobInfoContainer& container, AZ::Uuid builderGuid, QString jobKey, QString platform, JobStatus status)
     {
         bool found = false;
-        bool succeeded = QueryJobInfoBySourceName(exactSourceName.toUtf8().constData(),
+        bool succeeded = QueryJobInfoBySourceNameScanFolderId(exactSourceName.toUtf8().constData(), scanfolderId,
             [&](JobInfo& jobInfo)
         {
             found = true;

+ 3 - 2
Code/Tools/AssetProcessor/native/AssetDatabase/AssetDatabase.h

@@ -18,6 +18,7 @@
 #include <QtCore/QString>
 
 #include "AzToolsFramework/API/EditorAssetSystemAPI.h"
+#include <native/AssetManager/SourceAssetReference.h>
 
 class QStringList;
 
@@ -111,7 +112,7 @@ namespace AssetProcessor
         bool GetJobByProductID(AZ::s64 productID, AzToolsFramework::AssetDatabase::JobDatabaseEntry& entry);
 
         bool GetJobsBySourceID(AZ::s64 sourceID, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
-        bool GetJobsBySourceName(QString exactSourceName, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
+        bool GetJobsBySourceName(const AssetProcessor::SourceAssetReference& sourceAsset, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
         bool GetJobsLikeSourceName(QString likeSourceName, LikeType likeType, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
 
         bool GetJobsByProductName(QString exactProductName, AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
@@ -148,7 +149,7 @@ namespace AssetProcessor
         bool GetJobInfoByJobID(AZ::s64 jobID, AzToolsFramework::AssetSystem::JobInfo& jobInfo);
         bool GetJobInfoByJobKey(AZStd::string jobKey, AzToolsFramework::AssetSystem::JobInfoContainer& container);
         bool GetJobInfoByJobRunKey(AZ::u64 jobRunKey, AzToolsFramework::AssetSystem::JobInfoContainer& container);
-        bool GetJobInfoBySourceName(QString exactSourceName, AzToolsFramework::AssetSystem::JobInfoContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
+        bool GetJobInfoBySourceNameScanFolderId(QString exactSourceName, AZ::s64 scanfolderId, AzToolsFramework::AssetSystem::JobInfoContainer& container, AZ::Uuid builderGuid = AZ::Uuid::CreateNull(), QString jobKey = QString(), QString platform = QString(), AzToolsFramework::AssetSystem::JobStatus status = AzToolsFramework::AssetSystem::JobStatus::Any);
 
         /* --------------------- Source Dependency Table -------------------
         *    For example, this table records when a source file depends on another source file either directly (DEP_SourceToSource)

+ 80 - 116
Code/Tools/AssetProcessor/native/AssetManager/AssetCatalog.cpp

@@ -637,36 +637,30 @@ namespace AssetProcessor
         }
     }
 
-    void AssetCatalog::OnSourceQueued(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid, QString rootPath, QString relativeFilePath)
+    void AssetCatalog::OnSourceQueued(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid, const SourceAssetReference& sourceAsset)
     {
         AZStd::lock_guard<AZStd::mutex> lock(m_sourceUUIDToSourceNameMapMutex);
 
-        SourceInfo sourceInfo = { rootPath, relativeFilePath };
-        m_sourceUUIDToSourceNameMap.insert({ sourceUuid, sourceInfo });
+        m_sourceUUIDToSourceAssetMap.insert({ sourceUuid, sourceAsset });
 
         //adding legacy source uuid as well
-        m_sourceUUIDToSourceNameMap.insert({ legacyUuid, sourceInfo });
+        m_sourceUUIDToSourceAssetMap.insert({ legacyUuid, sourceAsset });
 
-        AZStd::string nameForMap(relativeFilePath.toUtf8().constData());
-        AZStd::to_lower(nameForMap.begin(), nameForMap.end());
-
-        m_sourceNameToSourceUUIDMap.insert({ nameForMap, sourceUuid });
+        m_sourceAssetToSourceUUIDMap.insert({ sourceAsset, 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())
+        auto found = m_sourceUUIDToSourceAssetMap.find(sourceUuid);
+        if (found != m_sourceUUIDToSourceAssetMap.end())
         {
-            AZStd::string nameForMap = found->second.m_sourceName.toUtf8().constData();
-            AZStd::to_lower(nameForMap.begin(), nameForMap.end());
-            m_sourceNameToSourceUUIDMap.erase(nameForMap);
+            m_sourceAssetToSourceUUIDMap.erase(found->second);
         }
 
-        m_sourceUUIDToSourceNameMap.erase(sourceUuid);
-        m_sourceUUIDToSourceNameMap.erase(legacyUuid);
+        m_sourceUUIDToSourceAssetMap.erase(sourceUuid);
+        m_sourceUUIDToSourceAssetMap.erase(legacyUuid);
     }
 
     //////////////////////////////////////////////////////////////////////////
@@ -785,7 +779,10 @@ namespace AssetProcessor
         // If the assetType wasn't provided, try to guess it
         if (assetType.IsNull())
         {
-            return GetAssetInfoByIdOnly(assetId, platformName, assetInfo, rootFilePath);
+            SourceAssetReference sourceAsset;
+            bool result = GetAssetInfoByIdOnly(assetId, platformName, assetInfo, sourceAsset);
+            rootFilePath = sourceAsset.ScanFolderPath().c_str();
+            return result;
         }
 
         bool isSourceType;
@@ -798,17 +795,15 @@ namespace AssetProcessor
         // If the assetType is registered as a source type, look up the source info
         if (isSourceType)
         {
-            AZStd::string relativePath;
+            SourceAssetReference sourceAsset;
 
-            if (GetSourceFileInfoFromAssetId(assetId, rootFilePath, relativePath))
+            if (GetSourceFileInfoFromAssetId(assetId, sourceAsset))
             {
-                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());
+                assetInfo.m_relativePath = sourceAsset.RelativePath().c_str();
+                assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(sourceAsset.AbsolutePath().c_str());
+                rootFilePath = sourceAsset.ScanFolderPath().c_str();
 
                 return true;
             }
@@ -1002,20 +997,24 @@ namespace AssetProcessor
 
     bool AssetCatalog::GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder)
     {
-        if (!sourcePath)
+        if (!sourcePath || strlen(sourcePath) <= 0)
         {
             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;
+        SourceAssetReference sourceAsset;
+
         if (!AzFramework::StringFunc::Path::IsRelative(sourcePath))
         {
-            // absolute paths just get converted directly.
-            m_platformConfig->ConvertToRelativePath(QString::fromUtf8(sourcePath), databaseName, scanFolder);
+            QString scanFolder;
+            QString relPath;
+
+            // Call ConvertToRelativePath first to verify the sourcePath exists in a scanfolder
+            if (m_platformConfig->ConvertToRelativePath(sourcePath, relPath, scanFolder))
+            {
+                sourceAsset = SourceAssetReference(scanFolder, relPath);
+            }
         }
         else
         {
@@ -1023,11 +1022,11 @@ namespace AssetProcessor
             QString absolutePath = m_platformConfig->FindFirstMatchingFile(QString::fromUtf8(sourcePath));
             if (!absolutePath.isEmpty())
             {
-                m_platformConfig->ConvertToRelativePath(absolutePath, databaseName, scanFolder);
+                sourceAsset = SourceAssetReference(absolutePath);
             }
         }
 
-        if ((databaseName.isEmpty()) || (scanFolder.isEmpty()))
+        if (!sourceAsset)
         {
             assetInfo.m_assetId.SetInvalid();
             return false;
@@ -1042,7 +1041,7 @@ namespace AssetProcessor
             AZStd::lock_guard<AZStd::mutex> lock(m_databaseMutex);
             AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer returnedSources;
 
-            if (m_db->GetSourcesBySourceName(databaseName, returnedSources))
+            if (m_db->GetSourcesBySourceNameScanFolderId(sourceAsset.RelativePath().c_str(), sourceAsset.ScanFolderId(), returnedSources))
             {
                 if (!returnedSources.empty())
                 {
@@ -1079,30 +1078,31 @@ namespace AssetProcessor
             }
         }
 
+        watchFolder = sourceAsset.ScanFolderPath().c_str();
+
         // Source file isn't in the database yet, see if its in the job queue
-        if (GetQueuedAssetInfoByRelativeSourceName(databaseName.toUtf8().data(), assetInfo, watchFolder))
+        if (GetQueuedAssetInfoByRelativeSourceName(sourceAsset, assetInfo))
         {
             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);
+        return GetUncachedSourceInfoFromDatabaseNameAndWatchFolder(sourceAsset, assetInfo);
     }
 
     bool AssetCatalog::GetSourceInfoBySourceUUID(const AZ::Uuid& sourceUuid, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder)
     {
         AZ::Data::AssetId partialId(sourceUuid, 0);
-        AZStd::string relativePath;
+        SourceAssetReference sourceAsset;
 
-        if (GetSourceFileInfoFromAssetId(partialId, watchFolder, relativePath))
+        if (GetSourceFileInfoFromAssetId(partialId, sourceAsset))
         {
-            AZStd::string sourceFileFullPath;
-            AzFramework::StringFunc::Path::Join(watchFolder.c_str(), relativePath.c_str(), sourceFileFullPath);
+            watchFolder = sourceAsset.ScanFolderPath().c_str();
+
             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());
+            assetInfo.m_relativePath = sourceAsset.RelativePath().c_str();
+            assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(sourceAsset.AbsolutePath().c_str());
 
             // if the type has registered with a typeid, then supply it here
             AZStd::lock_guard<AZStd::mutex> lock(m_sourceAssetTypesMutex);
@@ -1111,7 +1111,7 @@ namespace AssetProcessor
             // 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))
+                if (AZStd::wildcard_match(pair.first, sourceAsset.RelativePath().c_str()))
                 {
                     assetInfo.m_assetType = pair.second;
                     break;
@@ -1460,7 +1460,7 @@ namespace AssetProcessor
 
     //////////////////////////////////////////////////////////////////////////
 
-    bool AssetCatalog::GetSourceFileInfoFromAssetId(const AZ::Data::AssetId &assetId, AZStd::string& watchFolder, AZStd::string& relativePath)
+    bool AssetCatalog::GetSourceFileInfoFromAssetId(const AZ::Data::AssetId &assetId, SourceAssetReference& sourceAsset)
     {
         // Check the database first
         {
@@ -1472,10 +1472,7 @@ namespace AssetProcessor
                 AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanEntry;
                 if (m_db->GetScanFolderByScanFolderID(entry.m_scanFolderPK, scanEntry))
                 {
-                    relativePath = entry.m_sourceName;
-
-                    watchFolder = scanEntry.m_scanFolder;
-
+                    sourceAsset = SourceAssetReference(scanEntry.m_scanFolder.c_str(), entry.m_sourceName.c_str());
 
                     return true;
                 }
@@ -1483,7 +1480,7 @@ namespace AssetProcessor
         }
 
         // Source file isn't in the database yet, see if its in the job queue
-        return GetQueuedAssetInfoById(assetId.m_guid, watchFolder, relativePath);
+        return GetQueuedAssetInfoById(assetId.m_guid, sourceAsset);
     }
 
     AZ::Data::AssetInfo AssetCatalog::GetProductAssetInfo(const char* platformName, const AZ::Data::AssetId& assetId)
@@ -1536,11 +1533,9 @@ namespace AssetProcessor
         return AssetInfo(); // not found!
     }
 
-    bool AssetCatalog::GetAssetInfoByIdOnly(const AZ::Data::AssetId& id, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, AZStd::string& rootFilePath)
+    bool AssetCatalog::GetAssetInfoByIdOnly(const AZ::Data::AssetId& id, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, SourceAssetReference& sourceAsset)
     {
-        AZStd::string relativePath;
-
-        if (GetSourceFileInfoFromAssetId(id, rootFilePath, relativePath))
+        if (GetSourceFileInfoFromAssetId(id, sourceAsset))
         {
             {
                 AZStd::lock_guard<AZStd::mutex> lock(m_sourceAssetTypesMutex);
@@ -1548,15 +1543,12 @@ namespace AssetProcessor
                 // 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))
+                    if (AZStd::wildcard_match(pair.first, sourceAsset.AbsolutePath().c_str()))
                     {
-                        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());
+                        assetInfo.m_relativePath = sourceAsset.RelativePath().c_str();
+                        assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(sourceAsset.AbsolutePath().c_str());
 
                         return true;
                     }
@@ -1564,7 +1556,7 @@ namespace AssetProcessor
             }
 
             // If we get to here, we're going to assume it's a product type
-            rootFilePath.clear();
+            sourceAsset = {};
             assetInfo = GetProductAssetInfo(platformName.c_str(), id);
 
             return !assetInfo.m_relativePath.empty();
@@ -1574,19 +1566,16 @@ namespace AssetProcessor
         return false;
     }
 
-    bool AssetCatalog::GetQueuedAssetInfoById(const AZ::Uuid& guid, AZStd::string& watchFolder, AZStd::string& relativePath)
+    bool AssetCatalog::GetQueuedAssetInfoById(const AZ::Uuid& guid, SourceAssetReference& sourceAsset)
     {
         if (!guid.IsNull())
         {
             AZStd::lock_guard<AZStd::mutex> lock(m_sourceUUIDToSourceNameMapMutex);
 
-            auto foundSource = m_sourceUUIDToSourceNameMap.find(guid);
-            if (foundSource != m_sourceUUIDToSourceNameMap.end())
+            auto foundSource = m_sourceUUIDToSourceAssetMap.find(guid);
+            if (foundSource != m_sourceUUIDToSourceAssetMap.end())
             {
-                const SourceInfo& sourceInfo = foundSource->second;
-
-                watchFolder = sourceInfo.m_watchFolder.toStdString().c_str();
-                relativePath = sourceInfo.m_sourceName.toStdString().c_str();
+                sourceAsset = foundSource->second;
 
                 return true;
             }
@@ -1598,85 +1587,60 @@ namespace AssetProcessor
     }
 
 
-    bool AssetCatalog::GetQueuedAssetInfoByRelativeSourceName(const char* sourceName, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder)
+    bool AssetCatalog::GetQueuedAssetInfoByRelativeSourceName(const SourceAssetReference& sourceAsset, AZ::Data::AssetInfo& assetInfo)
     {
-        if (sourceName)
+        if (sourceAsset)
         {
-            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 foundSourceUUID = m_sourceAssetToSourceUUIDMap.find(sourceAsset);
+            if (foundSourceUUID != m_sourceAssetToSourceUUIDMap.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();
+                assetInfo.m_relativePath = sourceAsset.RelativePath().c_str();
+                assetInfo.m_assetId = foundSourceUUID->second;
+                assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(sourceAsset.AbsolutePath().c_str());
+                assetInfo.m_assetType = AZ::Uuid::CreateNull(); // most source files don't have a type!
 
-                    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)
+                // 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))
                     {
-                        if (AZStd::wildcard_match(pair.first, assetInfo.m_relativePath))
-                        {
-                            assetInfo.m_assetType = pair.second;
-                            break;
-                        }
+                        assetInfo.m_assetType = pair.second;
+                        break;
                     }
-
-                    return true;
                 }
+
+                return true;
             }
         }
+
         assetInfo.m_assetId.SetInvalid();
 
         return false;
     }
 
-    bool AssetCatalog::GetUncachedSourceInfoFromDatabaseNameAndWatchFolder(const char* sourceDatabaseName, const char* watchFolder, AZ::Data::AssetInfo& assetInfo)
+    bool AssetCatalog::GetUncachedSourceInfoFromDatabaseNameAndWatchFolder(const SourceAssetReference& sourceAsset, AZ::Data::AssetInfo& assetInfo)
     {
-        AZ::Uuid sourceUUID = AssetUtilities::CreateSafeSourceUUIDFromName(sourceDatabaseName);
+        AZ::Uuid sourceUUID = AssetUtilities::CreateSafeSourceUUIDFromName(sourceAsset.RelativePath().c_str());
         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;
+        assetInfo.m_assetId = sourceAssetId;
+        assetInfo.m_relativePath = sourceAsset.RelativePath().c_str();
+        assetInfo.m_sizeBytes = AZ::IO::SystemFile::Length(sourceAsset.AbsolutePath().c_str());
+        assetInfo.m_assetType = AZ::Uuid::CreateNull();
 
-        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()))
+        if (assetInfo.m_sizeBytes == 0 && !AZ::IO::SystemFile::Exists(sourceAsset.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)
         {

+ 10 - 16
Code/Tools/AssetProcessor/native/AssetManager/AssetCatalog.h

@@ -74,7 +74,7 @@ namespace AssetProcessor
         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 OnSourceQueued(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid, const SourceAssetReference& sourceAsset);
         void OnSourceFinished(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid);
         void AsyncAssetCatalogStatusRequest();
 
@@ -136,22 +136,22 @@ namespace AssetProcessor
         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);
+        bool GetSourceFileInfoFromAssetId(const AZ::Data::AssetId &assetId, SourceAssetReference& sourceAsset);
 
         //! 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);
+        bool GetAssetInfoByIdOnly(const AZ::Data::AssetId& id, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, SourceAssetReference& sourceAsset);
 
         //! 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);
+        bool GetQueuedAssetInfoById(const AZ::Uuid& guid, SourceAssetReference& sourceAsset);
 
         //! 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);
+        bool GetQueuedAssetInfoByRelativeSourceName(const SourceAssetReference& sourceAsset, AZ::Data::AssetInfo& assetInfo);
 
         //! 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 GetUncachedSourceInfoFromDatabaseNameAndWatchFolder(const SourceAssetReference& sourceAsset, AZ::Data::AssetInfo& assetInfo);
 
         bool ConnectToDatabase();
 
@@ -183,18 +183,12 @@ namespace AssetProcessor
         //! 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>;
+        using SourceUUIDToSourceAssetMap = AZStd::unordered_map<AZ::Uuid, SourceAssetReference>;
+        using SourceAssetToSourceUuidMap = AZStd::unordered_map<SourceAssetReference, AZ::Uuid>;
 
-        SourceUUIDToSourceNameMap m_sourceUUIDToSourceNameMap; // map of uuids to source file names for assets that are currently in the processing queue
-        SourceNameToSourceUuidMap m_sourceNameToSourceUUIDMap;
+        SourceUUIDToSourceAssetMap m_sourceUUIDToSourceAssetMap; // map of uuids to SourceAssetReferences for assets that are currently in the processing queue
+        SourceAssetToSourceUuidMap m_sourceAssetToSourceUUIDMap; // map of SourceAssetReferences to UUIDs for assets that are currently in the processing queue
 
         QMutex m_registriesMutex;
         QHash<QString, AzFramework::AssetRegistry> m_registries; // per platform.

+ 135 - 0
Code/Tools/AssetProcessor/native/AssetManager/SourceAssetReference.cpp

@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <native/AssetManager/SourceAssetReference.h>
+
+namespace AssetProcessor
+{
+    SourceAssetReference::SourceAssetReference(AZ::IO::PathView absolutePath)
+    {
+        IPathConversion* pathConversion = AZ::Interface<IPathConversion>::Get();
+
+        AZ_Assert(pathConversion, "IPathConversion interface is not available");
+
+        if(absolutePath.empty())
+        {
+            return;
+        }
+
+        QString relativePath, scanFolderPath;
+
+        if(!pathConversion->ConvertToRelativePath(absolutePath.FixedMaxPathStringAsPosix().c_str(), relativePath, scanFolderPath))
+        {
+            return;
+        }
+
+        auto* scanFolderInfo = pathConversion->GetScanFolderForFile(scanFolderPath);
+
+        if(!scanFolderInfo)
+        {
+            return;
+        }
+
+        m_scanFolderPath = scanFolderPath.toUtf8().constData();
+        m_relativePath = relativePath.toUtf8().constData();
+        m_absolutePath = absolutePath;
+        m_scanFolderId = scanFolderInfo->ScanFolderID();
+
+        Normalize();
+    }
+
+    SourceAssetReference::SourceAssetReference(AZ::IO::PathView scanFolderPath, AZ::IO::PathView pathRelativeToScanFolder)
+    {
+        IPathConversion* pathConversion = AZ::Interface<IPathConversion>::Get();
+
+        AZ_Assert(pathConversion, "IPathConversion interface is not available");
+
+        if(scanFolderPath.empty() || pathRelativeToScanFolder.empty())
+        {
+            return;
+        }
+
+        auto* scanFolderInfo = pathConversion->GetScanFolderForFile(scanFolderPath.FixedMaxPathStringAsPosix().c_str());
+
+        if(!scanFolderInfo)
+        {
+            return;
+        }
+
+        m_scanFolderPath = scanFolderPath;
+        m_relativePath = pathRelativeToScanFolder;
+        m_absolutePath = m_scanFolderPath / m_relativePath;
+        m_scanFolderId = scanFolderInfo->ScanFolderID();
+
+        Normalize();
+    }
+
+    bool SourceAssetReference::operator==(const SourceAssetReference& other) const
+    {
+        return m_absolutePath == other.m_absolutePath;
+    }
+
+    bool SourceAssetReference::operator!=(const SourceAssetReference& other) const
+    {
+        return !operator==(other);
+    }
+
+    bool SourceAssetReference::operator<(const SourceAssetReference& other) const
+    {
+        return m_absolutePath < other.m_absolutePath;
+    }
+
+    bool SourceAssetReference::operator>(const SourceAssetReference& other) const
+    {
+        return m_absolutePath > other.m_absolutePath;
+    }
+
+    SourceAssetReference::operator bool() const
+    {
+        return IsValid();
+    }
+
+    bool SourceAssetReference::IsValid() const
+    {
+        return !m_absolutePath.empty();
+    }
+
+    AZ::IO::Path SourceAssetReference::AbsolutePath() const
+    {
+        return m_absolutePath;
+    }
+
+    AZ::IO::Path SourceAssetReference::RelativePath() const
+    {
+        return m_relativePath;
+    }
+
+    AZ::IO::Path SourceAssetReference::ScanFolderPath() const
+    {
+        return m_scanFolderPath;
+    }
+
+    AZ::s64 SourceAssetReference::ScanFolderId() const
+    {
+        return m_scanFolderId;
+    }
+
+    void SourceAssetReference::Normalize()
+    {
+        m_scanFolderPath = m_scanFolderPath.LexicallyNormal().AsPosix();
+        m_relativePath = m_relativePath.LexicallyNormal().AsPosix();
+        m_absolutePath = m_absolutePath.LexicallyNormal().AsPosix();
+    }
+}
+
+AZStd::hash<AssetProcessor::SourceAssetReference>::result_type AZStd::hash<AssetProcessor::SourceAssetReference>::operator()(const argument_type& obj) const
+{
+    size_t h = 0;
+    hash_combine(h, obj.AbsolutePath());
+    return h;
+}

+ 89 - 0
Code/Tools/AssetProcessor/native/AssetManager/SourceAssetReference.h

@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <QString>
+#include <AzCore/Interface/Interface.h>
+#include <AzCore/IO/Path/Path.h>
+#include <utilities/IPathConversion.h>
+#include <native/AssetManager/assetScanFolderInfo.h>
+
+namespace AssetProcessor
+{
+    //! Represents a reference to a single source asset on disk
+    class SourceAssetReference
+    {
+    public:
+        SourceAssetReference() = default;
+
+        explicit SourceAssetReference(const char* absolutePath)
+            : SourceAssetReference(AZ::IO::PathView(absolutePath))
+        {
+        }
+
+        explicit SourceAssetReference(QString absolutePath)
+            : SourceAssetReference(absolutePath.toUtf8().constData())
+        {
+        }
+
+        explicit SourceAssetReference(AZ::IO::PathView absolutePath);
+
+        SourceAssetReference(AZ::s64 scanFolderId, AZ::IO::PathView pathRelativeToScanFolder)
+            : SourceAssetReference(AZ::Interface<IPathConversion>::Get()->GetScanFolderById(scanFolderId)->ScanPath().toUtf8().constData(), pathRelativeToScanFolder)
+        {
+        }
+
+        SourceAssetReference(QString scanFolderPath, QString pathRelativeToScanFolder)
+            : SourceAssetReference(
+                  AZ::IO::PathView(scanFolderPath.toUtf8().constData()), AZ::IO::PathView(pathRelativeToScanFolder.toUtf8().constData()))
+        {
+        }
+
+        SourceAssetReference(const char* scanFolderPath, const char* pathRelativeToScanFolder)
+            : SourceAssetReference(AZ::IO::PathView(scanFolderPath), AZ::IO::PathView(pathRelativeToScanFolder))
+        {
+        }
+
+        SourceAssetReference(AZ::IO::PathView scanFolderPath, AZ::IO::PathView pathRelativeToScanFolder);
+
+        AZ_DEFAULT_COPY_MOVE(SourceAssetReference);
+
+        bool operator==(const SourceAssetReference& other) const;
+        bool operator!=(const SourceAssetReference& other) const;
+        bool operator<(const SourceAssetReference& other) const;
+        bool operator>(const SourceAssetReference& other) const;
+        explicit operator bool() const;
+
+        bool IsValid() const;
+        AZ::IO::Path AbsolutePath() const;
+        AZ::IO::Path RelativePath() const;
+        AZ::IO::Path ScanFolderPath() const;
+        AZ::s64 ScanFolderId() const;
+
+    private:
+        void Normalize();
+
+        AZ::IO::Path m_absolutePath;
+        AZ::IO::Path m_relativePath;
+        AZ::IO::Path m_scanFolderPath;
+        AZ::s64 m_scanFolderId{};
+    };
+}
+
+namespace AZStd
+{
+    template<>
+    struct hash<AssetProcessor::SourceAssetReference>
+    {
+        using argument_type = AssetProcessor::SourceAssetReference;
+        using result_type = size_t;
+
+        result_type operator()(const argument_type& obj) const;
+    };
+}

文件差异内容过多而无法显示
+ 209 - 197
Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.cpp


+ 17 - 27
Code/Tools/AssetProcessor/native/AssetManager/assetProcessorManager.h

@@ -167,8 +167,7 @@ namespace AssetProcessor
         //! Internal structure that will hold all the necessary source info
         struct SourceFileInfo
         {
-            QString m_databasePath; // clarification: this is the database path
-            QString m_pathRelativeToScanFolder;
+            SourceAssetReference m_sourceAssetReference;
             AZ::Uuid m_uuid;
             const ScanFolderInfo* m_scanFolder{ nullptr };
         };
@@ -213,7 +212,7 @@ namespace AssetProcessor
         {
             bool operator<(const JobToProcessEntry& other)
             {
-                return m_sourceFileInfo.m_pathRelativeToScanFolder < other.m_sourceFileInfo.m_pathRelativeToScanFolder;
+                return m_sourceFileInfo.m_sourceAssetReference.RelativePath() < other.m_sourceFileInfo.m_sourceAssetReference.RelativePath();
             }
 
             SourceFileInfo m_sourceFileInfo;
@@ -261,9 +260,9 @@ namespace AssetProcessor
 
         void EscalateJobs(AssetProcessor::JobIdEscalationList jobIdEscalationList);
 
-        void SourceDeleted(QString relSourceFile);
+        void SourceDeleted(SourceAssetReference sourceAsset);
         void SourceFolderDeleted(QString folderPath);
-        void SourceQueued(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid, QString rootPath, QString relativeFilePath);
+        void SourceQueued(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid, SourceAssetReference sourceAssetReference);
         void SourceFinished(AZ::Uuid sourceUuid, AZ::Uuid legacyUuid);
         void JobRemoved(AzToolsFramework::AssetSystem::JobInfo jobInfo);
 
@@ -331,14 +330,14 @@ namespace AssetProcessor
         void CheckMissingJobs(QString relativeSourceFile, const ScanFolderInfo* scanFolder, const AZStd::vector<JobDetails>& jobsThisTime);
         void CheckDeletedProductFile(QString normalizedPath);
         void CheckDeletedSourceFile(
-            QString normalizedPath, QString relativePath, QString databaseSourceFile,
+            const SourceAssetReference& sourceAsset,
             AZStd::chrono::steady_clock::time_point initialProcessTime);
         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 FailTopLevelSourceForIntermediate(AZ::IO::PathView relativePathToIntermediateProduct, AZStd::string_view errorMessage);
+        void FailTopLevelSourceForIntermediate(const SourceAssetReference& intermediateAsset, AZStd::string_view errorMessage);
         void CheckMetaDataRealFiles(QString relativePath);
         bool DeleteProducts(const AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& products);
         void DispatchFileChange();
@@ -367,18 +366,9 @@ namespace AssetProcessor
         void ProcessBuilders(QString normalizedPath, QString relativePathToFile, const ScanFolderInfo* scanFolder, const AssetProcessor::BuilderInfoList& builderInfoList);
         AZStd::vector<AZStd::string> GetExcludedFolders();
 
-        struct SourceInfo
-        {
-            QString m_watchFolder;
-            QString m_sourceRelativeToWatchFolder;
-            QString m_sourceDatabaseName;
-        };
-
         struct SourceInfoWithFingerprints
         {
-            QString m_watchFolder;
-            QString m_sourceRelativeToWatchFolder;
-            QString m_sourceDatabaseName;
+            SourceAssetReference m_sourceAssetReference;
             QString m_analysisFingerprint;
         };
 
@@ -389,21 +379,19 @@ namespace AssetProcessor
                 None,
                 //! Indicates the conflict occurred because of a new intermediate overriding an existing source
                 Intermediate,
-                //! Indicates the conflict occurred because of a new source overriding an existing intermediate
-                Source
             };
 
             ConflictType m_type;
 
-            //! Full path to the file that has caused the conflict.  If ConflictType == Intermediate, this is the path to the source, if ConflictType == Source, this is the intermediate
-            AZ::IO::Path m_conflictingFile;
+            //! The file that has caused the conflict.  If ConflictType == Intermediate, this is the source
+            SourceAssetReference m_conflictingFile;
         };
 
         //! 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);
+        bool SearchSourceInfoBySourceUUID(const AZ::Uuid& sourceUuid, SourceAssetReference& result);
 
         //!  Adds the source to the database and returns the corresponding sourceDatabase Entry
-        void AddSourceToDatabase(AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceDatabaseEntry, const ScanFolderInfo* scanFolder, QString relativeSourceFilePath);
+        void AddSourceToDatabase(AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceDatabaseEntry, const ScanFolderInfo* scanFolder, const SourceAssetReference& sourceAsset);
 
         // ! Get the engine, project and active gem root directories which could potentially be separate repositories.
         AZStd::vector<AZStd::string> GetPotentialRepositoryRoots();
@@ -457,9 +445,11 @@ namespace AssetProcessor
         bool IsInIntermediateAssetsFolder(AZ::IO::PathView path) const;
         bool IsInIntermediateAssetsFolder(QString path) const;
 
-        ConflictResult CheckIntermediateProductConflict(bool isIntermediateProduct, const char* searchSourcePath);
+        // Checks if an intermediate product conflicts with an existing source asset
+        // searchSourcePath should be the path of the intermediate to use to search for existing sources of the same name
+        ConflictResult CheckIntermediateProductConflict(const char* searchSourcePath);
 
-        bool CheckForIntermediateAssetLoop(AZStd::string_view currentAsset, AZStd::string_view productAsset);
+        bool CheckForIntermediateAssetLoop(const SourceAssetReference& sourceAsset, const SourceAssetReference& productAsset);
 
         void UpdateForCacheServer(JobDetails& jobDetails);
 
@@ -499,8 +489,8 @@ namespace AssetProcessor
         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:
+        using SourceUUIDToSourceInfoMap = AZStd::unordered_map<AZ::Uuid, SourceAssetReference>;
+        SourceUUIDToSourceInfoMap m_sourceUUIDToSourceInfoMap; // contains Source Asset UUID -> SourceAssetReference
         AZStd::mutex m_sourceUUIDToSourceInfoMapMutex;
 
         QString m_normalizedCacheRootPath;

+ 14 - 20
Code/Tools/AssetProcessor/native/assetprocessor.h

@@ -28,6 +28,7 @@
 #include <native/AssetManager/assetScanFolderInfo.h>
 #include <AzFramework/StringFunc/StringFunc.h>
 #include "AssetProcessor_Traits_Platform.h"
+#include <AssetManager/SourceAssetReference.h>
 
 namespace AssetProcessor
 {
@@ -117,9 +118,7 @@ namespace AssetProcessor
     public:
         // note that QStrings are ref-counted copy-on-write, so a move operation will not be beneficial unless this struct gains considerable heap allocated fields.
 
-        QString m_databaseSourceName;                           //! DATABASE "SourceName" Column, which includes the 'output prefix' if present, used for keying
-        QString m_watchFolderPath;                              //! contains the absolute path to the watch folder that the file was found in.
-        QString m_pathRelativeToWatchFolder;                    //! contains the relative path (from the above watch folder) that the file was found in.
+        SourceAssetReference m_sourceAssetReference;
         AZ::Uuid m_builderGuid = AZ::Uuid::CreateNull();        //! the builder that will perform the job
         AssetBuilderSDK::PlatformInfo m_platformInfo;
         AZ::Uuid m_sourceFileUUID = AZ::Uuid::CreateNull(); ///< The actual UUID of the source being processed
@@ -132,16 +131,13 @@ namespace AssetProcessor
 
         QString GetAbsoluteSourcePath() const
         {
-            if (!m_watchFolderPath.isEmpty())
-            {
-                return m_watchFolderPath + "/" + m_pathRelativeToWatchFolder;
-            }
-            return m_pathRelativeToWatchFolder;
+            return m_sourceAssetReference.AbsolutePath().c_str();
         }
 
         AZ::u32 GetHash() const
         {
-            AZ::Crc32 crc(m_databaseSourceName.toUtf8().constData());
+            AZ::Crc32 crc(m_sourceAssetReference.ScanFolderPath().c_str());
+            crc.Add(m_sourceAssetReference.RelativePath().c_str());
             crc.Add(m_platformInfo.m_identifier.c_str());
             crc.Add(m_jobKey.toUtf8().constData());
             crc.Add(m_builderGuid.ToString<AZStd::string>().c_str());
@@ -149,10 +145,8 @@ namespace AssetProcessor
         }
 
         JobEntry() = default;
-        JobEntry(QString watchFolderPath, QString relativePathToFile, QString databaseSourceName, const AZ::Uuid& builderGuid, const AssetBuilderSDK::PlatformInfo& platformInfo, QString jobKey, AZ::u32 computedFingerprint, AZ::u64 jobRunKey, const AZ::Uuid &sourceUuid, bool addToDatabase = true)
-            : m_watchFolderPath(watchFolderPath)
-            , m_pathRelativeToWatchFolder(relativePathToFile)
-            , m_databaseSourceName(databaseSourceName)
+        JobEntry(SourceAssetReference sourceAssetReference, const AZ::Uuid& builderGuid, const AssetBuilderSDK::PlatformInfo& platformInfo, QString jobKey, AZ::u32 computedFingerprint, AZ::u64 jobRunKey, const AZ::Uuid &sourceUuid, bool addToDatabase = true)
+            : m_sourceAssetReference(AZStd::move(sourceAssetReference))
             , m_builderGuid(builderGuid)
             , m_platformInfo(platformInfo)
             , m_jobKey(jobKey)
@@ -238,12 +232,12 @@ namespace AssetProcessor
 
         AZStd::string ToString() const
         {
-            return QString("%1 %2 %3").arg(m_jobEntry.m_databaseSourceName, m_jobEntry.m_platformInfo.m_identifier.c_str(), m_jobEntry.m_jobKey).toUtf8().data();
+            return QString("%1 %2 %3").arg(m_jobEntry.GetAbsoluteSourcePath(), m_jobEntry.m_platformInfo.m_identifier.c_str(), m_jobEntry.m_jobKey).toUtf8().data();
         }
 
         bool operator==(const JobDetails& rhs) const
         {
-            return ((m_jobEntry.m_databaseSourceName == rhs.m_jobEntry.m_databaseSourceName) &&
+            return ((m_jobEntry.GetAbsoluteSourcePath() == rhs.m_jobEntry.GetAbsoluteSourcePath()) &&
                 (m_jobEntry.m_platformInfo.m_identifier == rhs.m_jobEntry.m_platformInfo.m_identifier) &&
                 (m_jobEntry.m_jobKey == rhs.m_jobEntry.m_jobKey) &&
                 m_jobEntry.m_builderGuid == rhs.m_jobEntry.m_builderGuid);
@@ -256,19 +250,19 @@ namespace AssetProcessor
     //! because of job dependency declared on them by other jobs
     struct JobDesc
     {
-        AZStd::string m_databaseSourceName;
+        SourceAssetReference m_sourceAsset;
         AZStd::string m_jobKey;
         AZStd::string m_platformIdentifier;
 
         bool operator==(const JobDesc& rhs) const
         {
-            return AzFramework::StringFunc::Equal(m_databaseSourceName.c_str(), rhs.m_databaseSourceName.c_str())
+            return m_sourceAsset == rhs.m_sourceAsset
                 && m_platformIdentifier == rhs.m_platformIdentifier
                 && m_jobKey == rhs.m_jobKey;
         }
 
-        JobDesc(const AZStd::string& databaseSourceName, const AZStd::string& jobKey, const AZStd::string& platformIdentifier)
-            : m_databaseSourceName(databaseSourceName)
+        JobDesc(SourceAssetReference sourceAsset, const AZStd::string& jobKey, const AZStd::string& platformIdentifier)
+            : m_sourceAsset(AZStd::move(sourceAsset))
             , m_jobKey(jobKey)
             , m_platformIdentifier(platformIdentifier)
         {
@@ -276,7 +270,7 @@ namespace AssetProcessor
 
         AZStd::string ToString() const
         {
-            AZStd::string lowerSourceName = m_databaseSourceName;
+            AZStd::string lowerSourceName = m_sourceAsset.AbsolutePath().Native();
             AZStd::to_lower(lowerSourceName.begin(), lowerSourceName.end());
 
             return AZStd::string::format("%s %s %s", lowerSourceName.c_str(), m_platformIdentifier.c_str(), m_jobKey.c_str());

+ 38 - 17
Code/Tools/AssetProcessor/native/resourcecompiler/JobsModel.cpp

@@ -169,7 +169,7 @@ namespace AssetProcessor
                 return GetStatusInString(jobInfo->m_jobState, jobInfo->m_warningCount, jobInfo->m_errorCount);
             }
             case ColumnSource:
-                return getItem(index.row())->m_elementId.GetInputAssetName();
+                return getItem(index.row())->m_elementId.GetSourceAssetReference().RelativePath().c_str();
             case ColumnPlatform:
                 return getItem(index.row())->m_elementId.GetPlatform();
             case ColumnJobKey:
@@ -209,7 +209,8 @@ namespace AssetProcessor
             JobInfo jobInfo;
             AssetJobLogResponse jobLogResponse;
             auto* cachedJobInfo = getItem(index.row());
-            jobInfo.m_sourceFile = cachedJobInfo->m_elementId.GetInputAssetName().toUtf8().data();
+            jobInfo.m_sourceFile = cachedJobInfo->m_elementId.GetSourceAssetReference().RelativePath().Native();
+            jobInfo.m_watchFolder = cachedJobInfo->m_elementId.GetSourceAssetReference().ScanFolderPath().Native();
             jobInfo.m_platform = cachedJobInfo->m_elementId.GetPlatform().toUtf8().data();
             jobInfo.m_jobKey = cachedJobInfo->m_elementId.GetJobDescriptor().toUtf8().data();
             jobInfo.m_builderGuid = cachedJobInfo->m_builderGuid;
@@ -258,7 +259,8 @@ namespace AssetProcessor
         {
             AzToolsFramework::AssetSystem::JobInfo jobInfo;
             CachedJobInfo* cachedJobInfo = getItem(index.row());
-            jobInfo.m_sourceFile = cachedJobInfo->m_elementId.GetInputAssetName().toUtf8().data();
+            jobInfo.m_sourceFile = cachedJobInfo->m_elementId.GetSourceAssetReference().RelativePath().Native();
+            jobInfo.m_watchFolder = cachedJobInfo->m_elementId.GetSourceAssetReference().ScanFolderPath().Native();
             jobInfo.m_platform = cachedJobInfo->m_elementId.GetPlatform().toUtf8().data();
             jobInfo.m_jobKey = cachedJobInfo->m_elementId.GetJobDescriptor().toUtf8().data();
             jobInfo.m_builderGuid = cachedJobInfo->m_builderGuid;
@@ -354,17 +356,18 @@ namespace AssetProcessor
             AZStd::unordered_map<QueueElementID, AZ::s64> historicalStats;
             auto statsFunction = [&historicalStats](AzToolsFramework::AssetDatabase::StatDatabaseEntry entry)
             {
-                static constexpr int numTokensExpected = 5;
+                static constexpr int numTokensExpected = 6;
                 AZStd::vector<AZStd::string> tokens;
                 AZ::StringFunc::Tokenize(entry.m_statName, tokens, ',');
 
                 if (tokens.size() == numTokensExpected)
                 {
                     QueueElementID elementId;
-                    elementId.SetInputAssetName(tokens[1].c_str());
-                    elementId.SetJobDescriptor(tokens[2].c_str());
-                    elementId.SetPlatform(tokens[3].c_str());
+                    elementId.SetSourceAssetReference(SourceAssetReference(tokens[1].c_str(), tokens[2].c_str()));
+                    elementId.SetJobDescriptor(tokens[3].c_str());
+                    elementId.SetPlatform(tokens[4].c_str());
                     historicalStats[elementId] = entry.m_statValue;
+                    AZ_UNUSED(historicalStats);
                 }
                 else
                 {
@@ -388,8 +391,12 @@ namespace AssetProcessor
             {
                 AzToolsFramework::AssetDatabase::SourceDatabaseEntry source;
                 assetDatabaseConnection.GetSourceBySourceID(entry.m_sourcePK, source);
+
+                AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder;
+                assetDatabaseConnection.GetScanFolderByScanFolderID(source.m_scanFolderPK, scanFolder);
+
                 CachedJobInfo* jobInfo = new CachedJobInfo();
-                jobInfo->m_elementId.SetInputAssetName(source.m_sourceName.c_str());
+                jobInfo->m_elementId.SetSourceAssetReference(SourceAssetReference(scanFolder.m_scanFolder.c_str(), source.m_sourceName.c_str()));
                 jobInfo->m_elementId.SetPlatform(entry.m_platform.c_str());
                 jobInfo->m_elementId.SetJobDescriptor(entry.m_jobKey.c_str());
                 jobInfo->m_jobState = entry.m_status;
@@ -416,11 +423,13 @@ namespace AssetProcessor
     QModelIndex JobsModel::GetJobFromProduct(const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& productEntry, AzToolsFramework::AssetDatabase::AssetDatabaseConnection& assetDatabaseConnection)
     {
         AZStd::string sourceForProduct;
+        AZ::s64 scanFolderId;
         assetDatabaseConnection.QuerySourceByProductID(
             productEntry.m_productID,
             [&](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry)
         {
             sourceForProduct = sourceEntry.m_sourceName;
+                scanFolderId = sourceEntry.m_scanFolderPK;
             return false;
         });
 
@@ -429,6 +438,18 @@ namespace AssetProcessor
             return QModelIndex();
         }
 
+        AZStd::string scanFolderPath;
+        assetDatabaseConnection.QueryScanFolderByScanFolderID(scanFolderId, [&](AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& entry)
+        {
+            scanFolderPath = entry.m_scanFolder;
+            return false;
+        });
+
+        if (scanFolderPath.empty())
+        {
+            return QModelIndex();
+        }
+
         AzToolsFramework::AssetDatabase::JobDatabaseEntry foundJobEntry;
         assetDatabaseConnection.QueryJobByProductID(
             productEntry.m_productID,
@@ -442,12 +463,12 @@ namespace AssetProcessor
         {
             return QModelIndex();
         }
-        return GetJobFromSourceAndJobInfo(sourceForProduct, foundJobEntry.m_platform, foundJobEntry.m_jobKey);
+        return GetJobFromSourceAndJobInfo(SourceAssetReference(scanFolderPath.c_str(), sourceForProduct.c_str()), foundJobEntry.m_platform, foundJobEntry.m_jobKey);
     }
 
-    QModelIndex JobsModel::GetJobFromSourceAndJobInfo(const AZStd::string& source, const AZStd::string& platform, const AZStd::string& jobKey)
+    QModelIndex JobsModel::GetJobFromSourceAndJobInfo(const SourceAssetReference& sourceAsset, const AZStd::string& platform, const AZStd::string& jobKey)
     {
-        QueueElementID elementId(source.c_str(), platform.c_str(), jobKey.c_str());
+        QueueElementID elementId(sourceAsset, platform.c_str(), jobKey.c_str());
         auto iter = m_cachedJobsLookup.find(elementId);
         if (iter == m_cachedJobsLookup.end())
         {
@@ -459,7 +480,7 @@ namespace AssetProcessor
 
     void JobsModel::OnJobStatusChanged(JobEntry entry, AzToolsFramework::AssetSystem::JobStatus status)
     {
-        QueueElementID elementId(entry.m_databaseSourceName, entry.m_platformInfo.m_identifier.c_str(), entry.m_jobKey);
+        QueueElementID elementId(entry.m_sourceAssetReference, entry.m_platformInfo.m_identifier.c_str(), entry.m_jobKey);
         CachedJobInfo* jobInfo = nullptr;
         unsigned int jobIndex = 0;
 
@@ -470,7 +491,7 @@ namespace AssetProcessor
         if (iter == m_cachedJobsLookup.end())
         {
             jobInfo = new CachedJobInfo();
-            jobInfo->m_elementId.SetInputAssetName(entry.m_databaseSourceName.toUtf8().data());
+            jobInfo->m_elementId.SetSourceAssetReference(entry.m_sourceAssetReference);
             jobInfo->m_elementId.SetPlatform(entry.m_platformInfo.m_identifier.c_str());
             jobInfo->m_elementId.SetJobDescriptor(entry.m_jobKey.toUtf8().data());
             jobInfo->m_jobRunKey = aznumeric_cast<uint32_t>(entry.m_jobRunKey);
@@ -507,7 +528,7 @@ namespace AssetProcessor
 
     void JobsModel::OnJobProcessDurationChanged(JobEntry jobEntry, int durationMs)
     {
-        QueueElementID elementId(jobEntry.m_databaseSourceName, jobEntry.m_platformInfo.m_identifier.c_str(), jobEntry.m_jobKey);
+        QueueElementID elementId(jobEntry.m_sourceAssetReference, jobEntry.m_platformInfo.m_identifier.c_str(), jobEntry.m_jobKey);
 
         if (auto iter = m_cachedJobsLookup.find(elementId); iter != m_cachedJobsLookup.end())
         {
@@ -519,13 +540,13 @@ namespace AssetProcessor
         }
     }
 
-    void JobsModel::OnSourceRemoved(QString sourceDatabasePath)
+    void JobsModel::OnSourceRemoved(const SourceAssetReference& sourceAsset)
     {
         // when a source is removed, we need to eliminate all job entries for that source regardless of all other details of it.
         QList<AssetProcessor::QueueElementID> elementsToRemove;
         for (int index = 0; index < m_cachedJobs.size(); ++index)
         {
-            if (QString::compare(m_cachedJobs[index]->m_elementId.GetInputAssetName(), sourceDatabasePath, Qt::CaseSensitive) == 0)
+            if (m_cachedJobs[index]->m_elementId.GetSourceAssetReference() == sourceAsset)
             {
                 elementsToRemove.push_back(m_cachedJobs[index]->m_elementId);
             }
@@ -541,7 +562,7 @@ namespace AssetProcessor
 
     void JobsModel::OnJobRemoved(AzToolsFramework::AssetSystem::JobInfo jobInfo)
     {
-        RemoveJob(QueueElementID(jobInfo.m_sourceFile.c_str(), jobInfo.m_platform.c_str(), jobInfo.m_jobKey.c_str()));
+        RemoveJob(QueueElementID(SourceAssetReference(jobInfo.m_watchFolder.c_str(), jobInfo.m_sourceFile.c_str()), jobInfo.m_platform.c_str(), jobInfo.m_jobKey.c_str()));
     }
 
     void JobsModel::RemoveJob(const AssetProcessor::QueueElementID& elementId)

+ 3 - 3
Code/Tools/AssetProcessor/native/resourcecompiler/JobsModel.h

@@ -88,13 +88,13 @@ namespace AssetProcessor
         void PopulateJobsFromDatabase();
 
         QModelIndex GetJobFromProduct(const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& productEntry, AzToolsFramework::AssetDatabase::AssetDatabaseConnection& assetDatabaseConnection);
-        QModelIndex GetJobFromSourceAndJobInfo(const AZStd::string& source, const AZStd::string& platform, const AZStd::string& jobKey);
+        QModelIndex GetJobFromSourceAndJobInfo(const SourceAssetReference& source, const AZStd::string& platform, const AZStd::string& jobKey);
 
 public Q_SLOTS:
         void OnJobStatusChanged(JobEntry entry, AzToolsFramework::AssetSystem::JobStatus status);
         void OnJobProcessDurationChanged(JobEntry jobEntry, int durationMs);
         void OnJobRemoved(AzToolsFramework::AssetSystem::JobInfo jobInfo);
-        void OnSourceRemoved(QString sourceDatabasePath);
+        void OnSourceRemoved(const SourceAssetReference& sourceAsset);
 
     protected:
         QIcon m_pendingIcon;
@@ -104,7 +104,7 @@ public Q_SLOTS:
         QIcon m_okIcon;
         QIcon m_processingIcon;
         AZStd::vector<CachedJobInfo*> m_cachedJobs;
-        QHash<AssetProcessor::QueueElementID, int> m_cachedJobsLookup; // QVector uses int as type of index.  
+        QHash<AssetProcessor::QueueElementID, int> m_cachedJobsLookup; // QVector uses int as type of index.
 
         void RemoveJob(const AssetProcessor::QueueElementID& elementId);
     };

+ 11 - 9
Code/Tools/AssetProcessor/native/resourcecompiler/RCCommon.cpp

@@ -7,20 +7,22 @@
  */
 #include "RCCommon.h"
 #include <QHash>
+#include <AzCore/StringFunc/StringFunc.h>
 
 namespace AssetProcessor
 {
-    QueueElementID::QueueElementID(QString inputAssetName, QString platform, QString jobDescriptor)
-        : m_inputAssetName(inputAssetName)
+    QueueElementID::QueueElementID(SourceAssetReference sourceAssetReference, QString platform, QString jobDescriptor)
+        : m_sourceAssetReference(AZStd::move(sourceAssetReference))
         , m_platform(platform)
         , m_jobDescriptor(jobDescriptor)
     {
     }
 
-    QString QueueElementID::GetInputAssetName() const
+    SourceAssetReference QueueElementID::GetSourceAssetReference() const
     {
-        return m_inputAssetName;
+        return m_sourceAssetReference;
     }
+
     QString QueueElementID::GetPlatform() const
     {
         return m_platform;
@@ -31,9 +33,9 @@ namespace AssetProcessor
         return m_jobDescriptor;
     }
 
-    void QueueElementID::SetInputAssetName(QString inputAssetName)
+    void QueueElementID::SetSourceAssetReference(SourceAssetReference sourceAssetReference)
     {
-        m_inputAssetName = inputAssetName;
+        m_sourceAssetReference = AZStd::move(sourceAssetReference);
     }
 
     void QueueElementID::SetPlatform(QString platform)
@@ -51,7 +53,7 @@ namespace AssetProcessor
         // if this becomes a hotspot in profile, we could use CRCs or other boost to comparison here.  These classes are constructed rarely
         // compared to how commonly they are compared with each other.
         return (
-            (QString::compare(m_inputAssetName, other.m_inputAssetName, Qt::CaseSensitive) == 0) &&
+            m_sourceAssetReference == other.m_sourceAssetReference &&
             (QString::compare(m_platform, other.m_platform, Qt::CaseInsensitive) == 0) &&
             (QString::compare(m_jobDescriptor, other.m_jobDescriptor, Qt::CaseInsensitive) == 0)
             );
@@ -59,7 +61,7 @@ namespace AssetProcessor
 
     bool QueueElementID::operator<(const QueueElementID& other) const
     {
-        int compare = QString::compare(m_inputAssetName, other.m_inputAssetName, Qt::CaseSensitive);
+        int compare = m_sourceAssetReference.AbsolutePath().Compare(other.m_sourceAssetReference.AbsolutePath());
         if (compare != 0)
         {
             return (compare < 0);
@@ -84,7 +86,7 @@ namespace AssetProcessor
 
     uint qHash(const AssetProcessor::QueueElementID& key, uint seed)
     {
-        return qHash(key.GetInputAssetName().toLower() + key.GetPlatform().toLower() + key.GetJobDescriptor().toLower(), seed);
+        return qHash(QString(key.GetSourceAssetReference().AbsolutePath().c_str()).toLower() + key.GetPlatform().toLower() + key.GetJobDescriptor().toLower(), seed);
     }
 } // end namespace AssetProcessor
 

+ 5 - 7
Code/Tools/AssetProcessor/native/resourcecompiler/RCCommon.h

@@ -10,6 +10,7 @@
 
 #include <AzCore/std/functional.h>
 #include <QString>
+#include <AssetManager/SourceAssetReference.h>
 
 namespace AssetProcessor
 {
@@ -18,22 +19,19 @@ namespace AssetProcessor
     {
     public:
         QueueElementID() = default;
-        
-        ///! note that inputAssetName is a database name, not a relative path.
-        QueueElementID(QString inputAssetName, QString platform, QString jobDescriptor);
+        QueueElementID(SourceAssetReference sourceAssetReference, QString platform, QString jobDescriptor);
 
-        
-        QString GetInputAssetName() const; ///< This is the database name, with output prefix.
+        SourceAssetReference GetSourceAssetReference() const;
         QString GetPlatform() const;
         QString GetJobDescriptor() const;
-        void SetInputAssetName(QString inputAssetName);
+        void SetSourceAssetReference(SourceAssetReference sourceAssetReference);
         void SetPlatform(QString platform);
         void SetJobDescriptor(QString jobDescriptor);
         bool operator==(const QueueElementID& other) const;
         bool operator<(const QueueElementID& other) const;
 
     protected:
-        QString m_inputAssetName;
+        SourceAssetReference m_sourceAssetReference;
         QString m_platform;
         QString m_jobDescriptor;
     };

+ 12 - 5
Code/Tools/AssetProcessor/native/resourcecompiler/RCQueueSortModel.cpp

@@ -61,7 +61,14 @@ namespace AssetProcessor
                     if (jobDepedencyInternal.m_jobDependency.m_type == AssetBuilderSDK::JobDependencyType::Order || jobDepedencyInternal.m_jobDependency.m_type == AssetBuilderSDK::JobDependencyType::OrderOnce)
                     {
                         const AssetBuilderSDK::JobDependency& jobDependency = jobDepedencyInternal.m_jobDependency;
-                        QueueElementID elementId(jobDependency.m_sourceFile.m_sourceFileDependencyPath.c_str(), jobDependency.m_platformIdentifier.c_str(), jobDependency.m_jobKey.c_str());
+                        AZ_Assert(
+                            AZ::IO::PathView(jobDependency.m_sourceFile.m_sourceFileDependencyPath).IsAbsolute(),
+                            "Dependency path %s is not an absolute path",
+                            jobDependency.m_sourceFile.m_sourceFileDependencyPath.c_str());
+                        QueueElementID elementId(
+                            SourceAssetReference(jobDependency.m_sourceFile.m_sourceFileDependencyPath.c_str()),
+                            jobDependency.m_platformIdentifier.c_str(),
+                            jobDependency.m_jobKey.c_str());
 
                         if (m_sourceModel->isInFlight(elementId) || m_sourceModel->isInQueue(elementId))
                         {
@@ -90,7 +97,7 @@ namespace AssetProcessor
         if (anyPendingJob && m_sourceModel->jobsInFlight() == 0 && !waitingOnCatalog)
         {
             AZ_Warning(AssetProcessor::DebugChannel, false, " Cyclic job order dependency detected. Processing job (%s, %s, %s, %s) to unblock.",
-                anyPendingJob->GetJobEntry().m_pathRelativeToWatchFolder.toUtf8().data(), anyPendingJob->GetJobKey().toUtf8().data(),
+                anyPendingJob->GetJobEntry().m_sourceAssetReference.AbsolutePath().c_str(), anyPendingJob->GetJobKey().toUtf8().data(),
                 anyPendingJob->GetJobEntry().m_platformInfo.m_identifier.c_str(), anyPendingJob->GetBuilderGuid().ToString<AZStd::string>().c_str());
             return anyPendingJob;
         }
@@ -197,8 +204,8 @@ namespace AssetProcessor
         {
             return priorityLeft > priorityRight;
         }
-        
-        if (leftJob->GetJobEntry().m_databaseSourceName == rightJob->GetJobEntry().m_databaseSourceName)
+
+        if (leftJob->GetJobEntry().m_sourceAssetReference == rightJob->GetJobEntry().m_sourceAssetReference)
         {
             // If there are two jobs for the same source, then sort by job run key.
             return leftJob->GetJobEntry().m_jobRunKey < rightJob->GetJobEntry().m_jobRunKey;
@@ -207,7 +214,7 @@ namespace AssetProcessor
         // if we get all the way down here it means we're dealing with two assets which are not
         // in any compile groups, not a priority platform, not a priority type, priority platform, etc.
         // we can arrange these any way we want, but must pick at least a stable order.
-        return leftJob->GetJobEntry().m_databaseSourceName < rightJob->GetJobEntry().m_databaseSourceName;
+        return leftJob->GetJobEntry().GetAbsoluteSourcePath() < rightJob->GetJobEntry().GetAbsoluteSourcePath();
     }
 
     void RCQueueSortModel::AssetProcessorPlatformConnected(const AZStd::string platform)

+ 36 - 18
Code/Tools/AssetProcessor/native/resourcecompiler/rccontroller.cpp

@@ -38,7 +38,7 @@ namespace AssetProcessor
 
         m_RCQueueSortModel.AttachToModel(&m_RCJobListModel);
 
-        // make sure that the global thread pool has enough slots to accomidate your request though, since 
+        // make sure that the global thread pool has enough slots to accomidate your request though, since
         // by default, the global thread pool has idealThreadCount() slots only.
         // leave an extra slot for non-job work.
         int currentMaxThreadCount = QThreadPool::globalInstance()->maxThreadCount();
@@ -73,13 +73,13 @@ namespace AssetProcessor
         m_RCJobListModel.markAsStarted(rcJob);
         Q_EMIT JobStatusChanged(rcJob->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::InProgress);
         rcJob->Start();
-        Q_EMIT JobStarted(rcJob->GetJobEntry().m_pathRelativeToWatchFolder, QString::fromUtf8(rcJob->GetPlatformInfo().m_identifier.c_str()));
+        Q_EMIT JobStarted(rcJob->GetJobEntry().m_sourceAssetReference.RelativePath().c_str(), QString::fromUtf8(rcJob->GetPlatformInfo().m_identifier.c_str()));
     }
 
     void RCController::QuitRequested()
     {
         m_shuttingDown = true;
-        
+
         // cancel all jobs:
         AssetBuilderSDK::JobCommandBus::Broadcast(&AssetBuilderSDK::JobCommandBus::Events::Cancel);
 
@@ -138,7 +138,7 @@ namespace AssetProcessor
             Q_EMIT FileCompiled(rcJob->GetJobEntry(), AZStd::move(rcJob->GetProcessJobResponse()));
             Q_EMIT JobStatusChanged(rcJob->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
         }
-        
+
         // Move to Completed list which will mark as "completed"
         // unless a different state has been set.
         m_RCJobListModel.markAsCompleted(rcJob);
@@ -168,11 +168,16 @@ namespace AssetProcessor
 
     void RCController::JobSubmitted(JobDetails details)
     {
-        AssetProcessor::QueueElementID checkFile(details.m_jobEntry.m_databaseSourceName, details.m_jobEntry.m_platformInfo.m_identifier.c_str(), details.m_jobEntry.m_jobKey);
+        AssetProcessor::QueueElementID checkFile(details.m_jobEntry.m_sourceAssetReference, details.m_jobEntry.m_platformInfo.m_identifier.c_str(), details.m_jobEntry.m_jobKey);
 
         if (m_RCJobListModel.isInQueue(checkFile))
         {
-            AZ_TracePrintf(AssetProcessor::DebugChannel, "Job is already in queue and has not started yet - ignored [%s, %s, %s]\n", checkFile.GetInputAssetName().toUtf8().data(), checkFile.GetPlatform().toUtf8().data(), checkFile.GetJobDescriptor().toUtf8().data());
+            AZ_TracePrintf(
+                AssetProcessor::DebugChannel,
+                "Job is already in queue and has not started yet - ignored [%s, %s, %s]\n",
+                checkFile.GetSourceAssetReference().AbsolutePath().c_str(),
+                checkFile.GetPlatform().toUtf8().data(),
+                checkFile.GetJobDescriptor().toUtf8().data());
             return;
         }
 
@@ -187,7 +192,14 @@ namespace AssetProcessor
 
                 if (job->GetJobEntry().m_computedFingerprint != details.m_jobEntry.m_computedFingerprint)
                 {
-                    AZ_TracePrintf(AssetProcessor::DebugChannel, "Cancelling Job [%s, %s, %s] with old FP %u, replacing with new FP %u \n", checkFile.GetInputAssetName().toUtf8().data(), checkFile.GetPlatform().toUtf8().data(), checkFile.GetJobDescriptor().toUtf8().data(), job->GetJobEntry().m_computedFingerprint, details.m_jobEntry.m_computedFingerprint);
+                    AZ_TracePrintf(
+                        AssetProcessor::DebugChannel,
+                        "Cancelling Job [%s, %s, %s] with old FP %u, replacing with new FP %u \n",
+                        checkFile.GetSourceAssetReference().AbsolutePath().c_str(),
+                        checkFile.GetPlatform().toUtf8().data(),
+                        checkFile.GetJobDescriptor().toUtf8().data(),
+                        job->GetJobEntry().m_computedFingerprint,
+                        details.m_jobEntry.m_computedFingerprint);
                     cancelJob = true;
                 }
                 else if(!job->GetJobDependencies().empty())
@@ -195,13 +207,19 @@ namespace AssetProcessor
                     // If a job has dependencies, it's very likely it was re-queued as a result of a dependency being changed
                     // The in-flight job is probably going to fail at best, or use old data at worst, so cancel the in-flight job
 
-                    AZ_TracePrintf(AssetProcessor::DebugChannel, "Cancelling Job with dependencies [%s, %s, %s], replacing with re-queued job\n", 
-                        checkFile.GetInputAssetName().toUtf8().data(), checkFile.GetPlatform().toUtf8().data(), checkFile.GetJobDescriptor().toUtf8().data());
+                    AZ_TracePrintf(AssetProcessor::DebugChannel, "Cancelling Job with dependencies [%s, %s, %s], replacing with re-queued job\n",
+                        checkFile.GetSourceAssetReference().AbsolutePath().c_str(), checkFile.GetPlatform().toUtf8().data(), checkFile.GetJobDescriptor().toUtf8().data());
                     cancelJob = true;
                 }
                 else
                 {
-                    AZ_TracePrintf(AssetProcessor::DebugChannel, "Job is already in progress but has the same computed fingerprint (%u) - ignored [%s, %s, %s]\n", details.m_jobEntry.m_computedFingerprint,  checkFile.GetInputAssetName().toUtf8().data(), checkFile.GetPlatform().toUtf8().data(), checkFile.GetJobDescriptor().toUtf8().data());
+                    AZ_TracePrintf(
+                        AssetProcessor::DebugChannel,
+                        "Job is already in progress but has the same computed fingerprint (%u) - ignored [%s, %s, %s]\n",
+                        details.m_jobEntry.m_computedFingerprint,
+                        checkFile.GetSourceAssetReference().AbsolutePath().c_str(),
+                        checkFile.GetPlatform().toUtf8().data(),
+                        checkFile.GetJobDescriptor().toUtf8().data());
                     return;
                 }
 
@@ -272,7 +290,7 @@ namespace AssetProcessor
         {
             m_dispatchingJobs = true;
             RCJob* rcJob = m_RCQueueSortModel.GetNextPendingJob();
-            
+
             while (m_RCJobListModel.jobsInFlight() < m_maxJobs && rcJob && !m_shuttingDown)
             {
                 if (m_dispatchingPaused)
@@ -330,7 +348,7 @@ namespace AssetProcessor
             // it is not necessary to denote the search terms or list of results here because
             // PerformHeursticSearch already prints out the results.
             m_RCQueueSortModel.OnEscalateJobs(escalationList);
-            
+
             m_activeCompileGroups.push_back(AssetCompileGroup());
             m_activeCompileGroups.back().m_groupMembers.swap(results);
             m_activeCompileGroups.back().m_requestID = groupID;
@@ -366,7 +384,7 @@ namespace AssetProcessor
 #if defined(AZ_ENABLE_TRACING)
             for (const AssetProcessor::QueueElementID& result : results)
             {
-                AZ_TracePrintf(AssetProcessor::DebugChannel, "OnEscalateJobsBySourceUUID:  %s --> %s\n", sourceUuid.ToString<AZStd::string>().c_str(), result.GetInputAssetName().toUtf8().constData());
+                AZ_TracePrintf(AssetProcessor::DebugChannel, "OnEscalateJobsBySourceUUID:  %s --> %s\n", sourceUuid.ToString<AZStd::string>().c_str(), result.GetSourceAssetReference().AbsolutePath().c_str());
             }
 #endif
             m_RCQueueSortModel.OnEscalateJobs(escalationList);
@@ -382,7 +400,7 @@ namespace AssetProcessor
             return;
         }
 
-        QueueElementID jobQueueId(completeEntry.m_databaseSourceName, completeEntry.m_platformInfo.m_identifier.c_str(), completeEntry.m_jobKey);
+        QueueElementID jobQueueId(completeEntry.m_sourceAssetReference, completeEntry.m_platformInfo.m_identifier.c_str(), completeEntry.m_jobKey);
 
         // only the 'completed' status means success:
         bool statusSucceeded = (state == AzToolsFramework::AssetSystem::JobStatus::Completed);
@@ -404,13 +422,13 @@ namespace AssetProcessor
             }
         }
     }
-    
-    void RCController::RemoveJobsBySource(QString relSourceFileDatabaseName)
+
+    void RCController::RemoveJobsBySource(const SourceAssetReference& sourceAsset)
     {
         // some jobs may have not been started yet, these need to be removed manually
         AZStd::vector<RCJob*> pendingJobs;
 
-        m_RCJobListModel.EraseJobs(relSourceFileDatabaseName, pendingJobs);
+        m_RCJobListModel.EraseJobs(sourceAsset, pendingJobs);
 
         // force finish all pending jobs
         for (auto* rcJob : pendingJobs)
@@ -421,7 +439,7 @@ namespace AssetProcessor
 
     void RCController::OnAddedToCatalog(JobEntry jobEntry)
     {
-        AssetProcessor::QueueElementID checkFile(jobEntry.m_databaseSourceName, jobEntry.m_platformInfo.m_identifier.c_str(), jobEntry.m_jobKey);
+        AssetProcessor::QueueElementID checkFile(jobEntry.m_sourceAssetReference, jobEntry.m_platformInfo.m_identifier.c_str(), jobEntry.m_jobKey);
 
         m_RCJobListModel.markAsCataloged(checkFile);
 

+ 3 - 3
Code/Tools/AssetProcessor/native/resourcecompiler/rccontroller.h

@@ -69,7 +69,7 @@ namespace AssetProcessor
         void JobStarted(QString inputFile, QString platform);
         void JobStatusChanged(JobEntry entry, AzToolsFramework::AssetSystem::JobStatus status);
         void JobsInQueuePerPlatform(QString platform, int jobs);
-        void ActiveJobsCountChanged(unsigned int jobs); // This is the count of jobs which are either queued or inflight 
+        void ActiveJobsCountChanged(unsigned int jobs); // This is the count of jobs which are either queued or inflight
 
         void BecameIdle();
 
@@ -97,7 +97,7 @@ namespace AssetProcessor
         void SetDispatchPaused(bool pause);
 
         //! All jobs which match this source will be cancelled or removed.  Note that relSourceFile should have any applicable output prefixes!
-        void RemoveJobsBySource(QString relSourceFileDatabaseName);
+        void RemoveJobsBySource(const AssetProcessor::SourceAssetReference& sourceAsset);
 
         // when the AP is truly done with a particular job and its going to be deleted and nothing more cares about it,
         // this function is called. this allows us to synchronize the various threads (catalog, queue, etc) to know that
@@ -131,7 +131,7 @@ namespace AssetProcessor
         };
 
         QList<AssetCompileGroup> m_activeCompileGroups;
-        
+
     };
 } // namespace AssetProcessor
 

+ 7 - 7
Code/Tools/AssetProcessor/native/resourcecompiler/rcjob.cpp

@@ -96,7 +96,7 @@ namespace AssetProcessor
     void RCJob::Init(JobDetails& details)
     {
         m_jobDetails = AZStd::move(details);
-        m_queueElementID = QueueElementID(GetJobEntry().m_databaseSourceName, GetPlatformInfo().m_identifier.c_str(), GetJobKey());
+        m_queueElementID = QueueElementID(GetJobEntry().m_sourceAssetReference, GetPlatformInfo().m_identifier.c_str(), GetJobKey());
     }
 
     const JobEntry& RCJob::GetJobEntry() const
@@ -234,9 +234,9 @@ namespace AssetProcessor
         processJobRequest.m_jobDescription.m_priority = GetPriority();
         processJobRequest.m_platformInfo = GetPlatformInfo();
         processJobRequest.m_builderGuid = GetBuilderGuid();
-        processJobRequest.m_sourceFile = GetJobEntry().m_pathRelativeToWatchFolder.toUtf8().data();
+        processJobRequest.m_sourceFile = GetJobEntry().m_sourceAssetReference.RelativePath().c_str();
         processJobRequest.m_sourceFileUUID = GetInputFileUuid();
-        processJobRequest.m_watchFolder = GetJobEntry().m_watchFolderPath.toUtf8().data();
+        processJobRequest.m_watchFolder = GetJobEntry().m_sourceAssetReference.ScanFolderPath().c_str();
         processJobRequest.m_fullPath = GetJobEntry().GetAbsoluteSourcePath().toUtf8().data();
         processJobRequest.m_jobId = GetJobEntry().m_jobRunKey;
     }
@@ -539,7 +539,7 @@ namespace AssetProcessor
                                 if (!operationResult)
                                 {
                                     AZ_TracePrintf(AssetProcessor::DebugChannel, "Unable to save job (%s, %s, %s) with fingerprint (%u) to the server.\n",
-                                        builderParams.m_rcJob->GetJobEntry().m_pathRelativeToWatchFolder.toUtf8().data(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
+                                        builderParams.m_rcJob->GetJobEntry().m_sourceAssetReference.AbsolutePath().c_str(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
                                         builderParams.m_rcJob->GetPlatformInfo().m_identifier.c_str(), builderParams.m_rcJob->GetOriginalFingerprint());
                                 }
                             }
@@ -557,7 +557,7 @@ namespace AssetProcessor
                             else
                             {
                                 AZ_TracePrintf(AssetProcessor::DebugChannel, "Unable to get job (%s, %s, %s) with fingerprint (%u) from the server. Processing locally.\n",
-                                    builderParams.m_rcJob->GetJobEntry().m_pathRelativeToWatchFolder.toUtf8().data(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
+                                    builderParams.m_rcJob->GetJobEntry().m_sourceAssetReference.AbsolutePath().c_str(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
                                     builderParams.m_rcJob->GetPlatformInfo().m_identifier.c_str(), builderParams.m_rcJob->GetOriginalFingerprint());
                             }
 
@@ -973,7 +973,7 @@ namespace AssetProcessor
         }
         AzToolsFramework::AssetSystem::JobInfo jobInfo;
         AzToolsFramework::AssetSystem::AssetJobLogResponse jobLogResponse;
-        jobInfo.m_sourceFile = builderParams.m_rcJob->GetJobEntry().m_databaseSourceName.toUtf8().data();
+        jobInfo.m_sourceFile = builderParams.m_rcJob->GetJobEntry().m_sourceAssetReference.RelativePath().c_str();
         jobInfo.m_platform = builderParams.m_rcJob->GetPlatformInfo().m_identifier.c_str();
         jobInfo.m_jobKey = builderParams.m_rcJob->GetJobKey().toUtf8().data();
         jobInfo.m_builderGuid = builderParams.m_rcJob->GetBuilderGuid();
@@ -1018,7 +1018,7 @@ namespace AssetProcessor
         if (!jobLogResponse.m_isSuccess)
         {
             AZ_TracePrintf(AssetProcessor::DebugChannel, "Job log request was unsuccessful for job (%s, %s, %s) from the server.\n",
-                builderParams.m_rcJob->GetJobEntry().m_pathRelativeToWatchFolder.toUtf8().data(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
+                builderParams.m_rcJob->GetJobEntry().m_sourceAssetReference.AbsolutePath().c_str(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
                 builderParams.m_rcJob->GetPlatformInfo().m_identifier.c_str());
 
             if(jobLogResponse.m_jobLog.find("No log file found") != AZStd::string::npos)

+ 72 - 32
Code/Tools/AssetProcessor/native/resourcecompiler/rcjoblistmodel.cpp

@@ -116,7 +116,7 @@ namespace AssetProcessor
         case stateRole:
             return RCJob::GetStateDescription(getItem(index.row())->GetState());
         case displayNameRole:
-            return getItem(index.row())->GetJobEntry().m_pathRelativeToWatchFolder;
+            return getItem(index.row())->GetJobEntry().m_sourceAssetReference.RelativePath().c_str();
         case timeCreatedRole:
             return getItem(index.row())->GetTimeCreated().toString("hh:mm:ss.zzz");
         case timeLaunchedRole:
@@ -132,7 +132,7 @@ namespace AssetProcessor
             case ColumnState:
                 return RCJob::GetStateDescription(getItem(index.row())->GetState());
             case ColumnCommand:
-                return getItem(index.row())->GetJobEntry().m_pathRelativeToWatchFolder;
+                return getItem(index.row())->GetJobEntry().m_sourceAssetReference.RelativePath().c_str();
             case ColumnCompleted:
                 return getItem(index.row())->GetTimeCompleted().toString("hh:mm:ss.zzz");
             case ColumnPlatform:
@@ -203,9 +203,9 @@ namespace AssetProcessor
                 return;
             }
         }
-        
+
         AZ_TracePrintf(AssetProcessor::DebugChannel, "JobTrace jobIndex == -1!!! (%i %s,%s,%s)\n",
-            rcJob, rcJob->GetJobEntry().m_databaseSourceName.toUtf8().constData(),
+            rcJob, rcJob->GetJobEntry().GetAbsoluteSourcePath().toUtf8().constData(),
             rcJob->GetPlatformInfo().m_identifier.c_str(),
             rcJob->GetJobKey().toUtf8().constData());
         AZ_Assert(false, "Job not found!!!");
@@ -276,7 +276,13 @@ namespace AssetProcessor
             }
         }
 
-        AZ_TracePrintf(AssetProcessor::DebugChannel, "JobTrace jobIndex == -1!!! (%i %s,%s,%s)\n", rcJob, rcJob->GetJobEntry().m_databaseSourceName.toUtf8().constData(), rcJob->GetPlatformInfo().m_identifier.c_str(), rcJob->GetJobKey().toUtf8().constData());
+        AZ_TracePrintf(
+            AssetProcessor::DebugChannel,
+            "JobTrace jobIndex == -1!!! (%i %s,%s,%s)\n",
+            rcJob,
+            rcJob->GetJobEntry().GetAbsoluteSourcePath().toUtf8().constData(),
+            rcJob->GetPlatformInfo().m_identifier.c_str(),
+            rcJob->GetJobKey().toUtf8().constData());
         AZ_Assert(false, "Job not found!!!");
     }
 
@@ -286,7 +292,7 @@ namespace AssetProcessor
 
         if(itr == m_finishedJobsNotInCatalog.end())
         {
-            AZ_Assert(false, "Attempting to mark a job as written to the catalog before the job has been put in the waiting queue! %s", check.GetInputAssetName().toUtf8().constData());
+            AZ_Assert(false, "Attempting to mark a job as written to the catalog before the job has been put in the waiting queue! %s", check.GetSourceAssetReference().AbsolutePath().c_str());
             return;
         }
 
@@ -325,20 +331,25 @@ namespace AssetProcessor
         return -1; // invalid index
     }
 
-    void RCJobListModel::EraseJobs(QString sourceFileDatabaseName, AZStd::vector<RCJob*>& pendingJobs)
+    void RCJobListModel::EraseJobs(const SourceAssetReference& sourceAsset, AZStd::vector<RCJob*>& pendingJobs)
     {
         for (int jobIdx = 0; jobIdx < rowCount(); ++jobIdx)
         {
             RCJob* job = getItem(jobIdx);
-            if (QString::compare(job->GetJobEntry().m_databaseSourceName, sourceFileDatabaseName, Qt::CaseInsensitive) == 0)
+            if (job->GetJobEntry().m_sourceAssetReference == sourceAsset)
             {
                 const QueueElementID& target = job->GetElementID();
                 if ((isInQueue(target)) || (isInFlight(target)))
                 {
                     // Its important that this still follows the 'cancelled' flow, so that other parts of the code can update their "in progress" and other maps.
-                    AZ_TracePrintf(AssetProcessor::DebugChannel, "Cancelling Job [%s, %s, %s] because the source file no longer exists.\n", target.GetInputAssetName().toUtf8().data(), target.GetPlatform().toUtf8().data(), target.GetJobDescriptor().toUtf8().data());
-
-                    // if a job is pending, it was never started and thus will never enter Finished state, 
+                    AZ_TracePrintf(
+                        AssetProcessor::DebugChannel,
+                        "Cancelling Job [%s, %s, %s] because the source file no longer exists.\n",
+                        target.GetSourceAssetReference().AbsolutePath().c_str(),
+                        target.GetPlatform().toUtf8().data(),
+                        target.GetJobDescriptor().toUtf8().data());
+
+                    // if a job is pending, it was never started and thus will never enter Finished state,
                     // so simply changing its state to cancelled is not enough, collect them and return to rccontroller to process manually
                     if (job->GetState() == RCJob::JobState::pending)
                     {
@@ -383,11 +394,16 @@ namespace AssetProcessor
             {
                 continue;
             }
-            QString input = rcJob->GetJobEntry().m_pathRelativeToWatchFolder;
+            QString input = rcJob->GetJobEntry().m_sourceAssetReference.RelativePath().c_str();
             if (input.endsWith(searchTerm, Qt::CaseInsensitive))
             {
-                AZ_TracePrintf(AssetProcessor::DebugChannel, "Job Queue: Heuristic search found exact match (%s,%s,%s).\n", rcJob->GetJobEntry().m_databaseSourceName.toUtf8().constData(), rcJob->GetPlatformInfo().m_identifier.c_str(), rcJob->GetJobKey().toUtf8().constData());
-                found.insert(QueueElementID(rcJob->GetJobEntry().m_databaseSourceName, platform, rcJob->GetJobKey()));
+                AZ_TracePrintf(
+                    AssetProcessor::DebugChannel,
+                    "Job Queue: Heuristic search found exact match (%s,%s,%s).\n",
+                    rcJob->GetJobEntry().GetAbsoluteSourcePath().toUtf8().constData(),
+                    rcJob->GetPlatformInfo().m_identifier.c_str(),
+                    rcJob->GetJobKey().toUtf8().constData());
+                found.insert(QueueElementID(rcJob->GetJobEntry().m_sourceAssetReference, platform, rcJob->GetJobKey()));
                 escalationList.append(qMakePair(rcJob->GetJobEntry().m_jobRunKey, escalationValue));
             }
         }
@@ -398,11 +414,16 @@ namespace AssetProcessor
             {
                 continue;
             }
-            QString input = rcJob->GetJobEntry().m_pathRelativeToWatchFolder;
+            QString input = rcJob->GetJobEntry().m_sourceAssetReference.RelativePath().c_str();
             if (input.endsWith(searchTerm, Qt::CaseInsensitive))
             {
-                AZ_TracePrintf(AssetProcessor::DebugChannel, "Job Queue: Heuristic search found exact match (%s,%s,%s).\n", rcJob->GetJobEntry().m_databaseSourceName.toUtf8().constData(), rcJob->GetPlatformInfo().m_identifier.c_str(), rcJob->GetJobKey().toUtf8().constData());
-                found.insert(QueueElementID(rcJob->GetJobEntry().m_databaseSourceName, platform, rcJob->GetJobKey()));
+                AZ_TracePrintf(
+                    AssetProcessor::DebugChannel,
+                    "Job Queue: Heuristic search found exact match (%s,%s,%s).\n",
+                    rcJob->GetJobEntry().GetAbsoluteSourcePath().toUtf8().constData(),
+                    rcJob->GetPlatformInfo().m_identifier.c_str(),
+                    rcJob->GetJobKey().toUtf8().constData());
+                found.insert(QueueElementID(rcJob->GetJobEntry().m_sourceAssetReference, platform, rcJob->GetJobKey()));
             }
         }
 
@@ -425,15 +446,20 @@ namespace AssetProcessor
                 {
                     continue;
                 }
-                QString input = rcJob->GetJobEntry().m_pathRelativeToWatchFolder;
+                QString input = rcJob->GetJobEntry().m_sourceAssetReference.RelativePath().c_str();
                 dotIndex = input.lastIndexOf('.');
                 if (dotIndex != -1)
                 {
                     QStringRef testref = input.midRef(0, dotIndex);
                     if (testref.endsWith(searchTermWithNoExtension, Qt::CaseInsensitive))
                     {
-                        AZ_TracePrintf(AssetProcessor::DebugChannel, "Job Queue: Heuristic search found broad match (%s,%s,%s).\n", rcJob->GetJobEntry().m_pathRelativeToWatchFolder.toUtf8().constData(), rcJob->GetPlatformInfo().m_identifier.c_str(), rcJob->GetJobKey().toUtf8().constData());
-                        found.insert(QueueElementID(rcJob->GetJobEntry().m_databaseSourceName, platform, rcJob->GetJobKey()));
+                        AZ_TracePrintf(
+                            AssetProcessor::DebugChannel,
+                            "Job Queue: Heuristic search found broad match (%s,%s,%s).\n",
+                            rcJob->GetJobEntry().GetAbsoluteSourcePath().toUtf8().constData(),
+                            rcJob->GetPlatformInfo().m_identifier.c_str(),
+                            rcJob->GetJobKey().toUtf8().constData());
+                        found.insert(QueueElementID(rcJob->GetJobEntry().m_sourceAssetReference, platform, rcJob->GetJobKey()));
                         escalationList.append(qMakePair(rcJob->GetJobEntry().m_jobRunKey, escalationValue));
                     }
                 }
@@ -445,15 +471,20 @@ namespace AssetProcessor
                 {
                     continue;
                 }
-                QString input = rcJob->GetJobEntry().m_pathRelativeToWatchFolder;
+                QString input = rcJob->GetJobEntry().m_sourceAssetReference.RelativePath().c_str();
                 dotIndex = input.lastIndexOf('.');
                 if (dotIndex != -1)
                 {
                     QStringRef testref = input.midRef(0, dotIndex);
                     if (testref.endsWith(searchTermWithNoExtension, Qt::CaseInsensitive))
                     {
-                        AZ_TracePrintf(AssetProcessor::DebugChannel, "Job Queue: Heuristic search found broad match (%s,%s,%s).\n", rcJob->GetJobEntry().m_databaseSourceName.toUtf8().constData(), rcJob->GetPlatformInfo().m_identifier.c_str(), rcJob->GetJobKey().toUtf8().constData());
-                        found.insert(QueueElementID(rcJob->GetJobEntry().m_databaseSourceName, platform, rcJob->GetJobKey()));
+                        AZ_TracePrintf(
+                            AssetProcessor::DebugChannel,
+                            "Job Queue: Heuristic search found broad match (%s,%s,%s).\n",
+                            rcJob->GetJobEntry().GetAbsoluteSourcePath().toUtf8().constData(),
+                            rcJob->GetPlatformInfo().m_identifier.c_str(),
+                            rcJob->GetJobKey().toUtf8().constData());
+                        found.insert(QueueElementID(rcJob->GetJobEntry().m_sourceAssetReference, platform, rcJob->GetJobKey()));
                     }
                 }
             }
@@ -481,11 +512,16 @@ namespace AssetProcessor
             {
                 continue;
             }
-            QString input = rcJob->GetJobEntry().m_pathRelativeToWatchFolder;
+            QString input = rcJob->GetJobEntry().m_sourceAssetReference.RelativePath().c_str();
             if (input.contains(searchTermWithNoSuffix, Qt::CaseInsensitive)) //notice here that we use simply CONTAINS instead of endswith - this can potentially be very broad!
             {
-                AZ_TracePrintf(AssetProcessor::DebugChannel, "Job Queue: Heuristic search found ultra-broad match (%s,%s,%s).\n", rcJob->GetJobEntry().m_databaseSourceName.toUtf8().constData(), rcJob->GetPlatformInfo().m_identifier.c_str(), rcJob->GetJobKey().toUtf8().constData());
-                found.insert(QueueElementID(rcJob->GetJobEntry().m_databaseSourceName, platform, rcJob->GetJobKey()));
+                AZ_TracePrintf(
+                    AssetProcessor::DebugChannel,
+                    "Job Queue: Heuristic search found ultra-broad match (%s,%s,%s).\n",
+                    rcJob->GetJobEntry().GetAbsoluteSourcePath().toUtf8().constData(),
+                    rcJob->GetPlatformInfo().m_identifier.c_str(),
+                    rcJob->GetJobKey().toUtf8().constData());
+                found.insert(QueueElementID(rcJob->GetJobEntry().m_sourceAssetReference, platform, rcJob->GetJobKey()));
                 escalationList.append(qMakePair(rcJob->GetJobEntry().m_jobRunKey, escalationValue));
             }
         }
@@ -496,17 +532,21 @@ namespace AssetProcessor
             {
                 continue;
             }
-            
-            QString input = rcJob->GetJobEntry().m_pathRelativeToWatchFolder;
+
+            QString input = rcJob->GetJobEntry().m_sourceAssetReference.RelativePath().c_str();
             if (input.contains(searchTermWithNoSuffix, Qt::CaseInsensitive)) //notice here that we use simply CONTAINS instead of endswith - this can potentially be very broad!
             {
-                AZ_TracePrintf(AssetProcessor::DebugChannel, "Job Queue: Heuristic search found ultra-broad match (%s,%s,%s).\n", rcJob->GetJobEntry().m_databaseSourceName.toUtf8().constData(), rcJob->GetPlatformInfo().m_identifier.c_str(), rcJob->GetJobKey().toUtf8().constData());
-                found.insert(QueueElementID(rcJob->GetJobEntry().m_databaseSourceName, platform, rcJob->GetJobKey()));
+                AZ_TracePrintf(
+                    AssetProcessor::DebugChannel,
+                    "Job Queue: Heuristic search found ultra-broad match (%s,%s,%s).\n",
+                    rcJob->GetJobEntry().GetAbsoluteSourcePath().toUtf8().constData(),
+                    rcJob->GetPlatformInfo().m_identifier.c_str(),
+                    rcJob->GetJobKey().toUtf8().constData());
+                found.insert(QueueElementID(rcJob->GetJobEntry().m_sourceAssetReference, platform, rcJob->GetJobKey()));
             }
         }
     }
 
-
     void RCJobListModel::PerformUUIDSearch(AZ::Uuid searchUuid, QString platform, QSet<QueueElementID>& found, AssetProcessor::JobIdEscalationList& escalationList, bool isStatusRequest)
     {
         int escalationValue = 0;
@@ -528,7 +568,7 @@ namespace AssetProcessor
 
             if (rcJob->GetJobEntry().m_sourceFileUUID == searchUuid)
             {
-                found.insert(QueueElementID(rcJob->GetJobEntry().m_databaseSourceName, platform, rcJob->GetJobKey()));
+                found.insert(QueueElementID(rcJob->GetJobEntry().m_sourceAssetReference, platform, rcJob->GetJobKey()));
                 escalationList.append(qMakePair(rcJob->GetJobEntry().m_jobRunKey, escalationValue));
             }
         }

+ 1 - 1
Code/Tools/AssetProcessor/native/resourcecompiler/rcjoblistmodel.h

@@ -93,7 +93,7 @@ namespace AssetProcessor
         int GetIndexOfProcessingJob(const QueueElementID& elementId);
 
         ///! EraseJobs expects the database name of the source file.
-        void EraseJobs(QString sourceFileDatabaseName, AZStd::vector<RCJob*>& pendingJobs);
+        void EraseJobs(const SourceAssetReference& sourceAssetReference, AZStd::vector<RCJob*>& pendingJobs);
 
     private:
 

+ 37 - 5
Code/Tools/AssetProcessor/native/tests/AssetCatalog/AssetCatalogUnitTests.cpp

@@ -18,6 +18,7 @@
 #include <native/resourcecompiler/RCBuilder.h>
 
 #include "AssetManager/FileStateCache.h"
+#include <tests/UnitTestUtilities.h>
 
 namespace AssetProcessor
 {
@@ -186,15 +187,17 @@ namespace AssetProcessor
         // -- utility functions to create default state data --
 
         // Adds a scan folder to the config and to the database
-        void AddScanFolder(const ScanFolderInfo& scanFolderInfo, PlatformConfiguration& config, AssetDatabaseConnection* dbConn)
+        void AddScanFolder(ScanFolderInfo scanFolderInfo, PlatformConfiguration& config, AssetDatabaseConnection* dbConn)
         {
-            config.AddScanFolder(scanFolderInfo);
             ScanFolderDatabaseEntry newScanFolder(
                 scanFolderInfo.ScanPath().toStdString().c_str(),
                 scanFolderInfo.GetDisplayName().toStdString().c_str(),
                 scanFolderInfo.GetPortableKey().toStdString().c_str(),
                 scanFolderInfo.IsRoot());
             dbConn->SetScanFolder(newScanFolder);
+
+            scanFolderInfo.SetScanFolderID(newScanFolder.m_scanFolderID);
+            config.AddScanFolder(scanFolderInfo);
         }
 
         virtual void AddScanFolders(
@@ -919,6 +922,12 @@ namespace AssetProcessor
 
             if (expectedResult)
             {
+                EXPECT_EQ(assetInfo.m_assetId, m_customDataMembers->m_assetA);
+                EXPECT_EQ(assetInfo.m_assetType, m_customDataMembers->m_assetAType);
+                EXPECT_EQ(assetInfo.m_relativePath, expectedRelPath);
+                EXPECT_EQ(assetInfo.m_sizeBytes, m_customDataMembers->m_assetTestString.size());
+                EXPECT_EQ(rootPath, expectedRootPath);
+
                 return (assetInfo.m_assetId == m_customDataMembers->m_assetA)
                     && (assetInfo.m_assetType == m_customDataMembers->m_assetAType)
                     && (assetInfo.m_relativePath == expectedRelPath)
@@ -988,6 +997,17 @@ namespace AssetProcessor
         EXPECT_TRUE(GetSourceInfoBySourcePath(false, "", AZ::Uuid::CreateNull(), "", ""));
     }
 
+    TEST_F(AssetCatalogTest_AssetInfo, Sanity_InvalidPath)
+    {
+        auto* ebus = AzToolsFramework::AssetSystemRequestBus::FindFirstHandler();
+
+        AZ::Data::AssetInfo assetInfo;
+        AZStd::string watchFolder;
+
+        EXPECT_FALSE(ebus->GetSourceInfoBySourcePath("G:/random/folder/does/not/exist.png", assetInfo, watchFolder)); // Absolute path
+        EXPECT_FALSE(ebus->GetSourceInfoBySourcePath("random/folder/does/not/exist.png", assetInfo, watchFolder)); // Relative path
+    }
+
     TEST_F(AssetCatalogTest_AssetInfo, FindAssetNotRegisteredAsSource_FindsProduct)
     {
         // Setup: Add asset to database
@@ -1017,7 +1037,11 @@ namespace AssetProcessor
     TEST_F(AssetCatalogTest_AssetInfo, FindAssetInBuildQueue_FindsSource)
     {
         // Setup:  Add a source to queue.
-        m_data->m_assetCatalog->OnSourceQueued(m_customDataMembers->m_assetA.m_guid, m_customDataMembers->m_assetALegacyUuid, m_customDataMembers->m_subfolder1AbsolutePath.c_str(), m_customDataMembers->m_assetASourceRelPath.c_str());
+        m_data->m_assetCatalog->OnSourceQueued(
+            m_customDataMembers->m_assetA.m_guid,
+            m_customDataMembers->m_assetALegacyUuid,
+            AssetProcessor::SourceAssetReference(
+                m_customDataMembers->m_subfolder1AbsolutePath.c_str(), m_customDataMembers->m_assetASourceRelPath.c_str()));
 
         // TEST: Asset in queue, not registered as source asset
         EXPECT_TRUE(GetAssetInfoByIdPair(false, "", ""));
@@ -1030,7 +1054,11 @@ namespace AssetProcessor
     TEST_F(AssetCatalogTest_AssetInfo, FindAssetInBuildQueue_RegisteredAsSourceType_StillFindsSource)
     {
         // Setup:  Add a source to queue.
-        m_data->m_assetCatalog->OnSourceQueued(m_customDataMembers->m_assetA.m_guid, m_customDataMembers->m_assetALegacyUuid, m_customDataMembers->m_subfolder1AbsolutePath.c_str(), m_customDataMembers->m_assetASourceRelPath.c_str());
+        m_data->m_assetCatalog->OnSourceQueued(
+            m_customDataMembers->m_assetA.m_guid,
+            m_customDataMembers->m_assetALegacyUuid,
+            AssetProcessor::SourceAssetReference(
+                m_customDataMembers->m_subfolder1AbsolutePath.c_str(), m_customDataMembers->m_assetASourceRelPath.c_str()));
 
         // Register as source type
         AzToolsFramework::ToolsAssetSystemBus::Broadcast(&AzToolsFramework::ToolsAssetSystemRequests::RegisterSourceAssetType, m_customDataMembers->m_assetAType, m_customDataMembers->m_assetAFileFilter.c_str());
@@ -1051,7 +1079,11 @@ namespace AssetProcessor
         // Setup:  Add a source to queue, then notify its finished and add it to the database (simulates a full pipeline)
         AZ::s64 jobId;
         EXPECT_TRUE(AddSourceAndJob("subfolder1", m_customDataMembers->m_assetASourceRelPath.c_str(), &(m_data->m_dbConn), jobId, m_customDataMembers->m_assetA.m_guid));
-        m_data->m_assetCatalog->OnSourceQueued(m_customDataMembers->m_assetA.m_guid, m_customDataMembers->m_assetALegacyUuid, m_customDataMembers->m_subfolder1AbsolutePath.c_str(), m_customDataMembers->m_assetASourceRelPath.c_str());
+        m_data->m_assetCatalog->OnSourceQueued(
+            m_customDataMembers->m_assetA.m_guid,
+            m_customDataMembers->m_assetALegacyUuid,
+            AssetProcessor::SourceAssetReference(
+                m_customDataMembers->m_subfolder1AbsolutePath.c_str(), m_customDataMembers->m_assetASourceRelPath.c_str()));
         m_data->m_assetCatalog->OnSourceFinished(m_customDataMembers->m_assetA.m_guid, m_customDataMembers->m_assetALegacyUuid);
         ProductDatabaseEntry assetAEntry(jobId, 0, m_customDataMembers->m_assetAProductRelPath.c_str(), m_customDataMembers->m_assetAType);
         m_data->m_dbConn.SetProduct(assetAEntry);

+ 9 - 2
Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.cpp

@@ -21,13 +21,18 @@
 
 namespace AssetProcessor
 {
-    class UnitTestAppManager : public BatchApplicationManager
+    class UnitTestAppManager : public BatchApplicationManager, AZ::Interface<IUnitTestAppManager>::Registrar
     {
     public:
         explicit UnitTestAppManager(int* argc, char*** argv)
             : BatchApplicationManager(argc, argv)
         {}
 
+        PlatformConfiguration& GetConfig()
+        {
+            return *m_platformConfig;
+        }
+
         bool PrepareForTests()
         {
             if (!ApplicationManager::Activate())
@@ -41,7 +46,10 @@ namespace AssetProcessor
             // Disable saving global user settings to prevent failure due to detecting file updates
             AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
 
+
             m_platformConfig.reset(new AssetProcessor::PlatformConfiguration);
+
+
             m_connectionManager.reset(new ConnectionManager(m_platformConfig.get()));
             RegisterObjectForQuit(m_connectionManager.get());
 
@@ -103,7 +111,6 @@ namespace AssetProcessor
 
         AZStd::unique_ptr<UnitTestAppManager> m_application;
         AZStd::unique_ptr<MockAssetDatabaseRequestsHandler> m_assetDatabaseRequestsHandler;
-
     };
 
     // use the list of registered legacy unit tests to generate the list of test parameters:

+ 8 - 0
Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h

@@ -17,9 +17,17 @@
 #include <AssetManager/FileStateCache.h>
 #include <AzCore/Component/ComponentApplicationLifecycle.h>
 #include <tests/ApplicationManagerTests.h>
+#include "UnitTestUtilities.h"
 
 namespace AssetProcessor
 {
+    struct IUnitTestAppManager
+    {
+        AZ_RTTI(IUnitTestAppManager, "{37578207-790A-4928-BD47-B9C4F4B49C3A}");
+
+        virtual PlatformConfiguration& GetConfig() = 0;
+    };
+
     // This is an utility class for Asset Processor Tests
     // Any gmock based fixture class can derived from this class and this will automatically do system allocation and teardown for you
     // It is important to note that if you are overriding Setup and Teardown functions of your fixture class than please call the base class functions.

+ 191 - 0
Code/Tools/AssetProcessor/native/tests/SourceAssetReferenceTests.cpp

@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <AzTest/AzTest.h>
+#include <AzCore/UnitTest/TestTypes.h>
+#include <AssetManager/SourceAssetReference.h>
+#include "UnitTestUtilities.h"
+
+namespace UnitTests
+{
+    using SourceAssetReferenceTests = ::UnitTest::ScopedAllocatorSetupFixture;
+
+    TEST_F(SourceAssetReferenceTests, Construct_AbsolutePath_Succeeds)
+    {
+        using namespace AssetProcessor;
+
+        MockPathConversion mockPathConversion;
+
+        constexpr const char* path = "c:/somepath/file.png";
+
+        SourceAssetReference test1{ QString(path) };
+        SourceAssetReference test2{ path };
+        SourceAssetReference test3{ AZ::IO::Path(path) };
+
+        EXPECT_EQ(test1.AbsolutePath(), path);
+        EXPECT_EQ(test2.AbsolutePath(), path);
+        EXPECT_EQ(test3.AbsolutePath(), path);
+
+        EXPECT_EQ(test1.RelativePath(), "file.png");
+        EXPECT_EQ(test1.ScanFolderPath(), "c:/somepath");
+        EXPECT_EQ(test1.ScanFolderId(), 1);
+    }
+
+    TEST_F(SourceAssetReferenceTests, Construct_ScanFolderPath_Succeeds)
+    {
+        using namespace AssetProcessor;
+
+        MockPathConversion mockPathConversion;
+
+        constexpr const char* path = "c:/somepath/file.png";
+        constexpr const char* scanfolder = "c:/somepath";
+        constexpr const char* relative = "file.png";
+
+        SourceAssetReference test1{ QString(scanfolder), QString(relative) };
+        SourceAssetReference test2{ scanfolder, relative };
+        SourceAssetReference test3{ AZ::IO::Path(scanfolder), AZ::IO::Path(relative) };
+
+        EXPECT_EQ(test1.AbsolutePath(), path);
+        EXPECT_EQ(test2.AbsolutePath(), path);
+        EXPECT_EQ(test3.AbsolutePath(), path);
+
+        EXPECT_EQ(test1.RelativePath(), relative);
+        EXPECT_EQ(test1.ScanFolderPath(), scanfolder);
+        EXPECT_EQ(test1.ScanFolderId(), 1);
+    }
+
+    TEST_F(SourceAssetReferenceTests, Construct_ScanFolderId_Succeeds)
+    {
+        using namespace AssetProcessor;
+
+        MockPathConversion mockPathConversion;
+
+        constexpr const char* path = "c:/somepath/file.png";
+        constexpr const char* scanfolder = "c:/somepath";
+        constexpr const char* relative = "file.png";
+
+        SourceAssetReference test1{ 1, relative };
+
+        EXPECT_EQ(test1.AbsolutePath(), path);
+        EXPECT_EQ(test1.RelativePath(), relative);
+        EXPECT_EQ(test1.ScanFolderPath(), scanfolder);
+        EXPECT_EQ(test1.ScanFolderId(), 1);
+    }
+
+    TEST_F(SourceAssetReferenceTests, Copy_Succeeds)
+    {
+        using namespace AssetProcessor;
+
+        MockPathConversion mockPathConversion;
+
+        constexpr const char* path = "c:/somepath/file.png";
+
+        SourceAssetReference test1{ QString(path) };
+
+        auto test2 = test1;
+        auto test3(test1);
+
+        EXPECT_EQ(test1.AbsolutePath(), test2.AbsolutePath());
+        EXPECT_EQ(test2.AbsolutePath(), test3.AbsolutePath());
+
+        EXPECT_EQ(test1.RelativePath(), test2.RelativePath());
+        EXPECT_EQ(test2.RelativePath(), test3.RelativePath());
+
+        EXPECT_EQ(test1.ScanFolderPath(), test2.ScanFolderPath());
+        EXPECT_EQ(test2.ScanFolderPath(), test3.ScanFolderPath());
+
+        EXPECT_EQ(test1.ScanFolderId(), test2.ScanFolderId());
+        EXPECT_EQ(test2.ScanFolderId(), test3.ScanFolderId());
+    }
+
+    TEST_F(SourceAssetReferenceTests, Move_Succeeds)
+    {
+        using namespace AssetProcessor;
+
+        MockPathConversion mockPathConversion;
+
+        constexpr const char* path = "c:/somepath/file.png";
+
+        SourceAssetReference test1{ QString(path) };
+
+        auto test2 = AZStd::move(test1);
+
+        EXPECT_EQ(test2.AbsolutePath(), path);
+        EXPECT_EQ(test2.RelativePath(), "file.png");
+        EXPECT_EQ(test2.ScanFolderPath(), "c:/somepath");
+        EXPECT_EQ(test2.ScanFolderId(), 1);
+
+        auto test3(AZStd::move(test2));
+
+        EXPECT_EQ(test3.AbsolutePath(), path);
+        EXPECT_EQ(test3.RelativePath(), "file.png");
+        EXPECT_EQ(test3.ScanFolderPath(), "c:/somepath");
+        EXPECT_EQ(test3.ScanFolderId(), 1);
+    }
+
+    TEST_F(SourceAssetReferenceTests, BoolCheck_EmptyReference_ReturnsFalse)
+    {
+        using namespace AssetProcessor;
+
+        SourceAssetReference test;
+
+        EXPECT_FALSE(test);
+    }
+
+    TEST_F(SourceAssetReferenceTests, BoolCheck_ValidReference_ReturnsTrue)
+    {
+        using namespace AssetProcessor;
+
+        MockPathConversion mockPathConversion;
+
+        constexpr const char* path = "c:/somepath/file.png";
+
+        SourceAssetReference test{ path };
+
+        EXPECT_TRUE(test);
+    }
+
+    TEST_F(SourceAssetReferenceTests, SamePaths_AreEqual)
+    {
+        using namespace AssetProcessor;
+
+        MockPathConversion mockPathConversion;
+
+        constexpr const char* path = "c:/somepath/file.png";
+
+        SourceAssetReference test1{ path };
+        SourceAssetReference test2{ path };
+
+        EXPECT_EQ(test1, test2);
+    }
+
+    TEST_F(SourceAssetReferenceTests, DifferentPaths_AreNotEqual)
+    {
+        using namespace AssetProcessor;
+
+        MockPathConversion mockPathConversion;
+
+        SourceAssetReference test1{ "c:/somepath/file.png" };
+        SourceAssetReference test2{ "c:/somepath/file2.png" };
+
+        EXPECT_NE(test1, test2);
+    }
+
+    TEST_F(SourceAssetReferenceTests, Comparison)
+    {
+        using namespace AssetProcessor;
+
+        MockPathConversion mockPathConversion;
+
+        SourceAssetReference test1{ "c:/somepath/file1.png" };
+        SourceAssetReference test2{ "c:/somepath/file2.png" };
+
+        EXPECT_LT(test1, test2);
+        EXPECT_GT(test2, test1);
+    }
+}

+ 30 - 0
Code/Tools/AssetProcessor/native/tests/UnitTestUtilities.h

@@ -109,4 +109,34 @@ namespace UnitTests
         MOCK_CONST_METHOD0(GetExecutableFolder, const char* ());
         MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&));
     };
+
+    struct MockPathConversion : AZ::Interface<AssetProcessor::IPathConversion>::Registrar
+    {
+        MockPathConversion(const char* scanfolder = "c:/somepath")
+        {
+            m_scanFolderInfo = AssetProcessor::ScanFolderInfo{ scanfolder, "scanfolder", "scanfolder", true, true, { AssetBuilderSDK::PlatformInfo{ "pc", {} } }, 0, 1 };
+        }
+
+        bool ConvertToRelativePath(QString fullFileName, QString& databaseSourceName, QString& scanFolderName) const override
+        {
+            scanFolderName = m_scanFolderInfo.ScanPath();
+            databaseSourceName = fullFileName.mid(scanFolderName.size() + 1);
+
+            return true;
+        }
+
+        //! given a full file name (assumed already fed through the normalization funciton), return the first matching scan folder
+        const AssetProcessor::ScanFolderInfo* GetScanFolderForFile(const QString& /*fullFileName*/) const override
+        {
+            return &m_scanFolderInfo;
+        }
+
+        const AssetProcessor::ScanFolderInfo* GetScanFolderById(AZ::s64 /*id*/) const override
+        {
+            return &m_scanFolderInfo;
+        }
+
+    private:
+        AssetProcessor::ScanFolderInfo m_scanFolderInfo;
+    };
 }

+ 0 - 3
Code/Tools/AssetProcessor/native/tests/assetmanager/AssetManagerTestingBase.h

@@ -75,9 +75,6 @@ namespace UnitTests
         void TearDown() override;
 
     protected:
-        void CreateTestData(AZ::u64 hashA, AZ::u64 hashB, bool useSubId);
-        void RunTest(bool firstProductChanged, bool secondProductChanged);
-
         void RunFile(int expectedJobCount, int expectedFileCount = 1, int dependencyFileCount = 0);
         void ProcessJob(AssetProcessor::RCController& rcController, const AssetProcessor::JobDetails& jobDetails);
 

+ 69 - 48
Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp

@@ -231,8 +231,7 @@ TEST_F(AssetProcessorManagerTest, UnitTestForGettingJobInfoBySourceUUIDSuccess)
     QString watchFolder = m_assetRootDir.absoluteFilePath("subfolder1");
 
     JobEntry entry;
-    entry.m_watchFolderPath = watchFolder;
-    entry.m_databaseSourceName = entry.m_pathRelativeToWatchFolder = relFileName;
+    entry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(watchFolder, relFileName);
     entry.m_jobKey = "txt";
     entry.m_platformInfo = { "pc", {"host", "renderer", "desktop"} };
     entry.m_jobRunKey = 1;
@@ -293,8 +292,7 @@ TEST_F(AssetProcessorManagerTest, WarningsAndErrorsReported_SuccessfullySavedToD
     QString watchFolder = m_assetRootDir.absoluteFilePath("subfolder1");
 
     JobEntry entry;
-    entry.m_watchFolderPath = watchFolder;
-    entry.m_databaseSourceName = entry.m_pathRelativeToWatchFolder = relFileName;
+    entry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(watchFolder, relFileName);
     entry.m_jobKey = "txt";
     entry.m_platformInfo = { "pc", {"host", "renderer", "desktop"} };
     entry.m_jobRunKey = 1;
@@ -354,9 +352,9 @@ TEST_F(AssetProcessorManagerTest, DeleteFolder_SignalsDeleteOfContainedFiles)
     m_assetProcessorManager->m_stateData->SetSource(sourceEntry);
 
     int count = 0;
-    auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted, [&count](QString file)
+    auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted, [&count](const SourceAssetReference& file)
         {
-            if (file.compare(folderPathNoScanfolder, Qt::CaseInsensitive) == 0)
+            if (QString(file.RelativePath().c_str()).compare(folderPathNoScanfolder, Qt::CaseInsensitive) == 0)
             {
                 count++;
             }
@@ -404,14 +402,12 @@ TEST_F(AssetProcessorManagerTest, UnitTestForCancelledJob)
     QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/assetProcessorManagerTest.txt"));
     JobEntry entry;
 
-    entry.m_watchFolderPath = m_assetRootDir.absolutePath();
-    entry.m_databaseSourceName = entry.m_pathRelativeToWatchFolder = relFileName;
-
+    entry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1"), relFileName);
     entry.m_jobKey = "txt";
     entry.m_platformInfo = { "pc", {"host", "renderer", "desktop"} };
     entry.m_jobRunKey = 1;
 
-    AZ::Uuid sourceUUID = AssetUtilities::CreateSafeSourceUUIDFromName(entry.m_databaseSourceName.toUtf8().data());
+    AZ::Uuid sourceUUID = AssetUtilities::CreateSafeSourceUUIDFromName(relFileName.toUtf8().constData());
     bool sourceFound = false;
 
     //Checking the response of the APM when we cancel a job in progress
@@ -2400,6 +2396,7 @@ TEST_F(PathDependencyTest, MixedPathDependencies_Deferred_ResolveCorrectly)
 void MultiplatformPathDependencyTest::SetUp()
 {
     AssetProcessorManagerTest::SetUp();
+    m_config = nullptr; // Make sure to clear this out first so the existing config can cleanup before we allocate the new one
     m_config.reset(new AssetProcessor::PlatformConfiguration());
     m_config->EnablePlatform({ "pc", { "host", "renderer", "desktop" } }, true);
     m_config->EnablePlatform({ "provo",{ "console" } }, true);
@@ -2879,14 +2876,13 @@ void SourceFileDependenciesTest::SetupData(
     }
 
     // construct the dummy job to feed to the database updater function:
-    job.m_sourceFileInfo.m_databasePath = "assetProcessorManagerTest.txt";
-    job.m_sourceFileInfo.m_pathRelativeToScanFolder = "assetProcessorManagerTest.txt";
+    job.m_sourceFileInfo.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_absPath);
     job.m_sourceFileInfo.m_scanFolder = m_scanFolder;
-    job.m_sourceFileInfo.m_uuid = AssetUtilities::CreateSafeSourceUUIDFromName(job.m_sourceFileInfo.m_databasePath.toUtf8().data());
+    job.m_sourceFileInfo.m_uuid = AssetUtilities::CreateSafeSourceUUIDFromName(job.m_sourceFileInfo.m_sourceAssetReference.RelativePath().c_str());
 
     if (primeMap)
     {
-        m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[job.m_sourceFileInfo.m_uuid] = { m_watchFolderPath, job.m_sourceFileInfo.m_databasePath, job.m_sourceFileInfo.m_databasePath };
+        m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[job.m_sourceFileInfo.m_uuid] = job.m_sourceFileInfo.m_sourceAssetReference;
     }
 
     for (const auto& sourceFileDependency : sourceFileDependencies)
@@ -3112,11 +3108,10 @@ TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_MissingF
     ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_dependsOnFile2_Source, QString("tempdata\n")));
     // now that B exists, we pretend a job came in to process B. (it doesn't require dependencies to be declared)
     // note that we have to "prime" the map with the UUIDs to the source info for this to work:
-    m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfB] = { m_watchFolderPath, "b.txt", "b.txt" };
+    m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfB] = SourceAssetReference(m_watchFolderPath, "b.txt");
 
     AssetProcessorManager::JobToProcessEntry job2;
-    job2.m_sourceFileInfo.m_databasePath = "b.txt";
-    job2.m_sourceFileInfo.m_pathRelativeToScanFolder = "b.txt";
+    job2.m_sourceFileInfo.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_watchFolderPath, "b.txt");
     job2.m_sourceFileInfo.m_scanFolder = m_scanFolder;
     job2.m_sourceFileInfo.m_uuid = m_uuidOfB;
 
@@ -3140,11 +3135,10 @@ TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_MissingF
     // now make d exist too and pretend a job came in to process it:
     ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_dependsOnFile2_Job, QString("tempdata\n"))); // create file D
     AssetProcessorManager::JobToProcessEntry job3;
-    job3.m_sourceFileInfo.m_databasePath = "d.txt";
-    job3.m_sourceFileInfo.m_pathRelativeToScanFolder = "d.txt";
+    job3.m_sourceFileInfo.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_watchFolderPath, "d.txt");
     job3.m_sourceFileInfo.m_scanFolder = m_scanFolder;
     job3.m_sourceFileInfo.m_uuid = m_uuidOfD;
-    m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfD] = { m_watchFolderPath, "d.txt", "d.txt" };
+    m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfD] = SourceAssetReference{ m_watchFolderPath, "d.txt" };
 
     m_assetProcessorManager.get()->UpdateSourceFileDependenciesDatabase(job3);
 
@@ -3175,11 +3169,10 @@ TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_MissingF
     ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_dependsOnFile1_Source, QString("tempdata\n")));
     // now that A exists, we pretend a job came in to process a. (it doesn't require dependencies to be declared)
     AssetProcessorManager::JobToProcessEntry job2;
-    job2.m_sourceFileInfo.m_databasePath = "a.txt";
-    job2.m_sourceFileInfo.m_pathRelativeToScanFolder = "a.txt";
+    job2.m_sourceFileInfo.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_watchFolderPath, "a.txt");
     job2.m_sourceFileInfo.m_scanFolder = m_scanFolder;
     job2.m_sourceFileInfo.m_uuid = m_uuidOfA;
-    m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfA] = { m_watchFolderPath, "a.txt", "a.txt" };
+    m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfA] = SourceAssetReference{ m_watchFolderPath, "a.txt" };
 
     m_assetProcessorManager.get()->UpdateSourceFileDependenciesDatabase(job2);
 
@@ -3201,11 +3194,10 @@ TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_MissingF
     ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_dependsOnFile1_Job, QString("tempdata\n")));
     AZ::Uuid uuidOfC = AssetUtilities::CreateSafeSourceUUIDFromName("c.txt");
     AssetProcessorManager::JobToProcessEntry job3;
-    job3.m_sourceFileInfo.m_databasePath = "c.txt";
-    job3.m_sourceFileInfo.m_pathRelativeToScanFolder = "c.txt";
+    job3.m_sourceFileInfo.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_watchFolderPath, "c.txt");
     job3.m_sourceFileInfo.m_scanFolder = m_scanFolder;
     job3.m_sourceFileInfo.m_uuid = uuidOfC;
-    m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfC] = { m_watchFolderPath, "c.txt", "c.txt" };
+    m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfC] = SourceAssetReference{ m_watchFolderPath, "c.txt" };
 
     m_assetProcessorManager.get()->UpdateSourceFileDependenciesDatabase(job3);
 
@@ -3276,7 +3268,10 @@ TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_JobAndSo
     EXPECT_THAT(
         actualDependencies,
         ::testing::UnorderedElementsAre(
-            "a.txt", m_uuidOfA.ToFixedString(false, false).c_str(), "b.txt", m_uuidOfB.ToFixedString(false, false).c_str()));
+            "a.txt",
+            m_uuidOfA.ToFixedString(false, false).c_str(),
+            "b.txt",
+            m_uuidOfB.ToFixedString(false, false).c_str()));
 }
 
 TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_SourceDependenciesDuplicatedWildcard)
@@ -3291,7 +3286,33 @@ TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_SourceDe
 
     auto actualDependencies = GetDependencyList();
 
-    EXPECT_THAT(actualDependencies, ::testing::UnorderedElementsAre("a.txt", "a.t%t", m_uuidOfB.ToFixedString(false, false).c_str()));
+    EXPECT_THAT(
+        actualDependencies,
+        ::testing::UnorderedElementsAre(
+            "a.txt",
+            "a.t%t",
+            m_uuidOfB.ToFixedString(false, false).c_str()));
+}
+
+TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_AbsolutePathIsPreserved)
+{
+    QDir tempPath(m_assetRootDir.path());
+    QString absPath = tempPath.absoluteFilePath("subfolder1/a.txt");
+
+    AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
+    SetupData(
+        {
+            MakeSourceDependency(absPath.toUtf8().constData()),
+        },
+        {},
+        true,
+        true,
+        true,
+        job);
+
+    auto actualDependencies = GetDependencyList();
+
+    EXPECT_THAT(actualDependencies, ::testing::UnorderedElementsAre(absPath.toUtf8().constData()));
 }
 
 TEST_F(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK)
@@ -3354,10 +3375,10 @@ TEST_F(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK)
 
     // Although we have processed a.dummy first, APM should send us notification of b.dummy job first and than of a.dummy job
     EXPECT_EQ(jobDetails.size(), 2);
-    EXPECT_EQ(jobDetails[0].m_jobEntry.m_databaseSourceName, secondRelSourceFile);
-    EXPECT_EQ(jobDetails[1].m_jobEntry.m_databaseSourceName, relSourceFileName);
+    EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile);
+    EXPECT_EQ(jobDetails[1].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), sourceFileName);
     EXPECT_EQ(jobDetails[1].m_jobDependencyList.size(), 1); // there should only be one job dependency
-    EXPECT_EQ(jobDetails[1].m_jobDependencyList[0].m_jobDependency.m_sourceFile.m_sourceFileDependencyPath, secondRelSourceFile); // there should only be one job dependency
+    EXPECT_EQ(jobDetails[1].m_jobDependencyList[0].m_jobDependency.m_sourceFile.m_sourceFileDependencyPath, secondSourceFile.toUtf8().constData()); // there should only be one job dependency
 
     // Process jobs in APM
     auto destination = jobDetails[0].m_cachePath;
@@ -3390,7 +3411,7 @@ TEST_F(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK)
     QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
     ASSERT_TRUE(BlockUntilIdle(5000));
     EXPECT_EQ(jobDetails.size(), 1);
-    EXPECT_EQ(jobDetails[0].m_jobEntry.m_databaseSourceName, secondRelSourceFile);
+    EXPECT_STREQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile.toUtf8().constData());
 
     jobDetails.clear();
     m_isIdling = false;
@@ -3399,7 +3420,7 @@ TEST_F(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK)
     QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileName));
     ASSERT_TRUE(BlockUntilIdle(5000));
     EXPECT_EQ(jobDetails.size(), 1);
-    EXPECT_EQ(jobDetails[0].m_jobEntry.m_databaseSourceName, relSourceFileName);
+    EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), sourceFileName);
     EXPECT_EQ(jobDetails[0].m_jobDependencyList.size(), 0); // there should not be any job dependency since APM has already processed b.dummy before
 
     m_isIdling = false;
@@ -3414,7 +3435,7 @@ TEST_F(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK)
     QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
     ASSERT_TRUE(BlockUntilIdle(5000));
     EXPECT_EQ(jobDetails.size(), 1);
-    EXPECT_EQ(jobDetails[0].m_jobEntry.m_databaseSourceName, secondRelSourceFile);
+    EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile);
 
     responseB.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
     m_isIdling = false;
@@ -3430,10 +3451,10 @@ TEST_F(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK)
     QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
     ASSERT_TRUE(BlockUntilIdle(5000));
     EXPECT_EQ(jobDetails.size(), 2);
-    EXPECT_EQ(jobDetails[0].m_jobEntry.m_databaseSourceName, secondRelSourceFile);
-    EXPECT_EQ(jobDetails[1].m_jobEntry.m_databaseSourceName, relSourceFileName);
+    EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile);
+    EXPECT_EQ(jobDetails[1].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), sourceFileName);
     EXPECT_EQ(jobDetails[1].m_jobDependencyList.size(), 1); // there should only be one job dependency
-    EXPECT_EQ(jobDetails[1].m_jobDependencyList[0].m_jobDependency.m_sourceFile.m_sourceFileDependencyPath, secondRelSourceFile); // there should only be one job dependency
+    EXPECT_STREQ(jobDetails[1].m_jobDependencyList[0].m_jobDependency.m_sourceFile.m_sourceFileDependencyPath.c_str(), secondSourceFile.toUtf8().constData()); // there should only be one job dependency
 }
 
 TEST_F(AssetProcessorManagerTest, SourceFile_With_NonASCII_Characters_Fail_Job_OK)
@@ -3469,8 +3490,7 @@ TEST_F(AssetProcessorManagerTest, SourceFile_With_NonASCII_Characters_Fail_Job_O
 
     ASSERT_TRUE(BlockUntilIdle(5000));
     EXPECT_EQ(failedjobDetails.m_autoFail, true);
-    QDir dir(failedjobDetails.m_jobEntry.m_watchFolderPath);
-    EXPECT_EQ(dir.absoluteFilePath(failedjobDetails.m_jobEntry.m_pathRelativeToWatchFolder), absPath);
+    EXPECT_EQ(failedjobDetails.m_jobEntry.GetAbsoluteSourcePath(), absPath);
 
     // folder delete notification
     folderPathDir.removeRecursively();
@@ -3506,7 +3526,7 @@ TEST_F(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint)
 
     for(const auto& processResult : processResults)
     {
-        AZStd::string file = (processResult.m_jobEntry.m_databaseSourceName + ".arc1").toUtf8().constData();
+        AZStd::string file = (processResult.m_jobEntry.m_sourceAssetReference.RelativePath().Native() + ".arc1");
 
         // Create the file on disk
         ASSERT_TRUE(UnitTestUtils::CreateDummyFile((processResult.m_cachePath / file).AsPosix().c_str(), "products."));
@@ -3764,8 +3784,7 @@ TEST_F(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_WildcardM
     // construct the dummy job to feed to the database updater function:
     AZ::Uuid wildcardTestUuid = AssetUtilities::CreateSafeSourceUUIDFromName("wildcardTest.txt");
     AssetProcessorManager::JobToProcessEntry job;
-    job.m_sourceFileInfo.m_databasePath = "wildcardTest.txt";
-    job.m_sourceFileInfo.m_pathRelativeToScanFolder = "wildcardTest.txt";
+    job.m_sourceFileInfo.m_sourceAssetReference = AssetProcessor::SourceAssetReference(absPath);
     job.m_sourceFileInfo.m_scanFolder = scanFolder;
     job.m_sourceFileInfo.m_uuid = wildcardTestUuid;
 
@@ -4217,6 +4236,8 @@ TEST_F(ChainJobDependencyTest, TestChainDependency_Multi)
         capturedDetails.clear();
     }
 
+    QDir tempPath(m_assetRootDir.path());
+
     // Run through the dependencies in reverse order
     // Each one should trigger a job for every file in front of it
     // Ex: 3 triggers -> 2 -> 1 -> 0
@@ -4229,13 +4250,14 @@ TEST_F(ChainJobDependencyTest, TestChainDependency_Multi)
 
         if (i > 0)
         {
-            ASSERT_EQ(capturedDetails[0].m_jobDependencyList[0].m_jobDependency.m_sourceFile.m_sourceFileDependencyPath, AZStd::string::format("%d.txt", i - 1));
+            QString absPath(tempPath.absoluteFilePath(AZStd::string::format("subfolder1/%d.txt", i - 1).c_str()));
+            ASSERT_EQ(capturedDetails[0].m_jobDependencyList[0].m_jobDependency.m_sourceFile.m_sourceFileDependencyPath, absPath.toUtf8().constData());
 
             capturedDetails.clear();
         }
     }
 
-    // Wait for the file compiled event and trigger OnddedToCatalog with a delay, this is what causes rccontroller to process out of order
+    // Wait for the file compiled event and trigger OnAddedToCatalog with a delay, this is what causes rccontroller to process out of order
     AZStd::vector<JobEntry> finishedJobs;
     QObject::connect(m_data->m_rcController.get(), &RCController::FileCompiled, [this, &finishedJobs](JobEntry entry, AssetBuilderSDK::ProcessJobResponse response)
         {
@@ -4267,7 +4289,7 @@ TEST_F(ChainJobDependencyTest, TestChainDependency_Multi)
     // Test that the jobs completed in the correct order (captureDetails has the correct ordering)
     for(int i = 0; i < capturedDetails.size(); ++i)
     {
-        ASSERT_STREQ(capturedDetails[i].m_jobEntry.m_databaseSourceName.toUtf8().constData(), finishedJobs[i].m_databaseSourceName.toUtf8().constData());
+        ASSERT_EQ(capturedDetails[i].m_jobEntry.m_sourceAssetReference, finishedJobs[i].m_sourceAssetReference);
     }
 }
 
@@ -4299,8 +4321,7 @@ TEST_F(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase)
     UnitTestUtils::CreateDummyFile(absPath, "dummy");
 
     JobEntry entry;
-    entry.m_watchFolderPath = watchFolder;
-    entry.m_databaseSourceName = entry.m_pathRelativeToWatchFolder = relFileName;
+    entry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(watchFolder,relFileName);
     entry.m_jobKey = "txt";
     entry.m_platformInfo = { "pc", {"host", "renderer", "desktop"} };
     entry.m_jobRunKey = 1;
@@ -4333,7 +4354,7 @@ TEST_F(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase)
     m_assetProcessorManager->AssessAddedFile(m_assetRootDir.absoluteFilePath(metadataFile));
 
     ASSERT_TRUE(BlockUntilIdle(5000));
-    ASSERT_EQ(jobDetails.m_jobEntry.m_pathRelativeToWatchFolder, relFileName);
+    ASSERT_EQ(jobDetails.m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), absPath);
 }
 
 AZStd::vector<AZStd::string> QStringListToVector(const QStringList& qstringList)

+ 136 - 19
Code/Tools/AssetProcessor/native/tests/assetmanager/IntermediateAssetTests.cpp

@@ -222,7 +222,7 @@ namespace UnitTests
             m_jobDetailsList.begin(), m_jobDetailsList.end(),
             [](const AssetProcessor::JobDetails& a, const AssetProcessor::JobDetails& b) -> bool
             {
-                return a.m_jobEntry.m_databaseSourceName.compare(b.m_jobEntry.m_databaseSourceName) < 0;
+                return a.m_jobEntry.m_sourceAssetReference < b.m_jobEntry.m_sourceAssetReference;
             });
 
         ProcessJob(*m_rc, m_jobDetailsList[jobToRun]);
@@ -272,6 +272,12 @@ namespace UnitTests
 
             ProcessSingleStep(expectedJobCount, expectedFileCount, jobToRun, true);
 
+            if(expectAutofail)
+            {
+                ASSERT_GT(m_jobDetailsList.size(), 0);
+                EXPECT_TRUE(m_jobDetailsList[0].m_autoFail);
+            }
+
             if (i < endStage)
             {
                 auto expectedIntermediatePath = intermediatesDir / AZStd::string::format("test.stage%d", i + 1);
@@ -337,12 +343,12 @@ namespace UnitTests
 
         ProcessFileMultiStage(3, false);
 
-        EXPECT_EQ(m_jobDetailsList.size(), 3);
+        ASSERT_EQ(m_jobDetailsList.size(), 3);
         EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
         EXPECT_TRUE(m_jobDetailsList[2].m_autoFail);
 
-        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_databaseSourceName, "test.stage3");
-        EXPECT_EQ(m_jobDetailsList[2].m_jobEntry.m_databaseSourceName, "test.stage1");
+        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage3");
+        EXPECT_EQ(m_jobDetailsList[2].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
     }
 
     TEST_F(IntermediateAssetTests, AALoop_CausesFailure)
@@ -354,12 +360,12 @@ namespace UnitTests
 
         ProcessFileMultiStage(2, false);
 
-        EXPECT_EQ(m_jobDetailsList.size(), 3);
+        ASSERT_EQ(m_jobDetailsList.size(), 3);
         EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
         EXPECT_TRUE(m_jobDetailsList[2].m_autoFail);
 
-        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_databaseSourceName, "test.stage2");
-        EXPECT_EQ(m_jobDetailsList[2].m_jobEntry.m_databaseSourceName, "test.stage1");
+        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage2");
+        EXPECT_EQ(m_jobDetailsList[2].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
     }
 
     TEST_F(IntermediateAssetTests, SelfLoop_CausesFailure)
@@ -370,10 +376,10 @@ namespace UnitTests
 
         ProcessFileMultiStage(1, false);
 
-        EXPECT_EQ(m_jobDetailsList.size(), 2);
+        ASSERT_EQ(m_jobDetailsList.size(), 2);
         EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
 
-        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_databaseSourceName, "test.stage1");
+        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
 
         m_assetProcessorManager->AssessDeletedFile(MakePath("test.stage1", true).c_str());
         RunFile(0);
@@ -393,7 +399,7 @@ namespace UnitTests
 
         auto expectedProduct = AZ::IO::Path(m_databaseLocationListener.GetAssetRootDir()) / "Cache" / "pc" / "test.stage1";
 
-        EXPECT_EQ(m_jobDetailsList.size(), 1);
+        ASSERT_EQ(m_jobDetailsList.size(), 1);
         EXPECT_TRUE(AZ::IO::SystemFile::Exists(expectedProduct.c_str())) << expectedProduct.c_str();
     }
 
@@ -464,13 +470,87 @@ namespace UnitTests
         DeleteIntermediateTest(MakePath("test.stage4", false).c_str());
     }
 
-    TEST_F(IntermediateAssetTests, Override_NormalFileProcessedFirst_CausesFailure)
+    TEST_F(IntermediateAssetTests, Override_NormalFileProcessedFirst_NormalFileOutputsIntermediate_FirstStageCausesFailure)
     {
+        // Test that a file outputting an intermediate that conflicts with an existing source which outputs an intermediate fails
         using namespace AssetBuilderSDK;
 
         CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
         CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
         CreateBuilder("stage3", "*.stage3", "stage4", false, ProductOutputFlags::ProductAsset);
+        constexpr int NumberOfStages = 3;
+
+        // Make and process a source file which matches an intermediate output name we will create later
+        AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
+        AZStd::string testFilename = "test.stage2";
+        AZStd::string testFilePath = (scanFolderDir / testFilename).AsPosix();
+
+        UnitTestUtils::CreateDummyFile(testFilePath.c_str(), "unit test file");
+
+        ProcessFileMultiStage(NumberOfStages, true, testFilePath.c_str(), 2);
+
+        // Now process another file which produces intermediates that conflict with the existing source file above
+        // Only go to stage 1 since we're expecting a failure at that point
+        ProcessFileMultiStage(1, false);
+
+        // Expect 2 jobs for the same file, 1 is the job that processed successfully and detected the problem, the 2nd is an autofail job
+        // used to actually mark the file as failed
+        ASSERT_EQ(m_jobDetailsList.size(), 2);
+
+        EXPECT_FALSE(m_jobDetailsList[0].m_autoFail);
+        EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
+
+        EXPECT_EQ(m_jobDetailsList[0].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
+        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
+    }
+
+    TEST_F(IntermediateAssetTests, Override_NormalFileProcessedFirst_NormalFileOutputsIntermediate_SecondStageCausesFailure)
+    {
+        // Test that an intermediate outputting an intermediate that conflicts with an existing source which outputs an intermediate fails
+        using namespace AssetBuilderSDK;
+
+        CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
+        CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
+        CreateBuilder("stage3", "*.stage3", "stage4", true, ProductOutputFlags::IntermediateAsset);
+        CreateBuilder("stage4", "*.stage4", "stage5", false, ProductOutputFlags::ProductAsset);
+        constexpr int NumberOfStages = 4;
+
+        // Make and process a source file which matches an intermediate output name we will create later
+        AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
+        AZStd::string testFilename = "test.stage3";
+        AZStd::string testFilePath = (scanFolderDir / testFilename).AsPosix();
+
+        UnitTestUtils::CreateDummyFile(testFilePath.c_str(), "unit test file");
+
+        ProcessFileMultiStage(NumberOfStages, true, testFilePath.c_str(), 3);
+
+        // Now process another file which produces intermediates that conflict with the existing source file above
+        // Only go to stage 2 since we're expecting a failure at that point
+        ProcessFileMultiStage(2, false);
+
+        // Expect 3 jobs:
+        // 1 is the job for stage2 that was processing and detected the failure
+        // 1 is the autofail job that was created to autofail stage2
+        // 1 is the autofail job for the top level source (stage1)
+        ASSERT_EQ(m_jobDetailsList.size(), 3);
+
+        EXPECT_FALSE(m_jobDetailsList[0].m_autoFail);
+        EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
+        EXPECT_TRUE(m_jobDetailsList[2].m_autoFail);
+
+        EXPECT_EQ(m_jobDetailsList[0].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage2");
+        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage2");
+        EXPECT_EQ(m_jobDetailsList[2].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
+    }
+
+    TEST_F(IntermediateAssetTests, Override_NormalFileProcessedFirst_NormalFileOutputsProduct_CausesFailure)
+    {
+        // Test that a source outputting an intermediate that conflicts with an existing source which outputs a product fails
+        using namespace AssetBuilderSDK;
+
+        CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
+        CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
+        constexpr int NumberOfStages = 2;
 
         // Make and process a source file which matches an intermediate output name we will create later
         AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
@@ -479,16 +559,21 @@ namespace UnitTests
 
         UnitTestUtils::CreateDummyFile(testFilePath.c_str(), "unit test file");
 
-        ProcessFileMultiStage(3, true, testFilePath.c_str(), 2);
+        ProcessFileMultiStage(NumberOfStages, true, testFilePath.c_str(), 2);
 
         // Now process another file which produces intermediates that conflict with the existing source file above
         // Only go to stage 1 since we're expecting a failure at that point
         ProcessFileMultiStage(1, false);
 
-        EXPECT_EQ(m_jobDetailsList.size(), 2);
+        // Expect 2 jobs for the same file, 1 is the job that processed successfully and detected the problem, the 2nd is an autofail job
+        // used to actually mark the file as failed
+        ASSERT_EQ(m_jobDetailsList.size(), 2);
+
+        EXPECT_FALSE(m_jobDetailsList[0].m_autoFail);
         EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
 
-        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_databaseSourceName, "test.stage1");
+        EXPECT_EQ(m_jobDetailsList[0].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
+        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
     }
 
     TEST_F(IntermediateAssetTests, DeleteFileInIntermediateFolder_CorrectlyDeletesOneFile)
@@ -577,16 +662,17 @@ namespace UnitTests
         ASSERT_FALSE(AZ::IO::SystemFile::Exists(intermediateFile.c_str()));
     }
 
-    TEST_F(IntermediateAssetTests, Override_IntermediateFileProcessedFirst_CausesFailure)
+    TEST_F(IntermediateAssetTests, Override_IntermediateFileProcessedFirst_NormalFileOutputsIntermediate_CausesFailure)
     {
         using namespace AssetBuilderSDK;
 
         CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
         CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
         CreateBuilder("stage3", "*.stage3", "stage4", false, ProductOutputFlags::ProductAsset);
+        constexpr int NumberOfStages = 3;
 
         // Process a file from stage1 -> stage4, this will create several intermediates
-        ProcessFileMultiStage(3, true);
+        ProcessFileMultiStage(NumberOfStages, true);
 
         // Now make a source file which is the same name as an existing intermediate and process it
         AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
@@ -595,10 +681,41 @@ namespace UnitTests
 
         UnitTestUtils::CreateDummyFile(testFilePath.c_str(), "unit test file");
 
-        ProcessFileMultiStage(3, true, testFilePath.c_str(), 2, true);
+        ProcessFileMultiStage(NumberOfStages, true, testFilePath.c_str(), 2, true);
+
+        ASSERT_EQ(m_jobDetailsList.size(), 1);
 
-        EXPECT_EQ(m_jobDetailsList.size(), 1);
         EXPECT_FALSE(m_jobDetailsList[0].m_autoFail);
+        EXPECT_EQ(m_jobDetailsList[0].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage3");
+    }
+
+    TEST_F(IntermediateAssetTests, Override_IntermediateFileProcessedFirst_NormalFileOutputsProduct_CausesFailure)
+    {
+        using namespace AssetBuilderSDK;
+
+        CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
+        CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
+        constexpr int NumberOfStages = 2;
+
+        // Process a file from stage1 -> stage4, this will create several intermediates
+        ProcessFileMultiStage(NumberOfStages, true);
+
+        // Now make a source file which is the same name as an existing intermediate and process it
+        AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
+        AZStd::string testFilename = "test.stage2";
+        AZStd::string testFilePath = (scanFolderDir / testFilename).AsPosix();
+
+        UnitTestUtils::CreateDummyFile(testFilePath.c_str(), "unit test file");
+
+        ProcessFileMultiStage(NumberOfStages, true, testFilePath.c_str(), 2, true);
+
+        ASSERT_EQ(m_jobDetailsList.size(), 2);
+
+        EXPECT_TRUE(m_jobDetailsList[0].m_autoFail);
+        EXPECT_FALSE(m_jobDetailsList[1].m_autoFail);
+
+        EXPECT_EQ(m_jobDetailsList[0].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
+        EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage2");
     }
 
     TEST_F(IntermediateAssetTests, DuplicateOutputs_CausesFailure)
@@ -628,7 +745,7 @@ namespace UnitTests
 
         m_assetProcessorManager->AssetProcessed(m_processedJobEntry, m_processJobResponse);
 
-        EXPECT_EQ(m_jobDetailsList.size(), 1);
+        ASSERT_EQ(m_jobDetailsList.size(), 1);
         EXPECT_TRUE(m_jobDetailsList[0].m_autoFail);
     }
 

+ 50 - 63
Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.cpp

@@ -31,7 +31,7 @@ namespace UnitTests
 
         QObject::connect(
             m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted,
-            [this](QString file)
+            [this](const SourceAssetReference& file)
             {
                 m_data->m_deletedSources.push_back(file);
             });
@@ -58,18 +58,13 @@ namespace UnitTests
 
         // Create the test file
         const auto& scanFolder = m_config->GetScanFolderAt(1);
-        m_data->m_relativePathFromWatchFolder[0] = "modtimeTestFile.txt";
-        m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[0]));
+        m_data->m_sourcePaths.push_back(AssetProcessor::SourceAssetReference(scanFolder.ScanPath(), "modtimeTestFile.txt"));
+        m_data->m_sourcePaths.push_back(AssetProcessor::SourceAssetReference(scanFolder.ScanPath(), "modtimeTestDependency.txt"));
+        m_data->m_sourcePaths.push_back(AssetProcessor::SourceAssetReference(scanFolder.ScanPath(), "modtimeTestDependency.txt.assetinfo"));
 
-        m_data->m_relativePathFromWatchFolder[1] = "modtimeTestDependency.txt";
-        m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[1]));
-
-        m_data->m_relativePathFromWatchFolder[2] = "modtimeTestDependency.txt.assetinfo";
-        m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[2]));
-
-        for (const auto& path : m_data->m_absolutePath)
+        for (const auto& path : m_data->m_sourcePaths)
         {
-            ASSERT_TRUE(UnitTestUtils::CreateDummyFile(path, ""));
+            ASSERT_TRUE(UnitTestUtils::CreateDummyFile(path.AbsolutePath().c_str(), ""));
         }
 
         // We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
@@ -78,7 +73,7 @@ namespace UnitTests
         m_data->m_mockBuilderInfoHandler.CreateBuilderDesc(
             "test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}",
             { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) },
-            MockMultiBuilderInfoHandler::AssetBuilderExtraInfo{ "", m_data->m_absolutePath[1].toUtf8().data(), "", "", {} });
+            MockMultiBuilderInfoHandler::AssetBuilderExtraInfo{ "", m_data->m_sourcePaths[1].AbsolutePath().c_str(), "", "", {} });
         m_data->m_mockBuilderInfoHandler.BusConnect();
 
         ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder));
@@ -90,7 +85,7 @@ namespace UnitTests
             AssetDatabaseConnection connection;
             ASSERT_TRUE(connection.OpenDatabase());
             AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
-            fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[0].toUtf8().data();
+            fileEntry.m_fileName = m_data->m_sourcePaths[0].RelativePath().Native();
             fileEntry.m_modTime = 0;
             fileEntry.m_isFolder = false;
             fileEntry.m_scanFolderPK = scanFolder.ScanFolderID();
@@ -100,12 +95,12 @@ namespace UnitTests
             ASSERT_FALSE(entryAlreadyExists);
 
             fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry
-            fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[1].toUtf8().data();
+            fileEntry.m_fileName = m_data->m_sourcePaths[1].RelativePath().Native();
             ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
             ASSERT_FALSE(entryAlreadyExists);
 
             fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry
-            fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[2].toUtf8().data();
+            fileEntry.m_fileName = m_data->m_sourcePaths[2].RelativePath().Native();
             ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
             ASSERT_FALSE(entryAlreadyExists);
         }
@@ -139,13 +134,9 @@ namespace UnitTests
 
         for (const auto& processResult : m_data->m_processResults)
         {
-            AZStd::string file = (processResult.m_jobEntry.m_databaseSourceName.toLower() + ".arc1").toUtf8().constData();
-            m_data->m_productPaths.emplace(
-                QDir(processResult.m_jobEntry.m_watchFolderPath)
-                    .absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName)
-                    .toUtf8()
-                    .constData(),
-                (processResult.m_cachePath / file).c_str());
+            AZStd::string file = QString((processResult.m_jobEntry.m_sourceAssetReference.RelativePath().Native() + ".arc1").c_str()).toLower().toUtf8().constData();
+
+            m_data->m_productPaths.emplace(processResult.m_jobEntry.GetAbsoluteSourcePath().toUtf8().constData(), (processResult.m_cachePath / file).c_str());
 
             // Create the file on disk
             ASSERT_TRUE(UnitTestUtils::CreateDummyFile((processResult.m_cachePath / file).AsPosix().c_str(), "products."));
@@ -182,12 +173,12 @@ namespace UnitTests
     {
         QSet<AssetFileInfo> filePaths;
 
-        for (const auto& path : m_data->m_absolutePath)
+        for (const auto& path : m_data->m_sourcePaths)
         {
-            QFileInfo fileInfo(path);
+            QFileInfo fileInfo(path.AbsolutePath().c_str());
             auto modtime = fileInfo.lastModified();
             AZ::u64 fileSize = fileInfo.size();
-            filePaths.insert(AssetFileInfo(path, modtime, fileSize, m_config->GetScanFolderForFile(path), false));
+            filePaths.insert(AssetFileInfo(path.AbsolutePath().c_str(), modtime, fileSize, m_config->GetScanFolderForFile(path.AbsolutePath().c_str()), false));
         }
 
         return filePaths;
@@ -295,19 +286,17 @@ namespace UnitTests
 
         uint64_t timestamp = 1594923423;
 
-        QString databaseName, scanfolderName;
-        m_config->ConvertToRelativePath(m_data->m_absolutePath[1], databaseName, scanfolderName);
-        auto* scanFolder = m_config->GetScanFolderForFile(m_data->m_absolutePath[1]);
+        const auto& sourcePath = m_data->m_sourcePaths[1];
 
         AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
 
-        m_assetProcessorManager->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry);
+        m_assetProcessorManager->m_stateData->GetFileByFileNameAndScanFolderId(sourcePath.RelativePath().c_str(), sourcePath.ScanFolderId(), fileEntry);
 
         ASSERT_NE(fileEntry.m_modTime, timestamp);
         uint64_t existingTimestamp = fileEntry.m_modTime;
 
         // Modify the timestamp on just one file
-        AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp);
+        AzToolsFramework::ToolsFileUtils::SetModificationTime(sourcePath.AbsolutePath().c_str(), timestamp);
 
         AssetUtilities::SetUseFileHashOverride(true, true);
 
@@ -316,7 +305,7 @@ namespace UnitTests
 
         ExpectNoWork();
 
-        m_assetProcessorManager->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry);
+        m_assetProcessorManager->m_stateData->GetFileByFileNameAndScanFolderId(sourcePath.RelativePath().c_str(), sourcePath.ScanFolderId(), fileEntry);
 
         // The timestamp should be updated even though nothing processed
         ASSERT_NE(fileEntry.m_modTime, existingTimestamp);
@@ -332,7 +321,7 @@ namespace UnitTests
         uint64_t timestamp = 1594923423;
 
         // Modify the timestamp on just one file
-        AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp);
+        AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_sourcePaths[1].AbsolutePath().c_str(), timestamp);
 
         AssetUtilities::SetUseFileHashOverride(true, false);
 
@@ -346,7 +335,7 @@ namespace UnitTests
     {
         using namespace AzToolsFramework::AssetSystem;
 
-        SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
+        SetFileContents(m_data->m_sourcePaths[1].AbsolutePath().c_str(), "hello world");
 
         AssetUtilities::SetUseFileHashOverride(true, true);
 
@@ -361,8 +350,8 @@ namespace UnitTests
     TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain)
     {
         using namespace AzToolsFramework::AssetSystem;
-        auto theFile = m_data->m_absolutePath[1].toUtf8();
-        const char* theFileString = theFile.constData();
+        auto theFile = m_data->m_sourcePaths[1].AbsolutePath().Native();
+        const char* theFileString = theFile.c_str();
 
         SetFileContents(theFileString, "hello world");
 
@@ -393,7 +382,7 @@ namespace UnitTests
     {
         using namespace AzToolsFramework::AssetSystem;
 
-        SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
+        SetFileContents(m_data->m_sourcePaths[1].AbsolutePath().c_str(), "hello world");
 
         AssetUtilities::SetUseFileHashOverride(true, true);
 
@@ -410,7 +399,7 @@ namespace UnitTests
         m_data->m_deletedSources.clear();
 
         // Make file 0 have the same contents as file 1
-        SetFileContents(m_data->m_absolutePath[0].toUtf8().constData(), "hello world");
+        SetFileContents(m_data->m_sourcePaths[0].AbsolutePath().c_str(), "hello world");
 
         filePaths = BuildFileSet();
         SimulateAssetScanner(filePaths);
@@ -422,7 +411,7 @@ namespace UnitTests
     {
         using namespace AzToolsFramework::AssetSystem;
 
-        SetFileContents(m_data->m_absolutePath[2].toUtf8().constData(), "hello world");
+        SetFileContents(m_data->m_sourcePaths[2].AbsolutePath().c_str(), "hello world");
 
         AssetUtilities::SetUseFileHashOverride(true, true);
 
@@ -440,14 +429,14 @@ namespace UnitTests
 
         AssetUtilities::SetUseFileHashOverride(true, true);
 
-        ASSERT_TRUE(QFile::remove(m_data->m_absolutePath[0]));
+        ASSERT_TRUE(QFile::remove(m_data->m_sourcePaths[0].AbsolutePath().c_str()));
 
         // Feed in ONLY one file (the one we didn't delete)
         QSet<AssetFileInfo> filePaths;
-        QFileInfo fileInfo(m_data->m_absolutePath[1]);
+        QFileInfo fileInfo(m_data->m_sourcePaths[1].AbsolutePath().c_str());
         auto modtime = fileInfo.lastModified();
         AZ::u64 fileSize = fileInfo.size();
-        filePaths.insert(AssetFileInfo(m_data->m_absolutePath[1], modtime, fileSize, &m_config->GetScanFolderAt(0), false));
+        filePaths.insert(AssetFileInfo(m_data->m_sourcePaths[1].AbsolutePath().c_str(), modtime, fileSize, &m_config->GetScanFolderAt(0), false));
 
         SimulateAssetScanner(filePaths);
 
@@ -457,18 +446,18 @@ namespace UnitTests
         do
         {
             QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
-        } while (m_data->m_deletedSources.size() < m_data->m_relativePathFromWatchFolder[0].size() && timer.elapsed() < 5000);
+        } while (m_data->m_deletedSources.empty() && timer.elapsed() < 5000);
 
         ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0);
         ASSERT_EQ(m_data->m_processResults.size(), 0);
-        ASSERT_THAT(m_data->m_deletedSources, testing::ElementsAre(m_data->m_relativePathFromWatchFolder[0]));
+        ASSERT_THAT(m_data->m_deletedSources, testing::ElementsAre(m_data->m_sourcePaths[0]));
     }
 
     TEST_F(ModtimeScanningTest, ReprocessRequest_FileNotModified_FileProcessed)
     {
         using namespace AzToolsFramework::AssetSystem;
 
-        m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]);
+        m_assetProcessorManager->RequestReprocess(m_data->m_sourcePaths[0].AbsolutePath().c_str());
 
         ASSERT_TRUE(BlockUntilIdle(5000));
 
@@ -485,16 +474,16 @@ namespace UnitTests
         newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
         newEntry1.m_builderGuid = AZ::Uuid::CreateRandom();
         newEntry1.m_sourceGuid = AZ::Uuid{ "{C0BD819A-F84E-4A56-A6A5-917AE3ECDE53}" };
-        newEntry1.m_dependsOnSource = PathOrUuid(m_data->m_absolutePath[1].toUtf8().constData());
+        newEntry1.m_dependsOnSource = PathOrUuid(m_data->m_sourcePaths[1].AbsolutePath().c_str());
         newEntry1.m_typeOfDependency = SourceFileDependencyEntry::DEP_SourceToSource;
 
-        m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]);
+        m_assetProcessorManager->RequestReprocess(m_data->m_sourcePaths[0].AbsolutePath().c_str());
         ASSERT_TRUE(BlockUntilIdle(5000));
 
         ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1);
         ASSERT_EQ(m_data->m_processResults.size(), 1);
 
-        m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[1]);
+        m_assetProcessorManager->RequestReprocess(m_data->m_sourcePaths[1].AbsolutePath().c_str());
         ASSERT_TRUE(BlockUntilIdle(5000));
 
         ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 3);
@@ -519,8 +508,8 @@ namespace UnitTests
      TEST_F(ModtimeScanningTest, AssetProcessorIsRestartedBeforeDependencyIsProcessed_DependencyIsProcessedOnStart)
     {
         using namespace AzToolsFramework::AssetSystem;
-        auto theFile = m_data->m_absolutePath[1].toUtf8();
-        const char* theFileString = theFile.constData();
+        auto theFile = m_data->m_sourcePaths[1].AbsolutePath();
+        const char* theFileString = theFile.c_str();
 
         SetFileContents(theFileString, "hello world");
 
@@ -542,17 +531,13 @@ namespace UnitTests
                 m_data->m_processResults.begin(), m_data->m_processResults.end(),
                 [](decltype(m_data->m_processResults[0])& left, decltype(left)& right)
                 {
-                    return left.m_jobEntry.m_databaseSourceName < right.m_jobEntry.m_databaseSourceName;
+                    return left.m_jobEntry.m_sourceAssetReference < right.m_jobEntry.m_sourceAssetReference;
                 });
 
             const auto& processResult = m_data->m_processResults[0];
-            AZStd::string file = (processResult.m_jobEntry.m_databaseSourceName.toLower() + ".arc1").toUtf8().constData();
-            m_data->m_productPaths.emplace(
-                QDir(processResult.m_jobEntry.m_watchFolderPath)
-                    .absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName)
-                    .toUtf8()
-                    .constData(),
-                (processResult.m_cachePath / file).c_str());
+            AZStd::string file = QString((processResult.m_jobEntry.m_sourceAssetReference.RelativePath().Native() + ".arc1").c_str()).toLower().toUtf8().constData();
+
+            m_data->m_productPaths.emplace(processResult.m_jobEntry.GetAbsoluteSourcePath().toUtf8().constData(), (processResult.m_cachePath / file).c_str());
 
             // Create the file on disk
             ASSERT_TRUE(UnitTestUtils::CreateDummyFile((processResult.m_cachePath / file).AsPosix().c_str(), "products."));
@@ -606,6 +591,8 @@ namespace UnitTests
 
         SetUpAssetProcessorManager();
 
+        m_data->m_sourcePaths.clear();
+
         auto createFileAndAddToDatabaseFunc = [this](const AssetProcessor::ScanFolderInfo* scanFolder, QString file)
         {
             using namespace AzToolsFramework::AssetDatabase;
@@ -614,7 +601,7 @@ namespace UnitTests
             QString absPath(QDir(watchFolderPath).absoluteFilePath(file));
             UnitTestUtils::CreateDummyFile(absPath);
 
-            m_data->m_absolutePath.push_back(absPath);
+            m_data->m_sourcePaths.push_back(AssetProcessor::SourceAssetReference(absPath));
 
             AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
             fileEntry.m_fileName = file.toUtf8().constData();
@@ -686,14 +673,14 @@ namespace UnitTests
         m_assetProcessorManager->AssessDeletedFile(absPath);
         ASSERT_TRUE(BlockUntilIdle(5000));
 
-        ASSERT_THAT(m_data->m_deletedSources, testing::UnorderedElementsAre("textures/a.txt"));
+        ASSERT_THAT(m_data->m_deletedSources, testing::UnorderedElementsAre(AssetProcessor::SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1"), "textures/a.txt")));
         ASSERT_THAT(deletedFolders, testing::UnorderedElementsAre(absPath.toUtf8().constData()));
     }
 
     TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails)
     {
-        auto theFile = m_data->m_absolutePath[1].toUtf8();
-        const char* theFileString = theFile.constData();
+        auto theFile = m_data->m_sourcePaths[1].AbsolutePath();
+        const char* theFileString = theFile.c_str();
         auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
 
         {
@@ -730,8 +717,8 @@ namespace UnitTests
         // when one of its product assets is locked temporarily
         // We'll lock the file by holding it open
 
-        auto theFile = m_data->m_absolutePath[1].toUtf8();
-        const char* theFileString = theFile.constData();
+        auto theFile = m_data->m_sourcePaths[1].AbsolutePath();
+        const char* theFileString = theFile.c_str();
         auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
 
         {

+ 2 - 3
Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.h

@@ -28,11 +28,10 @@ namespace UnitTests
 
         struct StaticData
         {
-            QString m_relativePathFromWatchFolder[3];
-            AZStd::vector<QString> m_absolutePath;
+            AZStd::vector<AssetProcessor::SourceAssetReference> m_sourcePaths;
             AZStd::vector<AssetProcessor::JobDetails> m_processResults;
             AZStd::unordered_multimap<AZStd::string, QString> m_productPaths;
-            AZStd::vector<QString> m_deletedSources;
+            AZStd::vector<AssetProcessor::SourceAssetReference> m_deletedSources;
             AZStd::shared_ptr<AssetProcessor::InternalMockBuilder> m_builderTxtBuilder;
             MockMultiBuilderInfoHandler m_mockBuilderInfoHandler;
         };

+ 10 - 13
Code/Tools/AssetProcessor/native/tests/resourcecompiler/RCControllerTest.cpp

@@ -7,17 +7,14 @@
  */
 
 #include "RCControllerTest.h"
-
-
 #include "native/resourcecompiler/rccontroller.h"
-#include "AzCore/std/parallel/binary_semaphore.h"
 
 TEST_F(RCcontrollerTest, CompileGroupCreatedWithUnknownStatusForFailedJobs)
 {
-    //Strategy Add a failed job to the job queue list and than ask the rc controller to request compile, it should emit unknown status  
+    //Strategy Add a failed job to the job queue list and than ask the rc controller to request compile, it should emit unknown status
     using namespace AssetProcessor;
-    // we have to initialize this to something other than Assetstatus_Unknown here because later on we will be testing the value of assetstatus  
-    AzFramework::AssetSystem::AssetStatus assetStatus = AzFramework::AssetSystem::AssetStatus_Failed; 
+    // we have to initialize this to something other than Assetstatus_Unknown here because later on we will be testing the value of assetstatus
+    AzFramework::AssetSystem::AssetStatus assetStatus = AzFramework::AssetSystem::AssetStatus_Failed;
     RCController rcController;
     QObject::connect(&rcController, &RCController::CompileGroupCreated,
         [&assetStatus]([[maybe_unused]] AssetProcessor::NetworkRequestID groupID, AzFramework::AssetSystem::AssetStatus status)
@@ -28,7 +25,7 @@ TEST_F(RCcontrollerTest, CompileGroupCreatedWithUnknownStatusForFailedJobs)
     RCJobListModel* rcJobListModel = rcController.GetQueueModel();
     RCJob* job = new RCJob(rcJobListModel);
     AssetProcessor::JobDetails jobDetails;
-    jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/failed.dds";
+    jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somepath/failed.dds");
     jobDetails.m_jobEntry.m_platformInfo = { "pc", {"desktop", "renderer"} };
     jobDetails.m_jobEntry.m_jobKey = "Compile Stuff";
     job->SetState(RCJob::failed);
@@ -68,7 +65,7 @@ public:
             RCJob* job = new RCJob(m_rcJobListModel);
             AssetProcessor::JobDetails jobDetails;
             jobDetails.m_jobEntry.m_computedFingerprint = 1;
-            jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/failed.dds";
+            jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somepath/failed.dds");
             jobDetails.m_jobEntry.m_platformInfo = { "ios",{ "mobile", "renderer" } };
             jobDetails.m_jobEntry.m_jobRunKey = 1;
             jobDetails.m_jobEntry.m_jobKey = "tiff";
@@ -82,7 +79,7 @@ public:
             // note that Init() is a move operation.  we cannot reuse jobDetails.
             AssetProcessor::JobDetails jobDetails;
             jobDetails.m_jobEntry.m_computedFingerprint = 1;
-            jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/failed.dds";
+            jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somepath/failed.dds");
             jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
             jobDetails.m_jobEntry.m_jobRunKey = 2;
             jobDetails.m_jobEntry.m_jobKey = "tiff";
@@ -112,7 +109,7 @@ TEST_F(RCcontrollerTest_Cancellation, JobSubmitted_SameFingerprint_DoesNotCancel
     {
         AssetProcessor::JobDetails jobDetails;
         jobDetails.m_jobEntry.m_computedFingerprint = 1; // same as above in SetUp
-        jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/failed.dds";
+        jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somepath/failed.dds");
         jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
         jobDetails.m_jobEntry.m_jobKey = "tiff";
         jobDetails.m_jobEntry.m_jobRunKey = 3;
@@ -133,7 +130,7 @@ TEST_F(RCcontrollerTest_Cancellation, JobSubmitted_DifferentFingerprint_CancelsT
     {
         AssetProcessor::JobDetails jobDetails;
         jobDetails.m_jobEntry.m_computedFingerprint = 2; // different from setup.
-        jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/failed.dds";
+        jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somepath/failed.dds");
         jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
         jobDetails.m_jobEntry.m_jobKey = "tiff";
         jobDetails.m_jobEntry.m_jobRunKey = 3;
@@ -197,7 +194,7 @@ void RCcontrollerTest_Simple::SubmitJob()
     {
         AssetProcessor::JobDetails jobDetails;
         jobDetails.m_jobEntry.m_computedFingerprint = 123;
-        jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/a.dds";
+        jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somepath/a.dds");
         jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
         jobDetails.m_jobEntry.m_jobKey = "tiff";
         jobDetails.m_jobEntry.m_jobRunKey = 3;
@@ -223,7 +220,7 @@ void RCcontrollerTest_Simple::SubmitJob()
 TEST_F(RCcontrollerTest_Simple, DISABLED_SameJobIsCompletedMultipleTimes_CompletesWithoutError)
 {
     using namespace AssetProcessor;
-    
+
     AZStd::vector<JobEntry> jobEntries;
     QObject::connect(m_rcController.get(), &RCController::FileCompiled, [&jobEntries](JobEntry entry, AssetBuilderSDK::ProcessJobResponse response)
     {

+ 2 - 0
Code/Tools/AssetProcessor/native/tests/resourcecompiler/RCControllerTest.h

@@ -12,6 +12,7 @@
 #include <QCoreApplication>
 #include "native/assetprocessor.h"
 #include <AzFramework/Asset/AssetSystemTypes.h>
+#include <native/tests/UnitTestUtilities.h>
 
 class RCcontrollerTest
     : public AssetProcessor::AssetProcessorTest
@@ -35,4 +36,5 @@ private:
     char**      m_argv;
 
     QCoreApplication* m_qApp;
+    UnitTests::MockPathConversion m_mockPathConversion;
 };

+ 29 - 24
Code/Tools/AssetProcessor/native/tests/utilities/JobModelTest.cpp

@@ -14,10 +14,11 @@ TEST_F(JobModelUnitTests, Test_RemoveMiddleJob)
     VerifyModel(); // verify up front for sanity.
 
     AzToolsFramework::AssetSystem::JobInfo jobInfo;
+    jobInfo.m_watchFolder = "c:/test";
     jobInfo.m_sourceFile = "source2.txt";
     jobInfo.m_platform = "platform";
     jobInfo.m_jobKey = "jobKey";
-    AssetProcessor::QueueElementID elementId("source2.txt", "platform", "jobKey");
+    AssetProcessor::QueueElementID elementId(AssetProcessor::SourceAssetReference("c:/test/source2.txt"), "platform", "jobKey");
     auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
     ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
     unsigned int jobIndex = iter.value();
@@ -28,9 +29,9 @@ TEST_F(JobModelUnitTests, Test_RemoveMiddleJob)
     iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
     ASSERT_EQ(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
     AssetProcessor::CachedJobInfo* cachedJobInfo = m_unitTestJobModel->m_cachedJobs[jobIndex];
-    ASSERT_EQ(cachedJobInfo->m_elementId.GetInputAssetName(), QString("source3.txt"));
+    ASSERT_EQ(cachedJobInfo->m_elementId.GetSourceAssetReference().AbsolutePath().Native(), "c:/test/source3.txt");
     // Checking index of last job
-    elementId.SetInputAssetName("source6.txt");
+    elementId.SetSourceAssetReference(AssetProcessor::SourceAssetReference("c:/test/source6.txt"));
     iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
     ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
     jobIndex = iter.value();
@@ -43,10 +44,11 @@ TEST_F(JobModelUnitTests, Test_RemoveFirstJob)
     VerifyModel(); // verify up front for sanity.
 
     AzToolsFramework::AssetSystem::JobInfo jobInfo;
+    jobInfo.m_watchFolder = "c:/test";
     jobInfo.m_sourceFile = "source1.txt";
     jobInfo.m_platform = "platform";
     jobInfo.m_jobKey = "jobKey";
-    AssetProcessor::QueueElementID elementId("source1.txt", "platform", "jobKey");
+    AssetProcessor::QueueElementID elementId(AssetProcessor::SourceAssetReference ("c:/test/source1.txt"), "platform", "jobKey");
     auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
     ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
     unsigned int jobIndex = iter.value();
@@ -57,9 +59,9 @@ TEST_F(JobModelUnitTests, Test_RemoveFirstJob)
     iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
     ASSERT_EQ(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
     AssetProcessor::CachedJobInfo* cachedJobInfo = m_unitTestJobModel->m_cachedJobs[jobIndex];
-    ASSERT_EQ(cachedJobInfo->m_elementId.GetInputAssetName(), QString("source2.txt"));
+    ASSERT_EQ(cachedJobInfo->m_elementId.GetSourceAssetReference().AbsolutePath().Native(), "c:/test/source2.txt");
     // Checking index of last job
-    elementId.SetInputAssetName("source6.txt");
+    elementId.SetSourceAssetReference(AssetProcessor::SourceAssetReference("c:/test/source6.txt"));
     iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
     ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
     jobIndex = iter.value();
@@ -72,10 +74,11 @@ TEST_F(JobModelUnitTests, Test_RemoveLastJob)
     VerifyModel(); // verify up front for sanity.
 
     AzToolsFramework::AssetSystem::JobInfo jobInfo;
+    jobInfo.m_watchFolder = "c:/test";
     jobInfo.m_sourceFile = "source6.txt";
     jobInfo.m_platform = "platform";
     jobInfo.m_jobKey = "jobKey";
-    AssetProcessor::QueueElementID elementId("source6.txt", "platform", "jobKey");
+    AssetProcessor::QueueElementID elementId(AssetProcessor::SourceAssetReference("c:/test/source6.txt"), "platform", "jobKey");
     auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
     ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
     unsigned int jobIndex = iter.value();
@@ -86,9 +89,9 @@ TEST_F(JobModelUnitTests, Test_RemoveLastJob)
     iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
     ASSERT_EQ(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
     AssetProcessor::CachedJobInfo* cachedJobInfo = m_unitTestJobModel->m_cachedJobs[jobIndex - 1];
-    ASSERT_EQ(cachedJobInfo->m_elementId.GetInputAssetName(), QString("source5.txt"));
+    ASSERT_EQ(cachedJobInfo->m_elementId.GetSourceAssetReference().AbsolutePath().Native(), "c:/test/source5.txt");
     // Checking index of first job
-    elementId.SetInputAssetName("source1.txt");
+    elementId.SetSourceAssetReference(AssetProcessor::SourceAssetReference("c:/test/source1.txt"));
     iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
     ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
     jobIndex = iter.value();
@@ -102,7 +105,8 @@ TEST_F(JobModelUnitTests, Test_RemoveAllJobsBySource)
     VerifyModel(); // verify up front for sanity.
 
     AssetProcessor::CachedJobInfo* jobInfo1 = new AssetProcessor::CachedJobInfo();
-    jobInfo1->m_elementId.SetInputAssetName("source3.txt"); // this is the second job for this source - the fixture creates one
+    jobInfo1->m_elementId.SetSourceAssetReference(
+        AssetProcessor::SourceAssetReference("c:/test/source3.txt")); // this is the second job for this source - the fixture creates one
     jobInfo1->m_elementId.SetPlatform("platform_2"); // differing job keys
     jobInfo1->m_elementId.SetJobDescriptor("jobKey_3"); // differing descriptor
 
@@ -110,14 +114,14 @@ TEST_F(JobModelUnitTests, Test_RemoveAllJobsBySource)
     m_unitTestJobModel->m_cachedJobs.push_back(jobInfo1);
     m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo1->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
 
-    AssetProcessor::QueueElementID elementId("source3.txt", "platform_2", "jobKey_3");
+    AssetProcessor::QueueElementID elementId(AssetProcessor::SourceAssetReference("c:/test/source3.txt"), "platform_2", "jobKey_3");
     auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
     ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
     unsigned int jobIndex = iter.value();
     ASSERT_EQ(jobIndex, 6); //last job
 
     ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 7);
-    m_unitTestJobModel->OnSourceRemoved("source3.txt");
+    m_unitTestJobModel->OnSourceRemoved(AssetProcessor::SourceAssetReference("c:/test/source3.txt"));
 
     // both  sources should be removed!
     ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 5);
@@ -127,7 +131,7 @@ TEST_F(JobModelUnitTests, Test_RemoveAllJobsBySource)
     for (int idx = 0; idx < m_unitTestJobModel->m_cachedJobs.size(); idx++)
     {
         AssetProcessor::CachedJobInfo* jobInfo = m_unitTestJobModel->m_cachedJobs[idx];
-        ASSERT_NE(jobInfo->m_elementId.GetInputAssetName(), QString::fromUtf8("source3.txt"));
+        ASSERT_NE(jobInfo->m_elementId.GetSourceAssetReference().AbsolutePath().Native(), "c:/test/source3.txt");
     }
 }
 
@@ -140,7 +144,7 @@ TEST_F(JobModelUnitTests, Test_PopulateJobsFromDatabase)
 
     for (const auto& jobEntry : m_data->m_jobEntries)
     {
-        AssetProcessor::QueueElementID elementId(m_data->m_sourceName.c_str(), jobEntry.m_platform.c_str(), jobEntry.m_jobKey.c_str());
+        AssetProcessor::QueueElementID elementId(AssetProcessor::SourceAssetReference("c:/test", m_data->m_sourceName.c_str()), jobEntry.m_platform.c_str(), jobEntry.m_jobKey.c_str());
         auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
         ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
 
@@ -172,7 +176,7 @@ void JobModelUnitTests::SetUp()
 
     m_unitTestJobModel = new UnitTestJobModel();
     AssetProcessor::CachedJobInfo* jobInfo1 = new AssetProcessor::CachedJobInfo();
-    jobInfo1->m_elementId.SetInputAssetName("source1.txt");
+    jobInfo1->m_elementId.SetSourceAssetReference(AssetProcessor::SourceAssetReference("c:/test/source1.txt"));
     jobInfo1->m_elementId.SetPlatform("platform");
     jobInfo1->m_elementId.SetJobDescriptor("jobKey");
     jobInfo1->m_jobState = AzToolsFramework::AssetSystem::JobStatus::Completed;
@@ -180,35 +184,35 @@ void JobModelUnitTests::SetUp()
     m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo1->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
 
     AssetProcessor::CachedJobInfo* jobInfo2 = new AssetProcessor::CachedJobInfo();
-    jobInfo2->m_elementId.SetInputAssetName("source2.txt");
+    jobInfo2->m_elementId.SetSourceAssetReference(AssetProcessor::SourceAssetReference("c:/test/source2.txt"));
     jobInfo2->m_elementId.SetPlatform("platform");
     jobInfo2->m_elementId.SetJobDescriptor("jobKey");
     m_unitTestJobModel->m_cachedJobs.push_back(jobInfo2);
     m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo2->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
 
     AssetProcessor::CachedJobInfo* jobInfo3 = new AssetProcessor::CachedJobInfo();
-    jobInfo3->m_elementId.SetInputAssetName("source3.txt");
+    jobInfo3->m_elementId.SetSourceAssetReference(AssetProcessor::SourceAssetReference("c:/test/source3.txt"));
     jobInfo3->m_elementId.SetPlatform("platform");
     jobInfo3->m_elementId.SetJobDescriptor("jobKey");
     m_unitTestJobModel->m_cachedJobs.push_back(jobInfo3);
     m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo3->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
 
     AssetProcessor::CachedJobInfo* jobInfo4 = new AssetProcessor::CachedJobInfo();
-    jobInfo4->m_elementId.SetInputAssetName("source4.txt");
+    jobInfo4->m_elementId.SetSourceAssetReference(AssetProcessor::SourceAssetReference("c:/test/source4.txt"));
     jobInfo4->m_elementId.SetPlatform("platform");
     jobInfo4->m_elementId.SetJobDescriptor("jobKey");
     m_unitTestJobModel->m_cachedJobs.push_back(jobInfo4);
     m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo4->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
 
     AssetProcessor::CachedJobInfo* jobInfo5 = new AssetProcessor::CachedJobInfo();
-    jobInfo5->m_elementId.SetInputAssetName("source5.txt");
+    jobInfo5->m_elementId.SetSourceAssetReference(AssetProcessor::SourceAssetReference("c:/test/source5.txt"));
     jobInfo5->m_elementId.SetPlatform("platform");
     jobInfo5->m_elementId.SetJobDescriptor("jobKey");
     m_unitTestJobModel->m_cachedJobs.push_back(jobInfo5);
     m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo5->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
 
     AssetProcessor::CachedJobInfo* jobInfo6 = new AssetProcessor::CachedJobInfo();
-    jobInfo6->m_elementId.SetInputAssetName("source6.txt");
+    jobInfo6->m_elementId.SetSourceAssetReference(AssetProcessor::SourceAssetReference("c:/test/source6.txt"));
     jobInfo6->m_elementId.SetPlatform("platform");
     jobInfo6->m_elementId.SetJobDescriptor("jobKey");
     m_unitTestJobModel->m_cachedJobs.push_back(jobInfo6);
@@ -257,7 +261,7 @@ void JobModelUnitTests::CreateDatabaseTestData()
     SourceDatabaseEntry sourceEntry;
     StatDatabaseEntry statEntry;
 
-    scanFolderEntry = { "c:/O3DE/dev", "dev", "rootportkey" };
+    scanFolderEntry = { "c:/test", "dev", "rootportkey" };
     ASSERT_TRUE(m_data->m_connection.SetScanFolder(scanFolderEntry));
     sourceEntry = { scanFolderEntry.m_scanFolderID, m_data->m_sourceName.c_str(), AZ::Uuid::CreateRandom(), "AFPAFPAFP1" };
     ASSERT_TRUE(m_data->m_connection.SetSource(sourceEntry));
@@ -279,7 +283,8 @@ void JobModelUnitTests::CreateDatabaseTestData()
     for (const auto& jobEntry : m_data->m_jobEntries)
     {
         AZStd::string statName = AZStd::string::format(
-            "ProcessJob,%s,%s,%s,%s",
+            "ProcessJob,%s,%s,%s,%s,%s",
+            scanFolderEntry.m_scanFolder.c_str(),
             m_data->m_sourceName.c_str(),
             jobEntry.m_jobKey.c_str(),
             jobEntry.m_platform.c_str(),
@@ -290,7 +295,7 @@ void JobModelUnitTests::CreateDatabaseTestData()
         ASSERT_TRUE(m_data->m_connection.ReplaceStat(statEntry));
     }
 
-    //! Insert an invalid stat entry (6 tokens)
-    statEntry = { "ProcessJob,apple,banana,carrot,dog,{FDAF4363-C530-476C-B382-579A43B3E2FC}", 123, 456 };
+    //! Insert an invalid stat entry (7 tokens)
+    statEntry = { "ProcessJob,apple,peach,banana,carrot,dog,{FDAF4363-C530-476C-B382-579A43B3E2FC}", 123, 456 };
     ASSERT_TRUE(m_data->m_connection.ReplaceStat(statEntry));
 }

+ 3 - 1
Code/Tools/AssetProcessor/native/tests/utilities/JobModelTest.h

@@ -13,6 +13,7 @@
 #include <native/tests/MockAssetDatabaseRequestsHandler.h>
 #include <AzToolsFramework/API/AssetDatabaseBus.h>
 #include <QCoreApplication>
+#include <tests/UnitTestUtilities.h>
 
 namespace AzToolsFramework
 {
@@ -22,7 +23,7 @@ namespace AzToolsFramework
     }
 }
 
-class UnitTestJobModel 
+class UnitTestJobModel
     : public AssetProcessor::JobsModel
 {
 public:
@@ -55,6 +56,7 @@ protected:
 
         const AZStd::string m_sourceName{ "theFile.fbx" };
         AZStd::vector<AzToolsFramework::AssetDatabase::JobDatabaseEntry> m_jobEntries;
+        UnitTests::MockPathConversion mockPathConversion{ "c:/test" };
     };
     AZStd::unique_ptr<StaticData> m_data;
     void CreateDatabaseTestData();

+ 16 - 14
Code/Tools/AssetProcessor/native/tests/utilities/assetUtilsTest.cpp

@@ -13,6 +13,7 @@
 #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
 #include <AzCore/std/parallel/thread.h>
 #include <AzTest/AzTest.h>
+#include <tests/UnitTestUtilities.h>
 
 
 using namespace AssetUtilities;
@@ -39,7 +40,7 @@ class AssetUtilitiesTest
             m_localFileIo = nullptr;
             AZ::IO::FileIOBase::SetInstance(nullptr);
         }
-        
+
         AssetProcessorTest::TearDown();
     }
 
@@ -151,7 +152,7 @@ TEST_F(AssetUtilitiesTest, UpdateToCorrectCase_ExistingFile_ReturnsTrue_Corrects
     thingsToTry << "specialFileName+.txt";
     thingsToTry << "specialFileName[9].txt";
     thingsToTry << "specialFileName[A-Za-z].txt"; // these should all be treated as literally the name of the file, not a regex!
-    
+
     for (QString triedThing : thingsToTry)
     {
         triedThing = NormalizeFilePath(triedThing);
@@ -159,7 +160,7 @@ TEST_F(AssetUtilitiesTest, UpdateToCorrectCase_ExistingFile_ReturnsTrue_Corrects
 
         QString lowercaseVersion = triedThing.toLower();
         // each one should be found.   If it fails, we'll pipe out the name of the file it fails on for extra context.
-        
+
         EXPECT_TRUE(AssetUtilities::UpdateToCorrectCase(canonicalTempDirPath, lowercaseVersion)) << "File being Examined: " << lowercaseVersion.toUtf8().constData();
         // each one should correct, and return a normalized path.
         EXPECT_STREQ(AssetUtilities::NormalizeFilePath(lowercaseVersion).toUtf8().constData(), AssetUtilities::NormalizeFilePath(triedThing).toUtf8().constData());
@@ -217,7 +218,7 @@ TEST_F(AssetUtilitiesTest, GenerateFingerprint_BasicTest)
     EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath1, "contents new"));
     result1 = AssetUtilities::GenerateFingerprint(jobDetail);
     EXPECT_NE(result1, result2);
-    
+
     // changing the other should also change the hash:
     EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath2, "contents new2"));
     result2 = AssetUtilities::GenerateFingerprint(jobDetail);
@@ -228,7 +229,7 @@ TEST_F(AssetUtilitiesTest, GenerateFingerprint_Empty_Asserts)
 {
     AssetProcessor::JobDetails jobDetail;
     AssetUtilities::GenerateFingerprint(jobDetail);
-    
+
     EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 1);
     m_errorAbsorber->Clear();
 }
@@ -242,7 +243,7 @@ TEST_F(AssetUtilitiesTest, GenerateFingerprint_MissingFile_NotSameAsZeroByteFile
     tempPath = QDir(canonicalTempDirPath);
     QString absoluteTestFilePath1 = tempPath.absoluteFilePath("basicfile.txt");
     QString absoluteTestFilePath2 = tempPath.absoluteFilePath("basicfile2.txt");
-    
+
     EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath1, "")); // empty file
     // note:  basicfile1 exists but is empty, whereas basicfile2, 3 are missing entirely.
 
@@ -300,7 +301,7 @@ TEST_F(AssetUtilitiesTest, GenerateFingerprint_OneFile_Differs)
     jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile.txt").toUtf8().constData(), "basicfile.txt"));
 
     AZ::u32 fingerprint1 = AssetUtilities::GenerateFingerprint(jobDetail);
-    
+
     jobDetail.m_fingerprintFiles.clear();
     jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile2.txt").toUtf8().constData(), "basicfile2.txt"));
     AZ::u32 fingerprint2 = AssetUtilities::GenerateFingerprint(jobDetail);
@@ -358,7 +359,7 @@ TEST_F(AssetUtilitiesTest, GenerateFingerprint_MultipleFile_Differs)
     jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile.txt").toUtf8().constData(), "basicfile.txt"));
     jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile2.txt").toUtf8().constData(), "basicfile2.txt"));
     fingerprint1 = AssetUtilities::GenerateFingerprint(jobDetail);
-    
+
     jobDetail.m_fingerprintFiles.clear();
     jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile2.txt").toUtf8().constData(), "basicfile2.txt"));
     jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile3.txt").toUtf8().constData(), "basicfile3.txt"));
@@ -379,6 +380,7 @@ TEST_F(AssetUtilitiesTest, GenerateFingerprint_OrderOnceJobDependency_NoChange)
     // OrderOnce Job dependency should not alter the fingerprint of the job
     QTemporaryDir dir;
     QDir tempPath(dir.path());
+    UnitTests::MockPathConversion mockPathConversion(dir.path().toUtf8().constData());
     QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
     UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
     tempPath = QDir(canonicalTempDirPath);
@@ -392,10 +394,9 @@ TEST_F(AssetUtilitiesTest, GenerateFingerprint_OrderOnceJobDependency_NoChange)
 
     AssetProcessor::JobDetails jobDetail;
 
-    jobDetail.m_jobEntry.m_databaseSourceName = relFile1Path;
-    jobDetail.m_jobEntry.m_watchFolderPath = tempPath.absolutePath();
+    jobDetail.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(tempPath.absolutePath(), relFile1Path);
     jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(absoluteTestFile1Path.toUtf8().constData(), relFile1Path));
-  
+
     AZ::u32 fingerprintWithoutOrderOnceJobDependency = AssetUtilities::GenerateFingerprint(jobDetail);
 
     AssetBuilderSDK::SourceFileDependency dep = { relFile2Path, AZ::Uuid::CreateNull() };
@@ -432,6 +433,7 @@ TEST_F(AssetUtilitiesTest, GenerateFingerprint_GivenJobDependencies_AffectsOutco
 
     QTemporaryDir dir;
     QDir tempPath(dir.path());
+    UnitTests::MockPathConversion mockPathConversion(dir.path().toUtf8().constData());
     QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
     UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
     tempPath = QDir(canonicalTempDirPath);
@@ -448,14 +450,14 @@ TEST_F(AssetUtilitiesTest, GenerateFingerprint_GivenJobDependencies_AffectsOutco
     jobDetail.m_jobDependencyList.push_back(internalJobDep);
 
     EXPECT_CALL(responder, GetJobFingerprint(_))
-        .WillOnce( 
+        .WillOnce(
             Return(0x12341234));
 
     AZ::u32 fingerprint2 = AssetUtilities::GenerateFingerprint(jobDetail);
 
     // different job fingerprint -> different result
     EXPECT_CALL(responder, GetJobFingerprint(_))
-        .WillOnce( 
+        .WillOnce(
             Return(0x11111111));
 
     AZ::u32 fingerprint3 = AssetUtilities::GenerateFingerprint(jobDetail);
@@ -469,6 +471,7 @@ TEST_F(AssetUtilitiesTest, GetFileFingerprint_BasicTest)
 {
     QTemporaryDir dir;
     QDir tempPath(dir.path());
+    UnitTests::MockPathConversion mockPathConversion(dir.path().toUtf8().constData());
     QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
     UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
     tempPath = QDir(canonicalTempDirPath);
@@ -605,4 +608,3 @@ TEST_F(AssetUtilitiesTest, CreateDir_InvalidDir_Timeout_Valid)
 
     ASSERT_FALSE(dir.exists());
 }
-

+ 1 - 1
Code/Tools/AssetProcessor/native/ui/BuilderData.cpp

@@ -163,7 +163,7 @@ namespace AssetProcessor
 
             AZStd::string entryName = AZStd::string::format(
                 "%s,%s,%s",
-                jobEntry.m_databaseSourceName.toUtf8().constData(),
+                jobEntry.m_sourceAssetReference.RelativePath().c_str(),
                 jobEntry.m_jobKey.toUtf8().constData(),
                 jobEntry.m_platformInfo.m_identifier.c_str());
 

+ 4 - 18
Code/Tools/AssetProcessor/native/ui/MainWindow.cpp

@@ -1751,7 +1751,7 @@ void MainWindow::JobStatusChanged([[maybe_unused]] AssetProcessor::JobEntry entr
     }
 
     // ignore the notification if it's not for the selected entry
-    if (cachedJobInfo->m_elementId.GetInputAssetName() != entry.m_databaseSourceName)
+    if (cachedJobInfo->m_elementId.GetSourceAssetReference() != entry.m_sourceAssetReference)
     {
         return;
     }
@@ -1841,21 +1841,7 @@ void MainWindow::ShowJobLogContextMenu(const QPoint& pos)
 
 static QString FindAbsoluteFilePath(const AssetProcessor::CachedJobInfo* cachedJobInfo)
 {
-    using namespace AzToolsFramework;
-
-    bool result = false;
-    AZ::Data::AssetInfo info;
-    AZStd::string watchFolder;
-    QByteArray assetNameUtf8 = cachedJobInfo->m_elementId.GetInputAssetName().toUtf8();
-    AssetSystemRequestBus::BroadcastResult(result, &AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, assetNameUtf8.constData(), info, watchFolder);
-    if (!result)
-    {
-        AZ_Error("AssetProvider", false, "Failed to locate asset info for '%s'.", assetNameUtf8.constData());
-    }
-
-    return result
-        ? QDir(watchFolder.c_str()).absoluteFilePath(info.m_relativePath.c_str())
-        : QString();
+    return cachedJobInfo ? cachedJobInfo->m_elementId.GetSourceAssetReference().AbsolutePath().c_str() : QString{};
 };
 
 static void SendShowInAssetBrowserResponse(const QString& filePath, ConnectionManager* connectionManager, unsigned int connectionId, QByteArray data)
@@ -1944,7 +1930,7 @@ void MainWindow::ShowJobViewContextMenu(const QPoint& pos)
     {
         ui->dialogStack->setCurrentIndex(static_cast<int>(DialogStackIndex::Assets));
         ui->buttonList->setCurrentIndex(static_cast<int>(DialogStackIndex::Assets));
-        ui->sourceAssetDetailsPanel->GoToSource(AZStd::string(item->m_elementId.GetInputAssetName().toUtf8().constData()));
+        ui->sourceAssetDetailsPanel->GoToSource(item->m_elementId.GetSourceAssetReference().RelativePath().c_str());
     });
 
     if (item->m_jobState != AzToolsFramework::AssetSystem::JobStatus::Completed)
@@ -2231,7 +2217,7 @@ void MainWindow::BuildSourceAssetTreeContextMenu(QMenu& menu, const AssetProcess
     {
         QAction* jobAction = jobMenu->addAction(tr("with key %1 for platform %2").arg(jobEntry.m_jobKey.c_str(), jobEntry.m_platform.c_str()), this, [&, jobEntry, sourceName]()
         {
-            QModelIndex jobIndex = m_jobsModel->GetJobFromSourceAndJobInfo(sourceName, jobEntry.m_platform, jobEntry.m_jobKey);
+            QModelIndex jobIndex = m_jobsModel->GetJobFromSourceAndJobInfo(SourceAssetReference(sourceItemData->m_scanFolderInfo.m_scanFolder.c_str(), sourceItemData->m_sourceInfo.m_sourceName.c_str()), jobEntry.m_platform, jobEntry.m_jobKey);
             SelectJobAndMakeVisible(jobIndex);
         });
         jobAction->setToolTip(tr("Show this job in the Jobs tab."));

+ 6 - 2
Code/Tools/AssetProcessor/native/unittests/AssetProcessingStateDataUnitTests.cpp

@@ -5,6 +5,7 @@
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 *
 */
+#include <native/tests/AssetProcessorTest.h>
 
 #include <native/unittests/AssetProcessorUnitTests.h>
 #include <native/unittests/UnitTestRunner.h> // for the assert absorber.
@@ -495,6 +496,9 @@ namespace AssetProcessor
         scanFolder = ScanFolderDatabaseEntry("c:/O3DE/dev", "dev", "devkey3");
         EXPECT_TRUE(m_connection.SetScanFolder(scanFolder));
 
+        auto& config = m_appManager->m_platformConfig;
+        config->AddScanFolder(AssetProcessor::ScanFolderInfo{scanFolder.m_scanFolder.c_str(), scanFolder.m_displayName.c_str(), scanFolder.m_portableKey.c_str(), false, true, {} , 0, scanFolder.m_scanFolderID});
+
         //Add some sources
         source = SourceDatabaseEntry(scanFolder.m_scanFolderID, "SomeSource1.tif", validSourceGuid1, "");
         EXPECT_TRUE(m_connection.SetSource(source));
@@ -597,7 +601,7 @@ namespace AssetProcessor
         EXPECT_FALSE(m_connection.GetJobs(jobs));
         EXPECT_FALSE(m_connection.GetJobByJobID(3443, job));
         EXPECT_FALSE(m_connection.GetJobsBySourceID(3234, jobs));
-        EXPECT_FALSE(m_connection.GetJobsBySourceName("none", jobs));
+        EXPECT_FALSE(m_connection.GetJobsBySourceName(AssetProcessor::SourceAssetReference("c:/O3DE/dev/none"), jobs));
 
         //trying to add a job without a valid source pk should fail:
         {
@@ -668,7 +672,7 @@ namespace AssetProcessor
 
         //try retrieving jobs by source name
         jobs.clear();
-        EXPECT_TRUE(m_connection.GetJobsBySourceName(source.m_sourceName.c_str(), jobs));
+        EXPECT_TRUE(m_connection.GetJobsBySourceName(AssetProcessor::SourceAssetReference(source.m_scanFolderPK, source.m_sourceName.c_str()), jobs));
         EXPECT_EQ(jobs.size(), 1);
         EXPECT_TRUE(JobsContainJobID(jobs, job.m_jobID));
         EXPECT_TRUE(JobsContainJobKey(jobs, job.m_jobKey.c_str()));

+ 60 - 43
Code/Tools/AssetProcessor/native/unittests/AssetProcessorManagerUnitTests.cpp

@@ -16,7 +16,7 @@
 #include "MockApplicationManager.h"
 #include "native/FileWatcher/FileWatcher.h"
 #include "native/unittests/MockConnectionHandler.h"
-
+#include <native/tests/AssetProcessorTest.h>
 #include <AzTest/AzTest.h>
 
 #include <QCoreApplication>
@@ -101,8 +101,8 @@ namespace AssetProcessor
 
             //Calculating fingerprints for the file for pc and android platforms
             AZ::Uuid sourceId = AZ::Uuid("{2206A6E0-FDBC-45DE-B6FE-C2FC63020BD5}");
-            JobEntry jobEntryPC(scanFolderPath, relPath, relPath, {}, { "pc", {"desktop", "renderer"} }, "", 0, 1, sourceId);
-            JobEntry jobEntryANDROID(scanFolderPath, relPath, relPath, {}, { "android", {"mobile", "renderer"} }, "", 0, 2, sourceId);
+            JobEntry jobEntryPC(SourceAssetReference(scanFolderPath, relPath), {}, { "pc", {"desktop", "renderer"} }, "", 0, 1, sourceId);
+            JobEntry jobEntryANDROID(SourceAssetReference(scanFolderPath, relPath), {}, { "android", {"mobile", "renderer"} }, "", 0, 2, sourceId);
 
             JobDetails jobDetailsPC;
             jobDetailsPC.m_extraInformationForFingerprinting = extraInfoForPC.toUtf8().constData();
@@ -245,8 +245,12 @@ namespace AssetProcessor
 #endif
         }
 
+        auto* appManager = AZ::Interface<IUnitTestAppManager>::Get();
+
+        UNIT_TEST_EXPECT_FALSE(appManager == nullptr);
+
+        auto& config = appManager->GetConfig();
 
-        PlatformConfiguration config;
         config.EnablePlatform({ "pc",{ "desktop", "renderer" } }, true);
         config.EnablePlatform({ "android",{ "mobile", "renderer" } }, true);
         config.EnablePlatform({ "fandago",{ "console", "renderer" } }, false);
@@ -463,9 +467,8 @@ namespace AssetProcessor
         {
             UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_computedFingerprint != 0);
             UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_jobRunKey != 0);
-            UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_watchFolderPath == AssetUtilities::NormalizeFilePath(watchFolderPath));
-            UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_pathRelativeToWatchFolder == "uniquefile.txt");
-            UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_databaseSourceName == "uniquefile.txt");
+            UNIT_TEST_EXPECT_TRUE(QString(processResults[checkIdx].m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str()) == AssetUtilities::NormalizeFilePath(watchFolderPath));
+            UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_sourceAssetReference.RelativePath().Native() == "uniquefile.txt");
 
             QString platformFolder = cacheRoot.filePath(QString::fromUtf8(processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()));
             platformFolder = AssetUtilities::NormalizeDirectoryPath(platformFolder);
@@ -490,8 +493,8 @@ namespace AssetProcessor
                 info.m_builderGuid = processResults[checkIdx].m_jobEntry.m_builderGuid;
                 info.m_jobKey = processResults[checkIdx].m_jobEntry.m_jobKey.toUtf8().data();
                 info.m_platform = processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str();
-                info.m_sourceFile = processResults[checkIdx].m_jobEntry.m_pathRelativeToWatchFolder.toUtf8().data();
-                info.m_watchFolder = processResults[checkIdx].m_jobEntry.m_watchFolderPath.toUtf8().data();
+                info.m_sourceFile = processResults[checkIdx].m_jobEntry.m_sourceAssetReference.RelativePath().c_str();
+                info.m_watchFolder = processResults[checkIdx].m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str();
 
                 AZStd::string logFolder = AZStd::string::format("%s/%s", AssetUtilities::ComputeJobLogFolder().c_str(), AssetUtilities::ComputeJobLogFileName(info).c_str());
                 AZ::IO::HandleType logHandle;
@@ -536,8 +539,9 @@ namespace AssetProcessor
 
                 for (const JobDetails& details : processResults)
                 {
-                    if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_pathRelativeToWatchFolder, Qt::CaseSensitive) == 0) &&
-                        (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_watchFolderPath, Qt::CaseSensitive) == 0) &&
+                    if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_sourceAssetReference.RelativePath().c_str(), Qt::CaseSensitive) == 0) &&
+                        (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str(), Qt::CaseSensitive) ==
+                         0) &&
                         (QString::compare(jobInfo.m_platform.c_str(), details.m_jobEntry.m_platformInfo.m_identifier.c_str(), Qt::CaseInsensitive) == 0) &&
                         (QString::compare(jobInfo.m_jobKey.c_str(), details.m_jobEntry.m_jobKey, Qt::CaseInsensitive) == 0) &&
                         (jobInfo.m_builderGuid == details.m_jobEntry.m_builderGuid) &&
@@ -642,8 +646,8 @@ namespace AssetProcessor
 
                 for (const JobDetails& details : processResults)
                 {
-                    if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_pathRelativeToWatchFolder, Qt::CaseSensitive) == 0) &&
-                        (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_watchFolderPath, Qt::CaseSensitive) == 0) &&
+                    if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_sourceAssetReference.RelativePath().c_str(), Qt::CaseSensitive) == 0) &&
+                        (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str(), Qt::CaseSensitive) == 0) &&
                         (QString::compare(jobInfo.m_platform.c_str(), details.m_jobEntry.m_platformInfo.m_identifier.c_str(), Qt::CaseInsensitive) == 0) &&
                         (QString::compare(jobInfo.m_jobKey.c_str(), details.m_jobEntry.m_jobKey, Qt::CaseInsensitive) == 0) &&
                         (jobInfo.m_builderGuid == details.m_jobEntry.m_builderGuid) &&
@@ -773,8 +777,8 @@ namespace AssetProcessor
                 {
                     const JobDetails& details = processResults[detailsIdx];
 
-                    if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_pathRelativeToWatchFolder, Qt::CaseSensitive) == 0) &&
-                        (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_watchFolderPath, Qt::CaseSensitive) == 0) &&
+                    if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_sourceAssetReference.RelativePath().c_str(), Qt::CaseSensitive) == 0) &&
+                        (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str(), Qt::CaseSensitive) == 0) &&
                         (QString::compare(jobInfo.m_jobKey.c_str(), details.m_jobEntry.m_jobKey, Qt::CaseInsensitive) == 0) &&
                         (jobInfo.m_builderGuid == details.m_jobEntry.m_builderGuid) &&
                         (jobInfo.GetHash() == details.m_jobEntry.GetHash()))
@@ -910,8 +914,8 @@ namespace AssetProcessor
 
                 for (const JobDetails& details : processResults)
                 {
-                    if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_pathRelativeToWatchFolder, Qt::CaseSensitive) == 0) &&
-                        (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_watchFolderPath, Qt::CaseSensitive) == 0) &&
+                    if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_sourceAssetReference.RelativePath().c_str(), Qt::CaseSensitive) == 0) &&
+                        (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str(), Qt::CaseSensitive) == 0) &&
                         (QString::compare(jobInfo.m_platform.c_str(), details.m_jobEntry.m_platformInfo.m_identifier.c_str(), Qt::CaseInsensitive) == 0) &&
                         (QString::compare(jobInfo.m_jobKey.c_str(), details.m_jobEntry.m_jobKey, Qt::CaseInsensitive) == 0) &&
                         (jobInfo.m_builderGuid == details.m_jobEntry.m_builderGuid) &&
@@ -1409,8 +1413,8 @@ namespace AssetProcessor
                 {
                     const JobDetails& details = processResults[detailsIdx];
 
-                    if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_pathRelativeToWatchFolder, Qt::CaseSensitive) == 0) &&
-                        (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_watchFolderPath, Qt::CaseSensitive) == 0) &&
+                    if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_sourceAssetReference.RelativePath().c_str(), Qt::CaseSensitive) == 0) &&
+                        (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str(), Qt::CaseSensitive) == 0) &&
                         (QString::compare(jobInfo.m_platform.c_str(), details.m_jobEntry.m_platformInfo.m_identifier.c_str(), Qt::CaseInsensitive) == 0) &&
                         (QString::compare(jobInfo.m_jobKey.c_str(), details.m_jobEntry.m_jobKey, Qt::CaseInsensitive) == 0) &&
                         (jobInfo.m_builderGuid == details.m_jobEntry.m_builderGuid) &&
@@ -1637,11 +1641,16 @@ namespace AssetProcessor
 
         sortAssetToProcessResultList(processResults);
 
-#if defined(AZ_PLATFORM_LINUX)
-        // On Linux, because of we cannot change the case of the source file, the job fingerprint is not updated due the case-switch so
-        // there will be actually nothing to process
-        UNIT_TEST_EXPECT_TRUE(processResults.size() == 0);
-#else
+        // On Linux, because we cannot change the case of the source file, the job fingerprint is not updated due the case-switch.
+        // The reason the fingerprint for subfolder3/basefile.txt and subfolder2/basefile.txt are the same ON LINUX is because the
+        // fingerprint of the file includes the filename (also both files have the same contents).  Additionally, when this test is set up,
+        // subfolder3BaseFilePath ON LINUX is set to basefile.txt whereas it is set to BaseFile.txt on windows.  That is why the hash is the
+        // same only for linux but different for other platforms. Note that if this test breaks on linux, it can be debugged on windows by
+        // setting subfolder3BaseFilePath = basefile.txt on windows.
+        // We still expect linux to produce the same result as other platforms however because we no longer query sources using just the relative path.
+        // This means the override file which has not been processed yet MUST be processed, regardless of whether it just happens to have the same fingerprint
+        // on linux.
+
         // --------- same result as above ----------
         UNIT_TEST_EXPECT_TRUE(processResults.size() == 4); // 2 each for pc and android,since we have two recognizer for .txt file
         UNIT_TEST_EXPECT_TRUE(processResults[0].m_jobEntry.m_platformInfo.m_identifier == processResults[1].m_jobEntry.m_platformInfo.m_identifier);
@@ -1659,7 +1668,7 @@ namespace AssetProcessor
             verifyProductPaths(processResults[checkIdx]);
             UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_computedFingerprint != 0);
         }
-#endif // defined(AZ_PLATFORM_LINUX)
+
         relativePathFromWatchFolder = "somefile.xxx";
         watchFolderPath = tempPath.absoluteFilePath("subfolder3");
         absolutePath = watchFolderPath + "/" + relativePathFromWatchFolder;
@@ -2289,7 +2298,12 @@ namespace AssetProcessor
 
         UNIT_TEST_EXPECT_FALSE(gameName.isEmpty());
 
-        PlatformConfiguration config;
+        auto* appManager = AZ::Interface<IUnitTestAppManager>::Get();
+
+        UNIT_TEST_EXPECT_FALSE(appManager == nullptr);
+
+        auto& config = appManager->GetConfig();
+
         config.EnablePlatform({ "pc" ,{ "desktop", "renderer" } }, true);
         AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms;
         config.PopulatePlatformsForScanFolder(platforms);
@@ -2353,7 +2367,7 @@ namespace AssetProcessor
         for (int idx = 0; idx < processResults.size(); idx++)
         {
             UNIT_TEST_EXPECT_TRUE((processResults[idx].m_jobEntry.m_platformInfo.m_identifier == "pc"));
-            UNIT_TEST_EXPECT_TRUE(processResults[idx].m_jobEntry.m_pathRelativeToWatchFolder.startsWith("basefile.foo"));
+            UNIT_TEST_EXPECT_TRUE(processResults[idx].m_jobEntry.m_sourceAssetReference.RelativePath().Native().starts_with("basefile.foo"));
         }
         UNIT_TEST_EXPECT_TRUE(processResults[0].m_jobEntry.m_jobKey.compare(processResults[1].m_jobEntry.m_jobKey) != 0);
 
@@ -2434,7 +2448,7 @@ namespace AssetProcessor
         for (int idx = 0; idx < processResults.size(); idx++)
         {
             UNIT_TEST_EXPECT_TRUE((processResults[idx].m_jobEntry.m_platformInfo.m_identifier == "pc"));
-            UNIT_TEST_EXPECT_TRUE(processResults[idx].m_jobEntry.m_pathRelativeToWatchFolder.startsWith("basefile.foo"));
+            UNIT_TEST_EXPECT_TRUE(processResults[idx].m_jobEntry.m_sourceAssetReference.RelativePath().Native().starts_with("basefile.foo"));
         }
 
         mockAssetBuilderInfoHandler.m_numberOfJobsToCreate = 1; //Create one job for this file this time
@@ -2469,7 +2483,7 @@ namespace AssetProcessor
         for (int idx = 0; idx < processResults.size(); idx++)
         {
             UNIT_TEST_EXPECT_TRUE((processResults[idx].m_jobEntry.m_platformInfo.m_identifier == "pc"));
-            UNIT_TEST_EXPECT_TRUE(processResults[idx].m_jobEntry.m_pathRelativeToWatchFolder.startsWith("basefile.foo"));
+            UNIT_TEST_EXPECT_TRUE(processResults[idx].m_jobEntry.m_sourceAssetReference.RelativePath().Native().starts_with("basefile.foo"));
         }
 
         Q_EMIT UnitTestPassed();
@@ -2746,7 +2760,12 @@ namespace AssetProcessor
         UNIT_TEST_EXPECT_FALSE(gameName.isEmpty());
         // should create cache folder in the root, and read everything from there.
 
-        PlatformConfiguration config;
+        auto* appManager = AZ::Interface<IUnitTestAppManager>::Get();
+
+        UNIT_TEST_EXPECT_FALSE(appManager == nullptr);
+
+        auto& config = appManager->GetConfig();
+
         config.EnablePlatform({ "pc",{ "desktop", "renderer" } }, true);
         AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms;
         config.PopulatePlatformsForScanFolder(platforms);
@@ -2881,7 +2900,7 @@ namespace AssetProcessor
         {
             UNIT_TEST_EXPECT_TRUE(processResults.size() == 2); // Repeat to ensure count doesn't change while looping
 
-            if (QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileB.txt"))
+            if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileB.txt"))
             {
                 // Ensure that we are processing the right FileB job
                 UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy") == 0);
@@ -2918,7 +2937,7 @@ namespace AssetProcessor
 
         for (JobDetails& jobDetail : processResults)
         {
-            if (QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileB.txt"))
+            if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileB.txt"))
             {
                 // Ensure that we are processing the right FileB job
                 UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy") == 0);
@@ -2954,7 +2973,7 @@ namespace AssetProcessor
 
         for (JobDetails& jobDetail : processResults)
         {
-            UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileC.txt"));
+            UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileC.txt"));
             if (jobDetail.m_jobDependencyList.size())
             {
                 // Verify FileC jobinfo
@@ -2991,18 +3010,17 @@ namespace AssetProcessor
 
         for (JobDetails& jobDetail : processResults)
         {
-            if (QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileA.txt"))
+            if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileA.txt"))
             {
                 // Verify FileA jobinfo
                 UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("xxx") == 0);
             }
-            else if(QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileB.txt"))
+            else if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileB.txt"))
             {
                 // Verify FileB jobinfo
                 UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy") == 0);
-
             }
-            else if (QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileC.txt"))
+            else if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileC.txt"))
             {
                 // Verify FileC jobinfo
                 UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("zzz") == 0);
@@ -3026,12 +3044,12 @@ namespace AssetProcessor
 
         for (JobDetails& jobDetail : processResults)
         {
-            if (QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileB.txt"))
+            if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileB.txt"))
             {
                 // Verify FileB jobinfo
                 UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy") == 0);
             }
-            else if (QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileC.txt"))
+            else if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileC.txt"))
             {
                 // Verify FileC jobinfo
                 UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("zzz") == 0);
@@ -3057,18 +3075,17 @@ namespace AssetProcessor
 
         for (JobDetails& jobDetail : processResults)
         {
-            if (QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileA.txt"))
+            if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileA.txt"))
             {
                 // Verify FileA jobinfo
                 UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("xxx") == 0);
             }
-            else if (QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileB.txt"))
+            else if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileB.txt"))
             {
                 // Verify FileB jobinfo
                 UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy") == 0);
-
             }
-            else if (QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileC.txt"))
+            else if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileC.txt"))
             {
                 // Verify FileC jobinfo
                 UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("zzz") == 0);

+ 38 - 34
Code/Tools/AssetProcessor/native/unittests/RCcontrollerUnitTests.cpp

@@ -19,6 +19,8 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 #endif
+#include <tests/UnitTestUtilities.h>
+#include <tests/AssetProcessorTest.h>
 
 using namespace AssetProcessor;
 using namespace AzFramework::AssetSystem;
@@ -27,7 +29,7 @@ namespace
 {
     constexpr NetworkRequestID RequestID(1, 1234);
     constexpr int MaxProcessingWaitTimeMs = 60 * 1000; // Wait up to 1 minute.  Give a generous amount of time to allow for slow CPUs
-    const ScanFolderInfo TestScanFolderInfo("samplepath", "sampledisplayname", "samplekey", false, false);
+    const ScanFolderInfo TestScanFolderInfo("c:/samplepath", "sampledisplayname", "samplekey", false, false);
     const AZ::Uuid BuilderUuid = AZ::Uuid::CreateRandom();
 }
 
@@ -63,9 +65,7 @@ void RCcontrollerUnitTests::PrepareRCJobListModelTest()
     m_rcQueueSortModel = &m_rcController->m_RCQueueSortModel;
 
     AssetProcessor::JobDetails jobDetails;
-    jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile0.txt";
-    jobDetails.m_jobEntry.m_watchFolderPath = QCoreApplication::applicationDirPath();
-    jobDetails.m_jobEntry.m_databaseSourceName = "someFile0.txt";
+    jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile0.txt");
     jobDetails.m_jobEntry.m_platformInfo = { "pc", { "desktop", "renderer" } };
     jobDetails.m_jobEntry.m_jobKey = "Text files";
 
@@ -74,35 +74,35 @@ void RCcontrollerUnitTests::PrepareRCJobListModelTest()
     m_rcJobListModel->addNewJob(job0);
 
     RCJob* job1 = new RCJob(m_rcJobListModel);
-    jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile1.txt";
+    jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile1.txt");
     jobDetails.m_jobEntry.m_platformInfo = { "pc", { "desktop", "renderer" } };
     jobDetails.m_jobEntry.m_jobKey = "Text files";
     job1->Init(jobDetails);
     m_rcJobListModel->addNewJob(job1);
 
     RCJob* job2 = new RCJob(m_rcJobListModel);
-    jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile2.txt";
+    jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile2.txt");
     jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
     jobDetails.m_jobEntry.m_jobKey = "Text files";
     job2->Init(jobDetails);
     m_rcJobListModel->addNewJob(job2);
 
     RCJob* job3 = new RCJob(m_rcJobListModel);
-    jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile3.txt";
+    jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile3.txt");
     jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
     jobDetails.m_jobEntry.m_jobKey = "Text files";
     job3->Init(jobDetails);
     m_rcJobListModel->addNewJob(job3);
 
     RCJob* job4 = new RCJob(m_rcJobListModel);
-    jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile4.txt";
+    jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile4.txt");
     jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
     jobDetails.m_jobEntry.m_jobKey = "Text files";
     job4->Init(jobDetails);
     m_rcJobListModel->addNewJob(job4);
 
     RCJob* job5 = new RCJob(m_rcJobListModel);
-    jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile5.txt";
+    jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile5.txt");
     jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
     jobDetails.m_jobEntry.m_jobKey = "Text files";
     job5->Init(jobDetails);
@@ -163,8 +163,7 @@ void RCcontrollerUnitTests::PrepareCompileGroupTests(bool& gotCreated, bool& got
         AZ::Uuid uuidOfSource = AZ::Uuid::CreateName(name.toUtf8().constData());
         RCJob* job = new RCJob(m_rcJobListModel);
         AssetProcessor::JobDetails jobDetails;
-        jobDetails.m_jobEntry.m_watchFolderPath = "c:/somerandomfolder/dev";
-        jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = name;
+        jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/dev", name);
         jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
         jobDetails.m_jobEntry.m_jobKey = "Compile Stuff";
         jobDetails.m_jobEntry.m_sourceFileUUID = uuidOfSource;
@@ -179,7 +178,7 @@ void RCcontrollerUnitTests::PrepareCompileGroupTests(bool& gotCreated, bool& got
         AZ::Uuid uuidOfSource = AZ::Uuid::CreateName(name.toUtf8().constData());
         RCJob* job0 = new RCJob(m_rcJobListModel);
         AssetProcessor::JobDetails jobDetails;
-        jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = name;
+        jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/dev", name);
         jobDetails.m_jobEntry.m_platformInfo = { "android" ,{ "mobile", "renderer" } };
         jobDetails.m_jobEntry.m_jobKey = "Compile Other Stuff";
         jobDetails.m_jobEntry.m_sourceFileUUID = uuidOfSource;
@@ -257,6 +256,16 @@ void RCcontrollerUnitTests::SetUp()
     UnitTest::AssetProcessorUnitTestBase::SetUp();
     m_rcController = AZStd::make_unique<AssetProcessor::RCController>(1, 4);
 
+    QDir assetRootPath(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
+
+    m_appManager->m_platformConfig->AddScanFolder(TestScanFolderInfo);
+    m_appManager->m_platformConfig->AddScanFolder(
+        AssetProcessor::ScanFolderInfo{ "c:/somerandomfolder", "scanfolder", "scanfolder", true, true, {}, 0, 1 });
+    m_appManager->m_platformConfig->AddScanFolder(
+        AssetProcessor::ScanFolderInfo{ "d:/test", "scanfolder2", "scanfolder2", true, true, {}, 0, 2 });
+    m_appManager->m_platformConfig->AddScanFolder(
+        AssetProcessor::ScanFolderInfo{ assetRootPath.absoluteFilePath("subfolder4"), "subfolder4", "subfolder4", false, true, {}, 0, 3 });
+
     using namespace AssetProcessor;
     m_rcJobListModel = m_rcController->GetQueueModel();
     m_rcQueueSortModel = &m_rcController->m_RCQueueSortModel;
@@ -335,7 +344,7 @@ TEST_F(RCcontrollerUnitTests, TestCompileGroup_RequestExactMatchCompileGroup_Suc
 }
 
 TEST_F(RCcontrollerUnitTests, TestCompileGroup_RequestNoMatchCompileGroup_Succeeds)
-{  
+{
     bool gotCreated = false;
     bool gotCompleted = false;
     NetworkRequestID gotGroupID;
@@ -551,7 +560,7 @@ TEST_F(RCcontrollerUnitTests, TestRCController_FeedDuplicateJobs_NotAccept)
 
     AZ::Uuid sourceId = AZ::Uuid("{2206A6E0-FDBC-45DE-B6FE-C2FC63020BD5}");
     JobDetails details;
-    details.m_jobEntry = JobEntry("d:/test", "test1.txt", "test1.txt", AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "pc" , {"desktop", "renderer"} }, "Test Job", 1234, 1, sourceId);
+    details.m_jobEntry = JobEntry(AssetProcessor::SourceAssetReference("d:/test", "test1.txt"), AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "pc" , {"desktop", "renderer"} }, "Test Job", 1234, 1, sourceId);
     gotJobsInQueueCall = false;
     int priorJobs = jobsInQueueCount;
     m_rcController->JobSubmitted(details);
@@ -563,13 +572,13 @@ TEST_F(RCcontrollerUnitTests, TestRCController_FeedDuplicateJobs_NotAccept)
     gotJobsInQueueCall = false;
 
     // submit same job, different run key
-    details.m_jobEntry = JobEntry("d:/test", "/test1.txt", "test1.txt", AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "pc" ,{ "desktop", "renderer" } }, "Test Job", 1234, 2, sourceId);
+    details.m_jobEntry = JobEntry(AssetProcessor::SourceAssetReference("d:/test", "test1.txt"), AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "pc" ,{ "desktop", "renderer" } }, "Test Job", 1234, 2, sourceId);
     m_rcController->JobSubmitted(details);
     QCoreApplication::processEvents(QEventLoop::AllEvents);
     EXPECT_FALSE(gotJobsInQueueCall);
 
     // submit same job but different platform:
-    details.m_jobEntry = JobEntry("d:/test", "test1.txt", "test1.txt", AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "android" ,{ "mobile", "renderer" } }, "Test Job", 1234, 3, sourceId);
+    details.m_jobEntry = JobEntry(AssetProcessor::SourceAssetReference("d:/test", "test1.txt"), AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "android" ,{ "mobile", "renderer" } }, "Test Job", 1234, 3, sourceId);
     m_rcController->JobSubmitted(details);
     QCoreApplication::processEvents(QEventLoop::AllEvents);
 
@@ -600,8 +609,7 @@ TEST_F(RCcontrollerUnitTests, TestRCController_StartRCJobWithCriticalLocking_Blo
     AZ::Uuid uuidOfSource = AZ::Uuid("{D013122E-CF2C-4534-A87D-F82570FBC2CD}");
     MockRCJob rcJob;
     AssetProcessor::JobDetails jobDetailsToInitWith;
-    jobDetailsToInitWith.m_jobEntry.m_watchFolderPath = assetRootPath.absoluteFilePath("subfolder4");
-    jobDetailsToInitWith.m_jobEntry.m_databaseSourceName = jobDetailsToInitWith.m_jobEntry.m_pathRelativeToWatchFolder = "needsLock.tiff";
+    jobDetailsToInitWith.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(fileInUsePath);
     jobDetailsToInitWith.m_jobEntry.m_platformInfo = { "pc", { "tools", "editor"} };
     jobDetailsToInitWith.m_jobEntry.m_jobKey = "Text files";
     jobDetailsToInitWith.m_jobEntry.m_sourceFileUUID = uuidOfSource;
@@ -679,8 +687,7 @@ TEST_F(RCcontrollerUnitTests, TestRCController_FeedJobsWithDependencies_Dispatch
     JobDetails jobdetailsA;
     jobdetailsA.m_scanFolder = &TestScanFolderInfo;
     jobdetailsA.m_assetBuilderDesc = m_assetBuilderDesc;
-    jobdetailsA.m_jobEntry.m_databaseSourceName = jobdetailsA.m_jobEntry.m_pathRelativeToWatchFolder = "fileA.txt";
-    jobdetailsA.m_jobEntry.m_watchFolderPath = TestScanFolderInfo.ScanPath();
+    jobdetailsA.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "fileA.txt");
     jobdetailsA.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
     jobdetailsA.m_jobEntry.m_jobKey = "TestJobA";
     jobdetailsA.m_jobEntry.m_builderGuid = BuilderUuid;
@@ -707,19 +714,15 @@ TEST_F(RCcontrollerUnitTests, TestRCController_FeedJobsWithDependencies_Dispatch
     JobDetails jobdetailsB;
     jobdetailsB.m_scanFolder = &TestScanFolderInfo;
     jobdetailsA.m_assetBuilderDesc = m_assetBuilderDesc;
-    jobdetailsB.m_jobEntry.m_databaseSourceName = jobdetailsB.m_jobEntry.m_pathRelativeToWatchFolder = "fileB.txt";
+    jobdetailsB.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "fileB.txt");
     jobdetailsB.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
-    jobdetailsB.m_jobEntry.m_watchFolderPath = TestScanFolderInfo.ScanPath();
     jobdetailsB.m_jobEntry.m_jobKey = "TestJobB";
     jobdetailsB.m_jobEntry.m_builderGuid = BuilderUuid;
 
     jobdetailsB.m_critical = true; //make jobB critical so that it will be analyzed first even though we want JobA to run first
 
-    AssetBuilderSDK::SourceFileDependency sourceFileBDependency;
-    sourceFileBDependency.m_sourceFileDependencyPath = "fileB.txt";
-
     AssetBuilderSDK::SourceFileDependency sourceFileADependency;
-    sourceFileADependency.m_sourceFileDependencyPath = "fileA.txt";
+    sourceFileADependency.m_sourceFileDependencyPath = (AZ::IO::Path(TestScanFolderInfo.ScanPath().toUtf8().constData()) / "fileA.txt").Native();
 
     // Make job B has an order job dependency on Job A
     AssetBuilderSDK::JobDependency jobDependencyA("TestJobA", "pc", AssetBuilderSDK::JobDependencyType::Order, sourceFileADependency);
@@ -773,26 +776,26 @@ TEST_F(RCcontrollerUnitTests, TestRCController_FeedJobsWithCyclicDependencies_Al
     JobDetails jobdetailsC;
     jobdetailsC.m_scanFolder = &TestScanFolderInfo;
     jobdetailsC.m_assetBuilderDesc = m_assetBuilderDesc;
-    jobdetailsC.m_jobEntry.m_databaseSourceName = jobdetailsC.m_jobEntry.m_pathRelativeToWatchFolder = "fileC.txt";
+    jobdetailsC.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "fileC.txt");
     jobdetailsC.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
-    jobdetailsC.m_jobEntry.m_watchFolderPath = TestScanFolderInfo.ScanPath();
     jobdetailsC.m_jobEntry.m_jobKey = "TestJobC";
     jobdetailsC.m_jobEntry.m_builderGuid = BuilderUuid;
 
     AssetBuilderSDK::SourceFileDependency sourceFileCDependency;
-    sourceFileCDependency.m_sourceFileDependencyPath = "fileC.txt";
+    sourceFileCDependency.m_sourceFileDependencyPath =
+        (AZ::IO::Path(TestScanFolderInfo.ScanPath().toUtf8().constData()) / "fileC.txt").Native();
 
     //Setting up Job D
     JobDetails jobdetailsD;
     jobdetailsD.m_scanFolder = &TestScanFolderInfo;
     jobdetailsD.m_assetBuilderDesc = m_assetBuilderDesc;
-    jobdetailsD.m_jobEntry.m_databaseSourceName = jobdetailsD.m_jobEntry.m_pathRelativeToWatchFolder = "fileD.txt";
+    jobdetailsD.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "fileD.txt");
     jobdetailsD.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
-    jobdetailsD.m_jobEntry.m_watchFolderPath = TestScanFolderInfo.ScanPath();
     jobdetailsD.m_jobEntry.m_jobKey = "TestJobD";
     jobdetailsD.m_jobEntry.m_builderGuid = BuilderUuid;
     AssetBuilderSDK::SourceFileDependency sourceFileDDependency;
-    sourceFileDDependency.m_sourceFileDependencyPath = "fileD.txt";
+    sourceFileDDependency.m_sourceFileDependencyPath =
+        (AZ::IO::Path(TestScanFolderInfo.ScanPath().toUtf8().constData()) / "fileD.txt").Native();
 
     //creating cyclic job order dependencies i.e  JobC and JobD have order job dependency on each other
     AssetBuilderSDK::JobDependency jobDependencyC("TestJobC", "pc", AssetBuilderSDK::JobDependencyType::Order, sourceFileCDependency);
@@ -820,7 +823,8 @@ TEST_F(RCcontrollerUnitTests, TestRCController_FeedJobsWithCyclicDependencies_Al
         int prevJobCount = m_rcJobListModel->itemCount();
         MockRCJob rcJobAddAndDelete;
         AssetProcessor::JobDetails jobDetailsToInitWithInsideScope;
-        jobDetailsToInitWithInsideScope.m_jobEntry.m_pathRelativeToWatchFolder = jobDetailsToInitWithInsideScope.m_jobEntry.m_databaseSourceName = "someFile0.txt";
+        jobDetailsToInitWithInsideScope.m_jobEntry.m_sourceAssetReference =
+            AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "someFile0.txt");
         jobDetailsToInitWithInsideScope.m_jobEntry.m_platformInfo = { "pc",{ "tools", "editor" } };
         jobDetailsToInitWithInsideScope.m_jobEntry.m_jobKey = "Text files";
         jobDetailsToInitWithInsideScope.m_jobEntry.m_sourceFileUUID = AZ::Uuid("{D013122E-CF2C-4534-A87D-F82570FBC2CD}");
@@ -830,7 +834,7 @@ TEST_F(RCcontrollerUnitTests, TestRCController_FeedJobsWithCyclicDependencies_Al
 
         // verify that job was added
         EXPECT_EQ(m_rcJobListModel->itemCount(), prevJobCount + 1);
-        m_rcController->RemoveJobsBySource("someFile0.txt");
+        m_rcController->RemoveJobsBySource(AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "someFile0.txt"));
         // verify that job was removed
         EXPECT_EQ(m_rcJobListModel->itemCount(), prevJobCount);
     }

+ 2 - 2
Code/Tools/AssetProcessor/native/utilities/ApplicationManagerBase.cpp

@@ -598,7 +598,7 @@ void ApplicationManagerBase::InitConnectionManager()
     result = QObject::connect(GetRCController(), &AssetProcessor::RCController::FileCompiled, this,
             [](AssetProcessor::JobEntry entry, AssetBuilderSDK::ProcessJobResponse /*response*/)
             {
-                AssetNotificationMessage message(entry.m_pathRelativeToWatchFolder.toUtf8().constData(), AssetNotificationMessage::JobCompleted, AZ::Data::s_invalidAssetType, entry.m_platformInfo.m_identifier.c_str());
+                AssetNotificationMessage message(entry.m_sourceAssetReference.RelativePath().c_str(), AssetNotificationMessage::JobCompleted, AZ::Data::s_invalidAssetType, entry.m_platformInfo.m_identifier.c_str());
                 EBUS_EVENT(AssetProcessor::ConnectionBus, SendPerPlatform, 0, message, QString::fromUtf8(entry.m_platformInfo.m_identifier.c_str()));
             }
             );
@@ -607,7 +607,7 @@ void ApplicationManagerBase::InitConnectionManager()
     result = QObject::connect(GetRCController(), &AssetProcessor::RCController::FileFailed, this,
             [](AssetProcessor::JobEntry entry)
             {
-                AssetNotificationMessage message(entry.m_pathRelativeToWatchFolder.toUtf8().constData(), AssetNotificationMessage::JobFailed, AZ::Data::s_invalidAssetType, entry.m_platformInfo.m_identifier.c_str());
+                AssetNotificationMessage message(entry.m_sourceAssetReference.RelativePath().c_str(), AssetNotificationMessage::JobFailed, AZ::Data::s_invalidAssetType, entry.m_platformInfo.m_identifier.c_str());
                 EBUS_EVENT(AssetProcessor::ConnectionBus, SendPerPlatform, 0, message, QString::fromUtf8(entry.m_platformInfo.m_identifier.c_str()));
             }
             );

+ 6 - 6
Code/Tools/AssetProcessor/native/utilities/AssetServerHandler.cpp

@@ -264,7 +264,7 @@ namespace AssetProcessor
 
         if (!QFile::exists(archiveAbsFilePath))
         {
-            // file does not exist on the server 
+            // file does not exist on the server
             AZ_TracePrintf(AssetProcessor::DebugChannel, "Extracting archive operation canceled. Archive does not exist on server. \n");
             return false;
         }
@@ -275,7 +275,7 @@ namespace AssetProcessor
             return false;
         }
         AZ_TracePrintf(AssetProcessor::DebugChannel, "Extracting archive for job (%s, %s, %s) with fingerprint (%u).\n",
-            builderParams.m_rcJob->GetJobEntry().m_pathRelativeToWatchFolder.toUtf8().data(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
+            builderParams.m_rcJob->GetJobEntry().m_sourceAssetReference.AbsolutePath().c_str(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
             builderParams.m_rcJob->GetPlatformInfo().m_identifier.c_str(), builderParams.m_rcJob->GetOriginalFingerprint());
         std::future<bool> extractResult;
         AzToolsFramework::ArchiveCommandsBus::BroadcastResult(extractResult,
@@ -292,7 +292,7 @@ namespace AssetProcessor
         AssetUtilities::QuitListener listener;
         listener.BusConnect();
         QString archiveAbsFilePath = ComputeArchiveFilePath(builderParams);
-        
+
         if (archiveAbsFilePath.isEmpty())
         {
             AZ_Error(AssetProcessor::DebugChannel, false, "Creating archive operation failed. Archive Absolute Path is empty. \n");
@@ -301,11 +301,11 @@ namespace AssetProcessor
 
         if (QFile::exists(archiveAbsFilePath))
         {
-            // file already exists on the server 
+            // file already exists on the server
             AZ_TracePrintf(AssetProcessor::DebugChannel, "Creating archive operation canceled. An archive of this asset already exists on server. \n");
             return true;
         }
-        
+
         if (listener.WasQuitRequested() || jobCancelListener.IsCancelled())
         {
             AZ_TracePrintf(AssetProcessor::DebugChannel, "Creating archive operation canceled. \n");
@@ -325,7 +325,7 @@ namespace AssetProcessor
         }
 
         AZ_TracePrintf(AssetProcessor::DebugChannel, "Creating archive for job (%s, %s, %s) with fingerprint (%u).\n",
-            builderParams.m_rcJob->GetJobEntry().m_pathRelativeToWatchFolder.toUtf8().data(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
+            builderParams.m_rcJob->GetJobEntry().m_sourceAssetReference.AbsolutePath().c_str(), builderParams.m_rcJob->GetJobKey().toUtf8().data(),
             builderParams.m_rcJob->GetPlatformInfo().m_identifier.c_str(), builderParams.m_rcJob->GetOriginalFingerprint());
 
         std::future<bool> createResult;

+ 33 - 0
Code/Tools/AssetProcessor/native/utilities/IPathConversion.h

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/RTTI/RTTI.h>
+
+class QString;
+
+namespace AssetProcessor
+{
+    class ScanFolderInfo;
+
+    //! Interface for requesting scanfolder/relative path info for a path
+    struct IPathConversion
+    {
+        AZ_RTTI(IPathConversion, "{8838D113-8BC0-4270-BF4D-4DFEAB628102}");
+
+        virtual ~IPathConversion() = default;
+
+        virtual bool ConvertToRelativePath(QString fullFileName, QString& databaseSourceName, QString& scanFolderName) const = 0;
+
+        //! given a full file name (assumed already fed through the normalization funciton), return the first matching scan folder
+        virtual const AssetProcessor::ScanFolderInfo* GetScanFolderForFile(const QString& fullFileName) const = 0;
+
+        virtual const AssetProcessor::ScanFolderInfo* GetScanFolderById(AZ::s64 id) const = 0;
+    };
+}

+ 15 - 2
Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp

@@ -621,7 +621,7 @@ namespace AssetProcessor
 
         return AZ::SettingsRegistryInterface::VisitResponse::Skip;
     }
- 
+
     const char AssetConfigPlatformDir[] = "AssetProcessorConfig/";
     const char AssetProcessorPlatformConfigFileName[] = "AssetProcessorPlatformConfig.ini";
 
@@ -1031,7 +1031,7 @@ namespace AssetProcessor
     bool PlatformConfiguration::ConvertToJson(const RecognizerContainer& recognizerContainer, AZStd::string& jsonText)
     {
         AZStd::unordered_map<AZStd::string, AssetCacheServerMatcher> assetCacheServerMatcherMap;
-        
+
         for (const auto& recognizer : recognizerContainer)
         {
             AssetCacheServerMatcher matcher;
@@ -1443,6 +1443,19 @@ namespace AssetProcessor
         return m_scanFolders[index];
     }
 
+    const AssetProcessor::ScanFolderInfo* PlatformConfiguration::GetScanFolderById(AZ::s64 id) const
+    {
+        auto* result = AZStd::find_if(
+            m_scanFolders.begin(),
+            m_scanFolders.end(),
+            [id](const ScanFolderInfo& scanFolder)
+            {
+                return scanFolder.ScanFolderID() == id;
+            });
+
+        return result != m_scanFolders.end() ? result : nullptr;
+    }
+
     void PlatformConfiguration::AddScanFolder(const AssetProcessor::ScanFolderInfo& source, bool isUnitTesting)
     {
         if (isUnitTesting)

+ 7 - 1
Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.h

@@ -26,6 +26,7 @@
 #include <AssetBuilderSDK/AssetBuilderSDK.h>
 #include <AzToolsFramework/Asset/AssetUtils.h>
 #endif
+#include "IPathConversion.h"
 
 
 namespace AZ
@@ -65,7 +66,7 @@ namespace AssetProcessor
             int priority,
             bool critical,
             bool supportsCreateJobs,
-            AssetBuilderSDK::FilePatternMatcher patternMatcher, 
+            AssetBuilderSDK::FilePatternMatcher patternMatcher,
             const AZStd::string& version,
             const AZ::Data::AssetType& productAssetType,
             bool outputProductDependencies,
@@ -130,9 +131,12 @@ namespace AssetProcessor
     class PlatformConfiguration
         : public QObject
         , public RecognizerConfiguration
+        , public AZ::Interface<IPathConversion>::Registrar
     {
         Q_OBJECT
     public:
+        AZ_RTTI(PlatformConfiguration, "{9F0C465D-A3A6-417E-B69C-62CBD22FD950}", RecognizerConfiguration, IPathConversion);
+
         typedef QPair<QRegExp, QString> RCSpec;
         typedef QVector<RCSpec> RCSpecList;
 
@@ -193,6 +197,8 @@ namespace AssetProcessor
         //! Retrieve the scan folder at a given index.
         const AssetProcessor::ScanFolderInfo& GetScanFolderAt(int index) const;
 
+        const AssetProcessor::ScanFolderInfo* GetScanFolderById(AZ::s64 id) const override;
+
         //!  Manually add a scan folder.  Also used for testing.
         void AddScanFolder(const AssetProcessor::ScanFolderInfo& source, bool isUnitTesting = false);
 

+ 26 - 17
Code/Tools/AssetProcessor/native/utilities/assetUtils.cpp

@@ -1039,7 +1039,7 @@ namespace AssetUtilities
                 // we do not want to include the fingerprint of dependent jobs if the job dependency type is OrderOnce.
                 continue;
             }
-            AssetProcessor::JobDesc jobDesc(jobDependencyInternal.m_jobDependency.m_sourceFile.m_sourceFileDependencyPath,
+            AssetProcessor::JobDesc jobDesc(AssetProcessor::SourceAssetReference(jobDependencyInternal.m_jobDependency.m_sourceFile.m_sourceFileDependencyPath.c_str()),
                 jobDependencyInternal.m_jobDependency.m_jobKey, jobDependencyInternal.m_jobDependency.m_platformIdentifier);
 
             for (auto builderIter = jobDependencyInternal.m_builderUuidList.begin(); builderIter != jobDependencyInternal.m_builderUuidList.end(); ++builderIter)
@@ -1175,7 +1175,8 @@ namespace AssetUtilities
 
     AZStd::string ComputeJobLogFileName(const AssetProcessor::JobEntry& jobEntry)
     {
-        return AZStd::string::format("%s-%u-%llu.log", jobEntry.m_databaseSourceName.toUtf8().constData(), jobEntry.GetHash(), jobEntry.m_jobRunKey);
+        return AZStd::string::format(
+            "%s-%u-%llu.log", jobEntry.m_sourceAssetReference.RelativePath().c_str(), jobEntry.GetHash(), jobEntry.m_jobRunKey);
     }
 
     bool CreateTempRootFolder(QString startFolder, QDir& tempRoot)
@@ -1398,11 +1399,11 @@ namespace AssetUtilities
         return (platformPrefix / relativePath).LexicallyNormal().StringAsPosix();
     }
 
-    AZStd::optional<AzToolsFramework::AssetDatabase::SourceDatabaseEntry> GetTopLevelSourceForProduct(
-        AZ::IO::PathView relativePath, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db)
+    AZStd::optional<AzToolsFramework::AssetDatabase::SourceDatabaseEntry> GetTopLevelSourceForIntermediateAsset(
+        const AssetProcessor::SourceAssetReference& sourceAsset, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db)
     {
         AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer sources;
-        db->GetSourcesByProductName(GetIntermediateAssetDatabaseName(relativePath).c_str(), sources);
+        db->GetSourcesByProductName(GetIntermediateAssetDatabaseName(sourceAsset.RelativePath()).c_str(), sources);
 
         if (sources.empty())
         {
@@ -1411,7 +1412,7 @@ namespace AssetUtilities
 
         if (sources.size() > 1)
         {
-            AZ_Error(AssetProcessor::ConsoleChannel, false, "GetTopLevelSourceForProduct found multiple sources for product %s", relativePath.FixedMaxPathStringAsPosix().c_str());
+            AZ_Error(AssetProcessor::ConsoleChannel, false, "GetTopLevelSourceForProduct found multiple sources for product %s", sourceAsset.AbsolutePath().c_str());
             return {};
         }
 
@@ -1426,22 +1427,30 @@ namespace AssetUtilities
         return source;
     }
 
-    AZStd::vector<AZStd::string> GetAllIntermediateSources(
-        AZ::IO::PathView relativeSourcePath, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db)
+    AZStd::vector<AssetProcessor::SourceAssetReference> GetAllIntermediateSources(
+        const AssetProcessor::SourceAssetReference& sourceAsset, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db)
     {
-        AZStd::vector<AZStd::string> sources;
+        AZStd::vector<AssetProcessor::SourceAssetReference> sources;
 
-        auto topLevelSource = GetTopLevelSourceForProduct(relativeSourcePath, db);
+        auto topLevelSource = GetTopLevelSourceForIntermediateAsset(sourceAsset, db);
 
         if (!topLevelSource)
         {
-            AzToolsFramework::AssetDatabase::SourceDatabaseEntry source;
-            db->GetSourceBySourceName(relativeSourcePath.FixedMaxPathStringAsPosix().c_str(), source);
+            AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer source;
+            db->GetSourcesBySourceNameScanFolderId(sourceAsset.RelativePath().c_str(), sourceAsset.ScanFolderId(), source);
 
-            topLevelSource = source;
+            if(source.empty())
+            {
+                return {};
+            }
+
+            topLevelSource = source[0];
         }
 
-        sources.emplace_back(topLevelSource->m_sourceName);
+        AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder;
+        db->GetScanFolderByScanFolderID(topLevelSource->m_scanFolderPK, scanFolder);
+
+        sources.emplace_back(scanFolder.m_scanFolder.c_str(), topLevelSource->m_sourceName.c_str());
 
         AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
         db->GetProductsBySourceID(topLevelSource->m_sourceID, products);
@@ -1453,12 +1462,12 @@ namespace AssetUtilities
 
             if ((static_cast<AssetBuilderSDK::ProductOutputFlags>(product.m_flags.to_ullong()) & AssetBuilderSDK::ProductOutputFlags::IntermediateAsset) == AssetBuilderSDK::ProductOutputFlags::IntermediateAsset)
             {
-                auto productSourceName = StripAssetPlatformNoCopy(product.m_productName);
-                sources.emplace_back(productSourceName);
+                auto productPath = ProductPath::FromDatabasePath(product.m_productName);
+                sources.emplace_back(productPath.GetIntermediatePath().c_str());
 
                 // Note: This call is intentionally re-using the products array.  The new results will be appended to the end (via push_back).
                 // The array will not be cleared.  We're essentially using products as a queue
-                db->GetProductsBySourceName(QString(QByteArray(productSourceName.data(), static_cast<int>(productSourceName.size()))), products);
+                db->GetProductsBySourceName(productPath.GetRelativePath().c_str(), products);
                 size = products.size(); // Update the loop size since the array grew
             }
         }

+ 4 - 3
Code/Tools/AssetProcessor/native/utilities/assetUtils.h

@@ -22,6 +22,7 @@
 #include <AzToolsFramework/Asset/AssetProcessorMessages.h>
 #include <AzCore/IO/Path/Path.h>
 #include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
+#include <AssetManager/SourceAssetReference.h>
 
 namespace AzToolsFramework
 {
@@ -264,11 +265,11 @@ namespace AssetUtilities
     AZStd::string GetIntermediateAssetDatabaseName(AZ::IO::PathView relativePath);
 
     //! Finds the top level source that produced an intermediate product.  If the source is not yet recorded in the database or has no top level source, this will return nothing
-    AZStd::optional<AzToolsFramework::AssetDatabase::SourceDatabaseEntry> GetTopLevelSourceForProduct(AZ::IO::PathView relativePath, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db);
+    AZStd::optional<AzToolsFramework::AssetDatabase::SourceDatabaseEntry> GetTopLevelSourceForIntermediateAsset(const AssetProcessor::SourceAssetReference& sourceAsset, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db);
 
     //! Finds all the sources (up and down) in an intermediate output chain
-    AZStd::vector<AZStd::string> GetAllIntermediateSources(
-        AZ::IO::PathView relativeSourcePath, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db);
+    AZStd::vector<AssetProcessor::SourceAssetReference> GetAllIntermediateSources(
+        const AssetProcessor::SourceAssetReference& sourceAsset, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db);
 
     //! Given a source path for an intermediate asset, constructs the product path.
     //! This does not verify either exist, it just manipulates the string.

部分文件因为文件数量过多而无法显示