2
0

RCQueueSortModel.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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 <native/resourcecompiler/RCQueueSortModel.h>
  9. #include <native/AssetDatabase/AssetDatabase.h>
  10. #include "rcjoblistmodel.h"
  11. namespace AssetProcessor
  12. {
  13. RCQueueSortModel::RCQueueSortModel(QObject* parent)
  14. : QSortFilterProxyModel(parent)
  15. {
  16. // jobs assigned to "all" platforms are always active.
  17. m_currentlyConnectedPlatforms.insert(QString("all"));
  18. }
  19. void RCQueueSortModel::AttachToModel(RCJobListModel* target)
  20. {
  21. if (target)
  22. {
  23. setDynamicSortFilter(true);
  24. BusConnect();
  25. m_sourceModel = target;
  26. setSourceModel(target);
  27. setSortRole(RCJobListModel::jobIndexRole);
  28. sort(0);
  29. }
  30. else
  31. {
  32. BusDisconnect();
  33. setSourceModel(nullptr);
  34. m_sourceModel = nullptr;
  35. }
  36. }
  37. RCJob* RCQueueSortModel::GetNextPendingJob()
  38. {
  39. if (m_dirtyNeedsResort)
  40. {
  41. setDynamicSortFilter(false);
  42. QSortFilterProxyModel::sort(0);
  43. setDynamicSortFilter(true);
  44. m_dirtyNeedsResort = false;
  45. }
  46. RCJob* anyPendingJob = nullptr;
  47. bool waitingOnCatalog = false; // If we find an asset thats waiting on the catalog, don't assume there's a cyclic dependency. We'll wait until the catalog is updated and then check again.
  48. for (int idx = 0; idx < rowCount(); ++idx)
  49. {
  50. QModelIndex parentIndex = mapToSource(index(idx, 0));
  51. RCJob* actualJob = m_sourceModel->getItem(parentIndex.row());
  52. if ((actualJob) && (actualJob->GetState() == RCJob::pending))
  53. {
  54. // If this job has a missing dependency, and there are any jobs in flight,
  55. // don't queue it until those jobs finish, in case they resolve the dependency.
  56. // This does mean that if there are multiple queued jobs with missing dependencies,
  57. // they'll run one at a time instead of in parallel, while waiting for the missing dependency
  58. // to be potentially resolved.
  59. if (actualJob->HasMissingSourceDependency() &&
  60. (m_sourceModel->jobsInFlight() > 0 || m_sourceModel->jobsInQueueWithoutMissingDependencies() > 0 ||
  61. m_sourceModel->jobsPendingCatalog() > 0))
  62. {
  63. // There is a race condition where this can fail:
  64. // Asset A generates an intermediate asset.
  65. // Asset B has a source dependency on that intermediate asset. Asset B's "HasMissingSourceDependency" flag is true.
  66. // Asset A is the last job in the queue without a missing job/source dependency, so it runs.
  67. // Asset A finishes processing job, and outputs the product.
  68. // Intermediate A has not yet been scanned and discovered by Asset Processor, so it's not in flight or in the queue yet.
  69. // Asset Processor goes to pull the next job in the queue. Asset B still technically has a missing job dependency on the intermediate asset output.
  70. // Asset B gets pulled from the queue to process here, even though Intermediate A hasn't run yet, because Intermediate A hasn't gone into the queue yet.
  71. // This happened with FBX files and a dependency on an intermediate asset materialtype, before Common platform jobs were made higher priority than host platform jobs.
  72. // Why not just check if the target file exists at this point? Because the job key has to match up.
  73. // When a check was added here to see if all the dependency files existed, other material jobs started to end up in the queue indefinitely
  74. // because they had a dependency on a file that existed but a job key that did not exist for that file.
  75. continue;
  76. }
  77. if (actualJob->HasMissingSourceDependency())
  78. {
  79. AZ_Warning(
  80. AssetProcessor::ConsoleChannel,
  81. false,
  82. "No job was found to match the job dependency criteria declared by file %s.\n"
  83. "This may be due to a mismatched job key.\n"
  84. "Job ordering will not be guaranteed and could result in errors or unexpected output.",
  85. actualJob->GetJobEntry().GetAbsoluteSourcePath().toUtf8().constData());
  86. }
  87. bool canProcessJob = true;
  88. for (const JobDependencyInternal& jobDependencyInternal : actualJob->GetJobDependencies())
  89. {
  90. if (jobDependencyInternal.m_jobDependency.m_type == AssetBuilderSDK::JobDependencyType::Order ||
  91. jobDependencyInternal.m_jobDependency.m_type == AssetBuilderSDK::JobDependencyType::OrderOnce ||
  92. jobDependencyInternal.m_jobDependency.m_type == AssetBuilderSDK::JobDependencyType::OrderOnly)
  93. {
  94. const AssetBuilderSDK::JobDependency& jobDependency = jobDependencyInternal.m_jobDependency;
  95. AZ_Assert(
  96. AZ::IO::PathView(jobDependency.m_sourceFile.m_sourceFileDependencyPath).IsAbsolute(),
  97. "Dependency path %s is not an absolute path",
  98. jobDependency.m_sourceFile.m_sourceFileDependencyPath.c_str());
  99. QueueElementID elementId(
  100. SourceAssetReference(jobDependency.m_sourceFile.m_sourceFileDependencyPath.c_str()),
  101. jobDependency.m_platformIdentifier.c_str(),
  102. jobDependency.m_jobKey.c_str());
  103. if (m_sourceModel->isInFlight(elementId) || m_sourceModel->isInQueue(elementId))
  104. {
  105. canProcessJob = false;
  106. if (!anyPendingJob || (anyPendingJob->HasMissingSourceDependency() && !actualJob->HasMissingSourceDependency()))
  107. {
  108. anyPendingJob = actualJob;
  109. }
  110. }
  111. else if(m_sourceModel->isWaitingOnCatalog(elementId))
  112. {
  113. canProcessJob = false;
  114. waitingOnCatalog = true;
  115. }
  116. }
  117. }
  118. if (canProcessJob)
  119. {
  120. return actualJob;
  121. }
  122. }
  123. }
  124. // Either there are no jobs to do or there is a cyclic order job dependency.
  125. if (anyPendingJob && m_sourceModel->jobsInFlight() == 0 && !waitingOnCatalog)
  126. {
  127. AZ_Warning(AssetProcessor::DebugChannel, false, " Cyclic job order dependency detected. Processing job (%s, %s, %s, %s) to unblock.",
  128. anyPendingJob->GetJobEntry().m_sourceAssetReference.AbsolutePath().c_str(), anyPendingJob->GetJobKey().toUtf8().data(),
  129. anyPendingJob->GetJobEntry().m_platformInfo.m_identifier.c_str(), anyPendingJob->GetBuilderGuid().ToString<AZStd::string>().c_str());
  130. return anyPendingJob;
  131. }
  132. else
  133. {
  134. return nullptr;
  135. }
  136. }
  137. bool RCQueueSortModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
  138. {
  139. (void)source_parent;
  140. RCJob* actualJob = m_sourceModel->getItem(source_row);
  141. if (!actualJob)
  142. {
  143. return false;
  144. }
  145. if (actualJob->GetState() != RCJob::pending)
  146. {
  147. return false;
  148. }
  149. return true;
  150. }
  151. bool RCQueueSortModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
  152. {
  153. RCJob* leftJob = m_sourceModel->getItem(left.row());
  154. RCJob* rightJob = m_sourceModel->getItem(right.row());
  155. // auto fail jobs always take priority to give user feedback asap.
  156. bool autoFailLeft = leftJob->IsAutoFail();
  157. bool autoFailRight = rightJob->IsAutoFail();
  158. if (autoFailLeft)
  159. {
  160. if (!autoFailRight)
  161. {
  162. return true; // left before right
  163. }
  164. }
  165. else if (autoFailRight)
  166. {
  167. return false; // right before left.
  168. }
  169. // Check if either job was marked as having a missing source dependency.
  170. // This means that the job is looking for another source asset and job to exist before it runs, but
  171. // that source doesn't exist yet. Those jobs are deferred to run later, in case the dependency eventually shows up.
  172. // The dependency may be on an intermediate asset that will be generated later in asset processing.
  173. if (leftJob->HasMissingSourceDependency() != rightJob->HasMissingSourceDependency())
  174. {
  175. if (rightJob->HasMissingSourceDependency())
  176. {
  177. return true; // left does not have a missing source dependency, but right does, so left wins.
  178. }
  179. return false; // Right does not have a missing source dependency, but left does, so right wins.
  180. }
  181. // Common platform jobs generate intermediate assets. These generate additional jobs, and the intermediate assets
  182. // can be source and/or job dependencies for other, queued assets.
  183. // Run intermediate assets before active platform and host platform jobs.
  184. // Critical jobs should run first, so skip the comparison here if either job is critical, to allow criticality to come in first.
  185. bool platformsMatch = leftJob->GetPlatformInfo().m_identifier == rightJob->GetPlatformInfo().m_identifier;
  186. // first thing to check is in platform.
  187. if (!platformsMatch)
  188. {
  189. if (leftJob->GetPlatformInfo().m_identifier == AssetBuilderSDK::CommonPlatformName)
  190. {
  191. return true;
  192. }
  193. if (rightJob->GetPlatformInfo().m_identifier == AssetBuilderSDK::CommonPlatformName)
  194. {
  195. return false;
  196. }
  197. bool leftActive = m_currentlyConnectedPlatforms.contains(leftJob->GetPlatformInfo().m_identifier.c_str());
  198. bool rightActive = m_currentlyConnectedPlatforms.contains(rightJob->GetPlatformInfo().m_identifier.c_str());
  199. if (leftActive)
  200. {
  201. if (!rightActive)
  202. {
  203. return true; // left before right
  204. }
  205. }
  206. else if (rightActive)
  207. {
  208. return false; // right before left.
  209. }
  210. }
  211. // critical jobs take priority
  212. if (leftJob->IsCritical())
  213. {
  214. if (!rightJob->IsCritical())
  215. {
  216. return true; // left wins.
  217. }
  218. }
  219. else if (rightJob->IsCritical())
  220. {
  221. return false; // right wins
  222. }
  223. int leftJobEscalation = leftJob->JobEscalation();
  224. int rightJobEscalation = rightJob->JobEscalation();
  225. // This function, even though its called lessThan(), really is asking, does LEFT come before RIGHT
  226. // The higher the escalation, the more important the request, and thus the sooner we want to process the job
  227. // Which means if left has a higher escalation number than right, its LESS THAN right.
  228. if (leftJobEscalation != rightJobEscalation)
  229. {
  230. return leftJobEscalation > rightJobEscalation;
  231. }
  232. // arbitrarily, lets have PC get done first since pc-format assets are what the editor uses.
  233. if (!platformsMatch)
  234. {
  235. if (leftJob->GetPlatformInfo().m_identifier == AzToolsFramework::AssetSystem::GetHostAssetPlatform())
  236. {
  237. return true; // left wins.
  238. }
  239. if (rightJob->GetPlatformInfo().m_identifier == AzToolsFramework::AssetSystem::GetHostAssetPlatform())
  240. {
  241. return false; // right wins
  242. }
  243. }
  244. int priorityLeft = leftJob->GetPriority();
  245. int priorityRight = rightJob->GetPriority();
  246. if (priorityLeft != priorityRight)
  247. {
  248. return priorityLeft > priorityRight;
  249. }
  250. if (leftJob->GetJobEntry().m_sourceAssetReference == rightJob->GetJobEntry().m_sourceAssetReference)
  251. {
  252. // If there are two jobs for the same source, then sort by job run key.
  253. return leftJob->GetJobEntry().m_jobRunKey < rightJob->GetJobEntry().m_jobRunKey;
  254. }
  255. // if we get all the way down here it means we're dealing with two assets which are not
  256. // in any compile groups, not a priority platform, not a priority type, priority platform, etc.
  257. // we can arrange these any way we want, but must pick at least a stable order.
  258. return leftJob->GetJobEntry().GetAbsoluteSourcePath() < rightJob->GetJobEntry().GetAbsoluteSourcePath();
  259. }
  260. void RCQueueSortModel::AssetProcessorPlatformConnected(const AZStd::string platform)
  261. {
  262. QMetaObject::invokeMethod(this, "ProcessPlatformChangeMessage", Qt::QueuedConnection, Q_ARG(QString, QString::fromUtf8(platform.c_str())), Q_ARG(bool, true));
  263. }
  264. void RCQueueSortModel::AssetProcessorPlatformDisconnected(const AZStd::string platform)
  265. {
  266. QMetaObject::invokeMethod(this, "ProcessPlatformChangeMessage", Qt::QueuedConnection, Q_ARG(QString, QString::fromUtf8(platform.c_str())), Q_ARG(bool, false));
  267. }
  268. void RCQueueSortModel::ProcessPlatformChangeMessage(QString platformName, bool connected)
  269. {
  270. AZ_TracePrintf(AssetProcessor::DebugChannel, "RCQueueSortModel: Platform %s has %s.", platformName.toUtf8().data(), connected ? "connected" : "disconnected");
  271. m_dirtyNeedsResort = true;
  272. if (connected)
  273. {
  274. m_currentlyConnectedPlatforms.insert(platformName);
  275. }
  276. else
  277. {
  278. m_currentlyConnectedPlatforms.remove(platformName);
  279. }
  280. }
  281. void RCQueueSortModel::AddJobIdEntry(AssetProcessor::RCJob* rcJob)
  282. {
  283. m_currentJobRunKeyToJobEntries[rcJob->GetJobEntry().m_jobRunKey] = rcJob;
  284. }
  285. void RCQueueSortModel::RemoveJobIdEntry(AssetProcessor::RCJob* rcJob)
  286. {
  287. m_currentJobRunKeyToJobEntries.erase(rcJob->GetJobEntry().m_jobRunKey);
  288. }
  289. void RCQueueSortModel::OnEscalateJobs(AssetProcessor::JobIdEscalationList jobIdEscalationList)
  290. {
  291. for (const auto& jobIdEscalationPair : jobIdEscalationList)
  292. {
  293. auto found = m_currentJobRunKeyToJobEntries.find(jobIdEscalationPair.first);
  294. if (found != m_currentJobRunKeyToJobEntries.end())
  295. {
  296. m_sourceModel->UpdateJobEscalation(found->second, jobIdEscalationPair.second);
  297. }
  298. }
  299. }
  300. } // end namespace AssetProcessor