UuidManager.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/RTTI/RTTI.h>
  9. #include <AzCore/std/parallel/scoped_lock.h>
  10. #include <native/utilities/UuidManager.h>
  11. #include <native/utilities/assetUtils.h>
  12. #include <Metadata/MetadataManager.h>
  13. #include <native/AssetManager/FileStateCache.h>
  14. #include "UuidManager.h"
  15. #include <QDir>
  16. namespace AssetProcessor
  17. {
  18. void UuidManager::Reflect(AZ::ReflectContext* context)
  19. {
  20. UuidSettings::Reflect(context);
  21. }
  22. void UuidSettings::Reflect(AZ::ReflectContext* context)
  23. {
  24. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  25. {
  26. serializeContext->Class<UuidSettings>()
  27. ->Version(2)
  28. ->Field("EnabledTypes", &UuidSettings::m_enabledTypes)
  29. ->Field("MetaCreationDelayMs", &UuidSettings::m_metaCreationDelayMs);
  30. }
  31. }
  32. AZ::Outcome<AZ::Uuid, AZStd::string> UuidManager::GetUuid(const SourceAssetReference& sourceAsset)
  33. {
  34. auto entry = GetOrCreateUuidEntry(sourceAsset);
  35. if (entry.IsSuccess())
  36. {
  37. return AZ::Success(entry.GetValue().m_uuid);
  38. }
  39. return AZ::Failure(entry.GetError());
  40. }
  41. AZ::Outcome<AZStd::unordered_set<AZ::Uuid>, AZStd::string> UuidManager::GetLegacyUuids(const SourceAssetReference& sourceAsset)
  42. {
  43. auto entry = GetOrCreateUuidEntry(sourceAsset);
  44. if (entry.IsSuccess())
  45. {
  46. return AZ::Success(entry.GetValue().m_legacyUuids);
  47. }
  48. return AZ::Failure(entry.GetError());
  49. }
  50. AZStd::optional<AZ::IO::Path> UuidManager::FindHighestPriorityFileByUuid(AZ::Uuid uuid)
  51. {
  52. if (auto sources = FindFilesByUuid(uuid); !sources.empty())
  53. {
  54. if (sources.size() == 1)
  55. {
  56. return sources[0];
  57. }
  58. else
  59. {
  60. // There are multiple files with the same legacy UUID, resolve to the highest priority one (highest priority scanfolder,
  61. // oldest creation time)
  62. // Convert all the paths into SourceAssetReferences which will get the scanfolder ID
  63. AZStd::vector<SourceAssetReference> sourceReferences;
  64. for (const auto& filePath : sources)
  65. {
  66. sourceReferences.emplace_back(filePath);
  67. }
  68. // Sort the list based on scanfolder ID
  69. std::stable_sort(
  70. sourceReferences.begin(),
  71. sourceReferences.end(),
  72. [](const SourceAssetReference& left, const SourceAssetReference& right)
  73. {
  74. return left.ScanFolderId() < right.ScanFolderId();
  75. });
  76. // Get the range of files from the highest priority scanfolder (having the same scanfolder ID)
  77. AZ::s64 highestPriorityScanFolder = sourceReferences.front().ScanFolderId();
  78. AZ::u64 oldestFileTime = AZStd::numeric_limits<AZ::u64>::max();
  79. SourceAssetReference* oldestFile{};
  80. // From the files in the highest priority scanfolder, pick the oldest one
  81. for (auto& source : sourceReferences)
  82. {
  83. if (source.ScanFolderId() > highestPriorityScanFolder)
  84. {
  85. // Only consider sources from the first, highest priority scanfolder
  86. break;
  87. }
  88. auto entryDetails = GetUuidDetails(source);
  89. if (entryDetails)
  90. {
  91. if (entryDetails.GetValue().m_millisecondsSinceUnixEpoch <= oldestFileTime)
  92. {
  93. oldestFile = &source;
  94. oldestFileTime = entryDetails.GetValue().m_millisecondsSinceUnixEpoch;
  95. }
  96. }
  97. }
  98. return oldestFile->AbsolutePath().c_str();
  99. }
  100. }
  101. return AZStd::nullopt;
  102. }
  103. AZStd::optional<AZ::Uuid> UuidManager::GetCanonicalUuid(AZ::Uuid legacyUuid)
  104. {
  105. if (auto result = FindHighestPriorityFileByUuid(legacyUuid); result)
  106. {
  107. if (auto details = GetUuidDetails(SourceAssetReference(result.value())); details)
  108. {
  109. return details.GetValue().m_uuid;
  110. }
  111. }
  112. return AZStd::nullopt;
  113. }
  114. AZ::Outcome<AzToolsFramework::MetaUuidEntry, AZStd::string> UuidManager::GetUuidDetails(const SourceAssetReference& sourceAsset)
  115. {
  116. return GetOrCreateUuidEntry(sourceAsset);
  117. }
  118. AZStd::vector<AZ::IO::Path> UuidManager::FindFilesByUuid(AZ::Uuid uuid)
  119. {
  120. AZStd::scoped_lock scopeLock(m_uuidMutex);
  121. auto itr = m_existingUuids.find(uuid);
  122. // First check if the UUID matches a canonical UUID.
  123. // These always have highest priority.
  124. if (itr != m_existingUuids.end())
  125. {
  126. return { itr->second };
  127. }
  128. // UUID doesn't match a canonical UUID, see if there are any matching legacy UUIDs.
  129. // In this case there could be multiple files with the same legacy UUID, so return all of them.
  130. auto range = m_existingLegacyUuids.equal_range(uuid);
  131. AZStd::vector<AZ::IO::Path> foundFiles;
  132. for (auto legacyItr = range.first; legacyItr != range.second; ++legacyItr)
  133. {
  134. foundFiles.emplace_back(legacyItr->second);
  135. }
  136. return foundFiles;
  137. }
  138. void UuidManager::FileChanged(AZ::IO::PathView file)
  139. {
  140. InvalidateCacheEntry(file);
  141. }
  142. void UuidManager::FileRemoved(AZ::IO::PathView file)
  143. {
  144. InvalidateCacheEntry(file);
  145. }
  146. void UuidManager::InvalidateCacheEntry(AZ::IO::FixedMaxPath file)
  147. {
  148. AZStd::string extension = file.Extension().Native();
  149. if (extension == AzToolsFramework::MetadataManager::MetadataFileExtension)
  150. {
  151. // Remove the metadata part of the extension since the cache is actually keyed by the source file path
  152. file.ReplaceExtension("");
  153. }
  154. AZStd::scoped_lock scopeLock(m_uuidMutex);
  155. auto normalizedPath = GetCanonicalPath(file);
  156. auto itr = m_uuids.find(normalizedPath);
  157. if (itr != m_uuids.end())
  158. {
  159. m_existingUuids.erase(itr->second.m_uuid);
  160. for (auto legacyUuid : itr->second.m_legacyUuids)
  161. {
  162. auto range = m_existingLegacyUuids.equal_range(legacyUuid);
  163. for (auto legacyItr = range.first; legacyItr != range.second; ++legacyItr)
  164. {
  165. if (legacyItr->second == file)
  166. {
  167. m_existingLegacyUuids.erase(legacyItr);
  168. break;
  169. }
  170. }
  171. }
  172. m_uuids.erase(itr);
  173. }
  174. }
  175. bool UuidManager::IsGenerationEnabledForFile(AZ::IO::PathView file)
  176. {
  177. return m_enabledTypes.contains(file.Extension().Native());
  178. }
  179. AZStd::unordered_set<AZStd::string> UuidManager::GetEnabledTypes()
  180. {
  181. return m_enabledTypes;
  182. }
  183. void UuidManager::EnableGenerationForTypes(AZStd::unordered_set<AZStd::string> types)
  184. {
  185. m_enabledTypes = AZStd::move(types);
  186. }
  187. AZ::IO::Path UuidManager::GetCanonicalPath(AZ::IO::PathView file)
  188. {
  189. return file.LexicallyNormal().FixedMaxPathStringAsPosix().c_str();
  190. }
  191. AZ::Outcome<AzToolsFramework::MetaUuidEntry, AZStd::string> UuidManager::GetOrCreateUuidEntry(const SourceAssetReference& sourceAsset)
  192. {
  193. AZStd::scoped_lock scopeLock(m_uuidMutex);
  194. AZ::IO::Path normalizedPath = GetCanonicalPath(sourceAsset.AbsolutePath());
  195. auto itr = m_uuids.find(normalizedPath);
  196. // Check if we already have the UUID loaded into memory
  197. if (itr != m_uuids.end())
  198. {
  199. return AZ::Success(itr->second);
  200. }
  201. auto* fileStateInterface = AZ::Interface<IFileStateRequests>::Get();
  202. if (!fileStateInterface)
  203. {
  204. AZ_Assert(false, "Programmer Error - IFileStateRequests interface is not available");
  205. return AZ::Failure(AZStd::string("Programmer Error - IFileStateRequests interface is not available"));
  206. }
  207. if (!fileStateInterface->Exists(sourceAsset.AbsolutePath().c_str()))
  208. {
  209. AZ_Error(
  210. "UuidManager",
  211. false,
  212. "Programmer Error - cannot request UUID for file which does not exist - %s",
  213. sourceAsset.AbsolutePath().c_str());
  214. return AZ::Failure(AZStd::string("Programmer Error - cannot request UUID for file which does not exist"));
  215. }
  216. const AZ::IO::Path metadataFilePath = AzToolsFramework::MetadataManager::ToMetadataPath(sourceAsset.AbsolutePath().c_str());
  217. AssetProcessor::FileStateInfo metadataFileInfo;
  218. bool metadataFileExists = fileStateInterface->GetFileInfo(metadataFilePath.c_str(), &metadataFileInfo);
  219. const bool isEnabledType = m_enabledTypes.contains(sourceAsset.AbsolutePath().Extension().Native());
  220. if constexpr (ASSETPROCESSOR_TRAIT_CASE_SENSITIVE_FILESYSTEM)
  221. {
  222. // On case sensitive filesystems, the above exists check will fail if the case is not correct
  223. // In that case, try to update the case to determine if the file actually exists
  224. if (!metadataFileExists)
  225. {
  226. QString parentPath = QString::fromStdString(AZStd::string(metadataFilePath.ParentPath().Native()).c_str());
  227. QString caseCorrectedMetadataRelPath = QString::fromStdString(AZStd::string(metadataFilePath.Filename().Native()).c_str());
  228. // in this case, we got the file name and path from a real existing file that has already got the correct case
  229. // so the only case correction we may need to do is for the last part (the meta file name)
  230. // so we can set the checkEntirePath param to false.
  231. metadataFileExists = AssetUtilities::UpdateToCorrectCase(parentPath, caseCorrectedMetadataRelPath, false);
  232. if (metadataFileExists)
  233. {
  234. AZ::IO::FixedMaxPath correctAbsolutePath(parentPath.toUtf8().constData());
  235. correctAbsolutePath /= caseCorrectedMetadataRelPath.toUtf8().constData();
  236. fileStateInterface->GetFileInfo(correctAbsolutePath.c_str(), &metadataFileInfo);
  237. }
  238. }
  239. }
  240. // Metadata manager can't use the file state cache since it is in AzToolsFramework, so it's faster to do an Exists check up-front.
  241. if (metadataFileExists)
  242. {
  243. AzToolsFramework::MetaUuidEntry uuidInfo;
  244. // Check if the path computed based on the source asset's filename is different from the on-disk path
  245. if (metadataFileInfo.m_absolutePath.compare(metadataFilePath.c_str()) != 0)
  246. {
  247. // Metadata filename case does not match source filename case
  248. // Rename the metadata file to match
  249. AZ::IO::FileIOBase::GetInstance()->Rename(
  250. metadataFileInfo.m_absolutePath.toUtf8().constData(), metadataFilePath.c_str());
  251. }
  252. // Check if there's a metadata file that already contains a saved UUID
  253. if (GetMetadataManager()->GetValue(sourceAsset.AbsolutePath(), AzToolsFramework::UuidUtilComponent::UuidKey, uuidInfo))
  254. {
  255. // Validate the entry - a null UUID is not ok
  256. if (uuidInfo.m_uuid.IsNull())
  257. {
  258. return AZ::Failure(AZStd::string::format(
  259. "Metadata file exists for %s but UUID is missing or invalid", sourceAsset.AbsolutePath().c_str()));
  260. }
  261. // Missing other entries is ok, just generate them now and update the metadata file
  262. if (uuidInfo.m_legacyUuids.empty() || uuidInfo.m_originalPath.empty() || uuidInfo.m_millisecondsSinceUnixEpoch == 0)
  263. {
  264. AzToolsFramework::MetaUuidEntry regeneratedEntry = CreateUuidEntry(sourceAsset, isEnabledType);
  265. if (uuidInfo.m_legacyUuids.empty())
  266. {
  267. uuidInfo.m_legacyUuids = regeneratedEntry.m_legacyUuids;
  268. }
  269. if (uuidInfo.m_originalPath.empty())
  270. {
  271. uuidInfo.m_originalPath = regeneratedEntry.m_originalPath;
  272. }
  273. if (uuidInfo.m_millisecondsSinceUnixEpoch == 0)
  274. {
  275. uuidInfo.m_millisecondsSinceUnixEpoch = regeneratedEntry.m_millisecondsSinceUnixEpoch;
  276. }
  277. // Update the metadata file
  278. GetMetadataManager()->SetValue(sourceAsset.AbsolutePath(), AzToolsFramework::UuidUtilComponent::UuidKey, uuidInfo);
  279. }
  280. auto outcome = CacheUuidEntry(normalizedPath, uuidInfo, isEnabledType);
  281. if (outcome)
  282. {
  283. return uuidInfo;
  284. }
  285. else
  286. {
  287. return AZ::Failure(outcome.GetError());
  288. }
  289. }
  290. }
  291. // Last resort - generate a new UUID and save it to the metadata file
  292. AzToolsFramework::MetaUuidEntry newUuid = CreateUuidEntry(sourceAsset, isEnabledType);
  293. if (!isEnabledType ||
  294. GetMetadataManager()->SetValue(sourceAsset.AbsolutePath(), AzToolsFramework::UuidUtilComponent::UuidKey, newUuid))
  295. {
  296. auto outcome = CacheUuidEntry(normalizedPath, newUuid, isEnabledType);
  297. if (outcome)
  298. {
  299. return newUuid;
  300. }
  301. else
  302. {
  303. return AZ::Failure(outcome.GetError());
  304. }
  305. }
  306. return AZ::Failure(AZStd::string::format("Failed to save UUID to metadata file - %s", sourceAsset.AbsolutePath().c_str()));
  307. }
  308. AzToolsFramework::IMetadataRequests* UuidManager::GetMetadataManager()
  309. {
  310. if (!m_metadataManager)
  311. {
  312. m_metadataManager = AZ::Interface<AzToolsFramework::IMetadataRequests>::Get();
  313. }
  314. return m_metadataManager;
  315. }
  316. AzToolsFramework::MetaUuidEntry UuidManager::CreateUuidEntry(const SourceAssetReference& sourceAsset, bool enabledType)
  317. {
  318. AzToolsFramework::MetaUuidEntry newUuid;
  319. newUuid.m_uuid = enabledType ? CreateUuid() : AssetUtilities::CreateSafeSourceUUIDFromName(sourceAsset.RelativePath().c_str());
  320. newUuid.m_legacyUuids = CreateLegacyUuids(sourceAsset.RelativePath().c_str());
  321. newUuid.m_originalPath = sourceAsset.RelativePath().c_str();
  322. newUuid.m_millisecondsSinceUnixEpoch = enabledType ? aznumeric_cast<AZ::u64>(QDateTime::currentMSecsSinceEpoch()) : 0;
  323. return newUuid;
  324. }
  325. AZ::Outcome<void, AZStd::string> UuidManager::CacheUuidEntry(AZ::IO::PathView normalizedPath, AzToolsFramework::MetaUuidEntry entry, bool enabledType)
  326. {
  327. if (enabledType)
  328. {
  329. auto result = m_existingUuids.emplace(entry.m_uuid, normalizedPath);
  330. if (!result.second)
  331. {
  332. // Insertion failure means this UUID is duplicated
  333. return AZ::Failure(AZStd::string::format(
  334. "Source " AZ_STRING_FORMAT " has duplicate UUID " AZ_STRING_FORMAT
  335. " which is already assigned to another asset " AZ_STRING_FORMAT ". "
  336. "Every asset must have a unique ID. Please change the UUID for one of these assets to resolve the conflict.",
  337. AZ_STRING_ARG(normalizedPath.Native()),
  338. AZ_STRING_ARG(entry.m_uuid.ToFixedString()),
  339. AZ_STRING_ARG(result.first->second.Native())));
  340. }
  341. for (const auto& legacyUuid : entry.m_legacyUuids)
  342. {
  343. m_existingLegacyUuids.emplace(legacyUuid, normalizedPath);
  344. }
  345. }
  346. m_uuids[normalizedPath] = AZStd::move(entry);
  347. return AZ::Success();
  348. }
  349. AZ::Uuid UuidManager::CreateUuid()
  350. {
  351. constexpr int MaxRetry = 50;
  352. auto uuid = AZ::Uuid::CreateRandom();
  353. int retry = 0;
  354. while (m_existingUuids.contains(uuid) && retry < MaxRetry)
  355. {
  356. uuid = AZ::Uuid::CreateRandom();
  357. ++retry;
  358. }
  359. if (retry >= MaxRetry)
  360. {
  361. AZ_Error("UuidManager", false, "Failed to randomly generate a unique UUID after %d attempts. UUID not assigned.", retry);
  362. return AZ::Uuid::CreateNull();
  363. }
  364. return uuid;
  365. }
  366. AZStd::unordered_set<AZ::Uuid> UuidManager::CreateLegacyUuids(const AZStd::string& relativePath)
  367. {
  368. return { AssetUtilities::CreateSafeSourceUUIDFromName(relativePath.c_str()),
  369. AssetUtilities::CreateSafeSourceUUIDFromName(relativePath.c_str(), false) };
  370. }
  371. } // namespace AssetProcessor