ProductAssetTreeModel.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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 "ProductAssetTreeModel.h"
  9. #include "ProductAssetTreeItemData.h"
  10. #include <AssetBuilderSDK/AssetBuilderSDK.h>
  11. #include <AzCore/Component/TickBus.h>
  12. #include <AzCore/IO/Path/Path.h>
  13. #include <AzFramework/StringFunc/StringFunc.h>
  14. #include <AzCore/Console/IConsole.h>
  15. namespace AssetProcessor
  16. {
  17. AZ_CVAR_EXTERNED(bool, ap_disableAssetTreeView);
  18. ProductAssetTreeModel::ProductAssetTreeModel(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> sharedDbConnection, QObject *parent) :
  19. AssetTreeModel(sharedDbConnection, parent)
  20. {
  21. }
  22. ProductAssetTreeModel::~ProductAssetTreeModel()
  23. {
  24. }
  25. void ProductAssetTreeModel::ResetModel()
  26. {
  27. if (ap_disableAssetTreeView)
  28. {
  29. return;
  30. }
  31. m_productToTreeItem.clear();
  32. m_productIdToTreeItem.clear();
  33. AZStd::string databaseLocation;
  34. AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Broadcast(&AzToolsFramework::AssetDatabase::AssetDatabaseRequests::GetAssetDatabaseLocation, databaseLocation);
  35. if (databaseLocation.empty())
  36. {
  37. return;
  38. }
  39. m_sharedDbConnection->QueryCombined(
  40. [&](AzToolsFramework::AssetDatabase::CombinedDatabaseEntry& combined)
  41. {
  42. AddOrUpdateEntry(combined, true);
  43. return true;
  44. });
  45. }
  46. void ProductAssetTreeModel::OnProductFileChanged(const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& entry)
  47. {
  48. if (ap_disableAssetTreeView)
  49. {
  50. return;
  51. }
  52. if (!m_root)
  53. {
  54. // no need to update the model if the root hasn't been created via ResetModel()
  55. return;
  56. }
  57. // Model changes need to be run on the main thread.
  58. AZ::SystemTickBus::QueueFunction([&, entry]()
  59. {
  60. m_sharedDbConnection->QueryCombinedByProductID(
  61. entry.m_productID,
  62. [this](const AzToolsFramework::AssetDatabase::CombinedDatabaseEntry& combined)
  63. {
  64. AddOrUpdateEntry(combined, false);
  65. return false; // Should only be 1 entry for 1 product
  66. });
  67. });
  68. }
  69. void ProductAssetTreeModel::RemoveAsset(AZ::s64 productId)
  70. {
  71. auto existingProduct = m_productIdToTreeItem.find(productId);
  72. if (existingProduct == m_productIdToTreeItem.end() || !existingProduct->second)
  73. {
  74. // If the product being removed wasn't cached, then something has gone wrong. Reset the model.
  75. Reset();
  76. return;
  77. }
  78. RemoveAssetTreeItem(existingProduct->second);
  79. }
  80. void ProductAssetTreeModel::RemoveAssetTreeItem(AssetTreeItem* assetToRemove)
  81. {
  82. if (!assetToRemove)
  83. {
  84. return;
  85. }
  86. AssetTreeItem* parent = assetToRemove->GetParent();
  87. if (!parent)
  88. {
  89. return;
  90. }
  91. QModelIndex parentIndex = createIndex(parent->GetRow(), 0, parent);
  92. Q_ASSERT(checkIndex(parentIndex));
  93. beginRemoveRows(parentIndex, assetToRemove->GetRow(), assetToRemove->GetRow());
  94. m_productToTreeItem.erase(assetToRemove->GetData()->m_assetDbName);
  95. const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(assetToRemove->GetData());
  96. if (productItemData && productItemData->m_hasDatabaseInfo)
  97. {
  98. m_productIdToTreeItem.erase(productItemData->m_databaseInfo.m_productID);
  99. }
  100. parent->EraseChild(assetToRemove);
  101. endRemoveRows();
  102. RemoveFoldersIfEmpty(parent);
  103. }
  104. void ProductAssetTreeModel::RemoveFoldersIfEmpty(AssetTreeItem* itemToCheck)
  105. {
  106. // Don't attempt to remove invalid items, non-folders, folders that still have items in them, or the root.
  107. if (!itemToCheck || !itemToCheck->GetData()->m_isFolder || itemToCheck->getChildCount() > 0 || !itemToCheck->GetParent())
  108. {
  109. return;
  110. }
  111. RemoveAssetTreeItem(itemToCheck);
  112. }
  113. void ProductAssetTreeModel::OnProductFileRemoved(AZ::s64 productId)
  114. {
  115. if (ap_disableAssetTreeView)
  116. {
  117. return;
  118. }
  119. if (!m_root)
  120. {
  121. // we haven't reset the model yet, which means all of this will happen when we do.
  122. return;
  123. }
  124. // UI changes need to be done on the main thread.
  125. AZ::SystemTickBus::QueueFunction([&, productId]()
  126. {
  127. RemoveAsset(productId);
  128. });
  129. }
  130. void ProductAssetTreeModel::OnProductFilesRemoved(const AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& products)
  131. {
  132. if (ap_disableAssetTreeView)
  133. {
  134. return;
  135. }
  136. if (!m_root)
  137. {
  138. // we haven't reset the model yet, which means all of this will happen when we do.
  139. return;
  140. }
  141. // UI changes need to be done on the main thread.
  142. AZ::SystemTickBus::QueueFunction([&, products]()
  143. {
  144. for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
  145. {
  146. RemoveAsset(product.m_productID);
  147. }
  148. });
  149. }
  150. QModelIndex ProductAssetTreeModel::GetIndexForProduct(const AZStd::string& product)
  151. {
  152. if (ap_disableAssetTreeView)
  153. {
  154. return QModelIndex();
  155. }
  156. auto productItem = m_productToTreeItem.find(product);
  157. if (productItem == m_productToTreeItem.end())
  158. {
  159. return QModelIndex();
  160. }
  161. return createIndex(productItem->second->GetRow(), 0, productItem->second);
  162. }
  163. void ProductAssetTreeModel::AddOrUpdateEntry(
  164. const AzToolsFramework::AssetDatabase::CombinedDatabaseEntry& combinedDatabaseEntry,
  165. bool modelIsResetting)
  166. {
  167. // Intermediate assets are functionally source assets, output as products from other source assets.
  168. // Don't display them in the product assets tab.
  169. if (combinedDatabaseEntry.m_flags.test(static_cast<int>(AssetBuilderSDK::ProductOutputFlags::IntermediateAsset)))
  170. {
  171. return;
  172. }
  173. const auto& existingEntry = m_productIdToTreeItem.find(combinedDatabaseEntry.m_productID);
  174. if (existingEntry != m_productIdToTreeItem.end())
  175. {
  176. AZStd::shared_ptr<ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<ProductAssetTreeItemData>(existingEntry->second->GetData());
  177. // This item already exists, refresh the related data.
  178. productItemData->m_databaseInfo = combinedDatabaseEntry; // Intentional object slicing occuring here. CombinedEntry -> ProductEntry
  179. CheckForUnresolvedIssues(productItemData);
  180. QModelIndex existingIndexStart = createIndex(existingEntry->second->GetRow(), 0, existingEntry->second);
  181. QModelIndex existingIndexEnd = createIndex(existingEntry->second->GetRow(), existingEntry->second->GetColumnCount() - 1, existingEntry->second);
  182. Q_ASSERT(checkIndex(existingIndexStart));
  183. Q_ASSERT(checkIndex(existingIndexEnd));
  184. dataChanged(existingIndexStart, existingIndexEnd);
  185. return;
  186. }
  187. AZ::IO::Path productNamePath(combinedDatabaseEntry.m_productName, AZ::IO::PosixPathSeparator);
  188. if (productNamePath.empty())
  189. {
  190. AZ_Warning(
  191. "AssetProcessor",
  192. false,
  193. "Product id %d has an invalid name: %s",
  194. combinedDatabaseEntry.m_productID,
  195. combinedDatabaseEntry.m_productName.c_str());
  196. return;
  197. }
  198. AssetTreeItem* parentItem = m_root.get();
  199. AZ::IO::Path currentFullFolderPath;
  200. const AZ::IO::PathView filename = productNamePath.Filename();
  201. const AZ::IO::PathView fullPathWithoutFilename = productNamePath.RemoveFilename();
  202. AZ::IO::FixedMaxPathString currentPath;
  203. for (auto pathIt = fullPathWithoutFilename.begin(); pathIt != fullPathWithoutFilename.end(); ++pathIt)
  204. {
  205. currentPath = pathIt->FixedMaxPathString();
  206. currentFullFolderPath /= currentPath;
  207. AssetTreeItem* nextParent = parentItem->GetChildFolder(currentPath.c_str());
  208. if (!nextParent)
  209. {
  210. if (!modelIsResetting)
  211. {
  212. QModelIndex parentIndex = parentItem == m_root.get() ? QModelIndex() : createIndex(parentItem->GetRow(), 0, parentItem);
  213. Q_ASSERT(checkIndex(parentIndex));
  214. beginInsertRows(parentIndex, parentItem->getChildCount(), parentItem->getChildCount());
  215. }
  216. nextParent = parentItem->CreateChild(ProductAssetTreeItemData::MakeShared(
  217. nullptr,
  218. currentFullFolderPath.Native(),
  219. currentPath.c_str(),
  220. true,
  221. AZ::Uuid::CreateNull(),
  222. AzToolsFramework::AssetDatabase::InvalidEntryId));
  223. m_productToTreeItem[currentFullFolderPath.Native()] = nextParent;
  224. // m_productIdToTreeItem is not used for folders, folders don't have product IDs.
  225. if (!modelIsResetting)
  226. {
  227. endInsertRows();
  228. }
  229. }
  230. parentItem = nextParent;
  231. }
  232. AZ::Uuid sourceId = combinedDatabaseEntry.m_sourceGuid;
  233. AZ::s64 scanFolderID = combinedDatabaseEntry.m_scanFolderPK;
  234. if (!modelIsResetting)
  235. {
  236. QModelIndex parentIndex = parentItem == m_root.get() ? QModelIndex() : createIndex(parentItem->GetRow(), 0, parentItem);
  237. Q_ASSERT(checkIndex(parentIndex));
  238. beginInsertRows(parentIndex, parentItem->getChildCount(), parentItem->getChildCount());
  239. }
  240. AZStd::shared_ptr<ProductAssetTreeItemData> productItemData = ProductAssetTreeItemData::MakeShared(
  241. &combinedDatabaseEntry,
  242. combinedDatabaseEntry.m_productName,
  243. AZ::IO::FixedMaxPathString(filename.Native()).c_str(),
  244. false,
  245. sourceId,
  246. scanFolderID);
  247. m_productToTreeItem[combinedDatabaseEntry.m_productName] =
  248. parentItem->CreateChild(productItemData);
  249. m_productIdToTreeItem[combinedDatabaseEntry.m_productID] = m_productToTreeItem[combinedDatabaseEntry.m_productName];
  250. CheckForUnresolvedIssues(productItemData);
  251. if (!modelIsResetting)
  252. {
  253. endInsertRows();
  254. }
  255. }
  256. void ProductAssetTreeModel::CheckForUnresolvedIssues(AZStd::shared_ptr<ProductAssetTreeItemData> productItemData)
  257. {
  258. productItemData->m_assetHasUnresolvedIssue = false;
  259. // Start by clearing the tooltip, so any errors don't append to the existing text.
  260. productItemData->m_unresolvedIssuesTooltip = QString();
  261. if (!productItemData->m_hasDatabaseInfo)
  262. {
  263. // Folders can't have unresolved issues.
  264. return;
  265. }
  266. m_sharedDbConnection->QueryMissingProductDependencyByProductId(
  267. productItemData->m_databaseInfo.m_productID,
  268. [&, productItemData](AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry& missingDependency)
  269. {
  270. if (missingDependency.m_dependencySourceGuid.IsNull())
  271. {
  272. // This was an empty row that likely included information like the last time this file was scanned.
  273. // Don't mark this product as having unresolved issues, and return true to continue looking through the scan results.
  274. return true;
  275. }
  276. // If this asset has any missing dependencies, mark it as having an unresolved issue.
  277. productItemData->m_assetHasUnresolvedIssue = true;
  278. productItemData->m_unresolvedIssuesTooltip = tr("A missing product dependency has been detected for this asset.");
  279. return false; // Don't keep iterating, an unresolved issue was found.
  280. });
  281. }
  282. }