JobsModel.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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/JobsModel.h>
  9. #include <AzToolsFramework/API/AssetDatabaseBus.h>
  10. #include <native/utilities/assetUtils.h>
  11. #include <native/AssetDatabase/AssetDatabase.h>
  12. #include <native/resourcecompiler/RCJobSortFilterProxyModel.h>
  13. #include <native/utilities/JobDiagnosticTracker.h>
  14. #include <AzCore/StringFunc/StringFunc.h>
  15. namespace AssetProcessor
  16. {
  17. JobsModel::JobsModel(QObject* parent)
  18. : QAbstractItemModel(parent)
  19. , m_pendingIcon(QStringLiteral(":/stylesheet/img/logging/pending.svg"))
  20. , m_errorIcon(QStringLiteral(":/stylesheet/img/logging/error.svg"))
  21. , m_failureIcon(QStringLiteral(":/stylesheet/img/logging/failure.svg"))
  22. , m_warningIcon(QStringLiteral(":/stylesheet/img/logging/warning.svg"))
  23. , m_okIcon(QStringLiteral(":/stylesheet/img/logging/valid.svg"))
  24. , m_processingIcon(QStringLiteral(":/stylesheet/img/logging/processing.svg"))
  25. {
  26. }
  27. JobsModel::~JobsModel()
  28. {
  29. for (int idx = 0; idx < m_cachedJobs.size(); idx++)
  30. {
  31. delete m_cachedJobs[idx];
  32. }
  33. m_cachedJobs.clear();
  34. m_cachedJobsLookup.clear();
  35. }
  36. QModelIndex JobsModel::parent(const QModelIndex& index) const
  37. {
  38. AZ_UNUSED(index);
  39. return QModelIndex();
  40. }
  41. QModelIndex JobsModel::index(int row, int column, const QModelIndex& parent) const
  42. {
  43. if (row >= rowCount(parent) || column >= columnCount(parent))
  44. {
  45. return QModelIndex();
  46. }
  47. return createIndex(row, column);
  48. }
  49. int JobsModel::rowCount(const QModelIndex& parent) const
  50. {
  51. return parent.isValid() ? 0 : itemCount();
  52. }
  53. int JobsModel::columnCount(const QModelIndex& parent) const
  54. {
  55. return parent.isValid() ? 0 : Column::Max;
  56. }
  57. QVariant JobsModel::headerData(int section, Qt::Orientation orientation, int role) const
  58. {
  59. if (orientation != Qt::Horizontal)
  60. {
  61. return QAbstractItemModel::headerData(section, orientation, role);
  62. }
  63. switch (role)
  64. {
  65. case Qt::DisplayRole:
  66. {
  67. switch (section)
  68. {
  69. case ColumnStatus:
  70. return tr("Status");
  71. case ColumnSource:
  72. return tr("Source");
  73. case ColumnPlatform:
  74. return tr("Platform");
  75. case ColumnJobKey:
  76. return tr("Job Key");
  77. case ColumnCompleted:
  78. return tr("Completed");
  79. case ColumnProcessDuration:
  80. return tr("Last Processing Job Duration");
  81. default:
  82. break;
  83. }
  84. }
  85. case Qt::TextAlignmentRole:
  86. {
  87. return Qt::AlignLeft + Qt::AlignVCenter;
  88. }
  89. default:
  90. break;
  91. }
  92. return QAbstractItemModel::headerData(section, orientation, role);
  93. }
  94. int JobsModel::itemCount() const
  95. {
  96. return aznumeric_caster(m_cachedJobs.size());
  97. }
  98. QVariant JobsModel::data(const QModelIndex& index, int role) const
  99. {
  100. if (!index.isValid())
  101. {
  102. return QVariant();
  103. }
  104. if (index.row() >= itemCount())
  105. {
  106. return QVariant();
  107. }
  108. switch (role)
  109. {
  110. case Qt::DecorationRole:
  111. {
  112. if (index.column() == ColumnStatus) {
  113. using namespace AzToolsFramework::AssetSystem;
  114. switch (getItem(index.row())->m_jobState) {
  115. case JobStatus::Queued:
  116. return m_pendingIcon;
  117. case JobStatus::Failed_InvalidSourceNameExceedsMaxLimit: // fall through intentional
  118. case JobStatus::Failed:
  119. return m_failureIcon;
  120. case JobStatus::Completed:
  121. {
  122. CachedJobInfo* jobInfo = getItem(index.row());
  123. if(jobInfo->m_errorCount > 0)
  124. {
  125. return m_errorIcon;
  126. }
  127. if(jobInfo->m_warningCount > 0)
  128. {
  129. return m_warningIcon;
  130. }
  131. return m_okIcon;
  132. }
  133. case JobStatus::InProgress:
  134. return m_processingIcon;
  135. }
  136. }
  137. break;
  138. }
  139. case Qt::DisplayRole:
  140. case SortRole:
  141. switch (index.column())
  142. {
  143. case ColumnStatus:
  144. {
  145. CachedJobInfo* jobInfo = getItem(index.row());
  146. return GetStatusInString(jobInfo->m_jobState, jobInfo->m_warningCount, jobInfo->m_errorCount);
  147. }
  148. case ColumnSource:
  149. return getItem(index.row())->m_elementId.GetSourceAssetReference().RelativePath().c_str();
  150. case ColumnPlatform:
  151. return getItem(index.row())->m_elementId.GetPlatform();
  152. case ColumnJobKey:
  153. return getItem(index.row())->m_elementId.GetJobDescriptor();
  154. case ColumnCompleted:
  155. if (role == SortRole)
  156. {
  157. return getItem(index.row())->m_completedTime;
  158. }
  159. else
  160. {
  161. return getItem(index.row())->m_completedTime.toString("hh:mm:ss.zzz MMM dd, yyyy");
  162. }
  163. case ColumnProcessDuration:
  164. if (role == SortRole)
  165. {
  166. return getItem(index.row())->m_processDuration;
  167. }
  168. else
  169. {
  170. QTime processDuration = getItem(index.row())->m_processDuration;
  171. if (!processDuration.isValid())
  172. {
  173. return "";
  174. }
  175. return processDuration.toString("hh:mm:ss.zzz");
  176. }
  177. default:
  178. break;
  179. }
  180. case logRole:
  181. {
  182. using namespace AzToolsFramework::AssetSystem;
  183. using namespace AssetUtilities;
  184. JobInfo jobInfo;
  185. AssetJobLogResponse jobLogResponse;
  186. auto* cachedJobInfo = getItem(index.row());
  187. jobInfo.m_sourceFile = cachedJobInfo->m_elementId.GetSourceAssetReference().RelativePath().Native();
  188. jobInfo.m_watchFolder = cachedJobInfo->m_elementId.GetSourceAssetReference().ScanFolderPath().Native();
  189. jobInfo.m_platform = cachedJobInfo->m_elementId.GetPlatform().toUtf8().data();
  190. jobInfo.m_jobKey = cachedJobInfo->m_elementId.GetJobDescriptor().toUtf8().data();
  191. jobInfo.m_builderGuid = cachedJobInfo->m_builderGuid;
  192. jobInfo.m_jobRunKey = cachedJobInfo->m_jobRunKey;
  193. jobInfo.m_warningCount = cachedJobInfo->m_warningCount;
  194. jobInfo.m_errorCount = cachedJobInfo->m_errorCount;
  195. ReadJobLogResult readJobLogResult = ReadJobLog(jobInfo, jobLogResponse);
  196. const char* jobLogData = jobLogResponse.m_jobLog.c_str();
  197. // ReadJobLog prepends the result with Error: if it can't find the file, even if the job was
  198. // completed successfully or is still pending, so we detect that and give a less panicky response
  199. // to the end user.
  200. if (readJobLogResult == ReadJobLogResult::MissingLogFile)
  201. {
  202. switch (cachedJobInfo->m_jobState)
  203. {
  204. case JobStatus::Completed:
  205. jobLogData = "The log file from the last (successful) run of this job could not be found.\nLogs are not always generated for successful jobs and this does not indicate an error.";
  206. break;
  207. case JobStatus::InProgress:
  208. case JobStatus::Queued:
  209. jobLogData = "The job is still processing and the log file has not yet been created";
  210. break;
  211. default:
  212. // leave the job log as it is
  213. break;
  214. }
  215. }
  216. return QVariant(jobLogData);
  217. }
  218. case Qt::TextAlignmentRole:
  219. {
  220. return Qt::AlignLeft + Qt::AlignVCenter;
  221. }
  222. case statusRole:
  223. {
  224. CachedJobInfo* jobInfo = getItem(index.row());
  225. return QVariant::fromValue(JobStatusInfo{ jobInfo->m_jobState, jobInfo->m_warningCount, jobInfo->m_errorCount });
  226. }
  227. case logFileRole:
  228. {
  229. AzToolsFramework::AssetSystem::JobInfo jobInfo;
  230. CachedJobInfo* cachedJobInfo = getItem(index.row());
  231. jobInfo.m_sourceFile = cachedJobInfo->m_elementId.GetSourceAssetReference().RelativePath().Native();
  232. jobInfo.m_watchFolder = cachedJobInfo->m_elementId.GetSourceAssetReference().ScanFolderPath().Native();
  233. jobInfo.m_platform = cachedJobInfo->m_elementId.GetPlatform().toUtf8().data();
  234. jobInfo.m_jobKey = cachedJobInfo->m_elementId.GetJobDescriptor().toUtf8().data();
  235. jobInfo.m_builderGuid = cachedJobInfo->m_builderGuid;
  236. jobInfo.m_jobRunKey = cachedJobInfo->m_jobRunKey;
  237. jobInfo.m_warningCount = cachedJobInfo->m_warningCount;
  238. jobInfo.m_errorCount = cachedJobInfo->m_errorCount;
  239. AZStd::string logFile = AssetUtilities::ComputeJobLogFolder() + "/" + AssetUtilities::ComputeJobLogFileName(jobInfo);
  240. return QVariant(logFile.c_str());
  241. }
  242. default:
  243. break;
  244. }
  245. return QVariant();
  246. }
  247. CachedJobInfo* JobsModel::getItem(int index) const
  248. {
  249. if (index >= 0 && index < m_cachedJobs.size())
  250. {
  251. return m_cachedJobs[index];
  252. }
  253. return nullptr; //invalid index
  254. }
  255. void Append(QString& base, const QString& input, const QString& seperator = ", ")
  256. {
  257. if(input.isEmpty())
  258. {
  259. return;
  260. }
  261. if(!base.isEmpty())
  262. {
  263. base.append(seperator);
  264. }
  265. base.append(input);
  266. }
  267. QString JobsModel::GetStatusInString(const AzToolsFramework::AssetSystem::JobStatus& state, AZ::u32 warningCount, AZ::u32 errorCount)
  268. {
  269. using namespace AzToolsFramework::AssetSystem;
  270. switch (state)
  271. {
  272. case JobStatus::Queued:
  273. return tr("Pending");
  274. case JobStatus::Failed_InvalidSourceNameExceedsMaxLimit: // fall through intentional
  275. case JobStatus::Failed:
  276. return tr("Failed");
  277. case JobStatus::Completed:
  278. {
  279. QString message = tr("Completed");
  280. QString extra;
  281. if (warningCount > 0)
  282. {
  283. extra.append(QString("%1 %2").arg(warningCount).arg( warningCount == 1 ? tr("warning") : tr("warnings") ));
  284. }
  285. if (errorCount > 0)
  286. {
  287. Append(extra, QString("%1 %2").arg(errorCount).arg( errorCount == 1 ? tr("error") : tr("errors") ));
  288. }
  289. Append(message, extra, ": ");
  290. return message;
  291. }
  292. case JobStatus::InProgress:
  293. return tr("InProgress");
  294. }
  295. return QString();
  296. }
  297. void JobsModel::PopulateJobsFromDatabase()
  298. {
  299. beginResetModel();
  300. AZStd::string databaseLocation;
  301. AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Broadcast(&AzToolsFramework::AssetDatabase::AssetDatabaseRequests::GetAssetDatabaseLocation, databaseLocation);
  302. if (!databaseLocation.empty())
  303. {
  304. AssetProcessor::AssetDatabaseConnection assetDatabaseConnection;
  305. assetDatabaseConnection.OpenDatabase();
  306. // Get historical ProcessJob stats from asset database
  307. AZStd::unordered_map<QueueElementID, AZ::s64> historicalStats;
  308. auto statsFunction = [&historicalStats](AzToolsFramework::AssetDatabase::StatDatabaseEntry entry)
  309. {
  310. static constexpr int numTokensExpected = 6;
  311. AZStd::vector<AZStd::string> tokens;
  312. AZ::StringFunc::Tokenize(entry.m_statName, tokens, ',');
  313. if (tokens.size() == numTokensExpected)
  314. {
  315. QueueElementID elementId;
  316. elementId.SetSourceAssetReference(SourceAssetReference(tokens[1].c_str(), tokens[2].c_str()));
  317. elementId.SetJobDescriptor(tokens[3].c_str());
  318. elementId.SetPlatform(tokens[4].c_str());
  319. historicalStats[elementId] = entry.m_statValue;
  320. AZ_UNUSED(historicalStats);
  321. }
  322. else
  323. {
  324. AZ_Warning(
  325. "AssetProcessor",
  326. false,
  327. "ProcessJob stat entry \"%s\" could not be parsed and will not be used. Expected %d tokens, but found %d. A wrong "
  328. "stat name may be used in Asset Processor code, or the asset database may be corrupted. If you keep encountering "
  329. "this warning, report an issue on GitHub with O3DE version number.",
  330. entry.m_statName.c_str(),
  331. numTokensExpected,
  332. tokens.size());
  333. }
  334. return true;
  335. };
  336. assetDatabaseConnection.QueryStatLikeStatName("ProcessJob,%", statsFunction);
  337. // Get jobs from asset database
  338. auto jobsFunction = [this, &assetDatabaseConnection, &historicalStats](AzToolsFramework::AssetDatabase::JobDatabaseEntry& entry)
  339. {
  340. AzToolsFramework::AssetDatabase::SourceDatabaseEntry source;
  341. assetDatabaseConnection.GetSourceBySourceID(entry.m_sourcePK, source);
  342. AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder;
  343. assetDatabaseConnection.GetScanFolderByScanFolderID(source.m_scanFolderPK, scanFolder);
  344. CachedJobInfo* jobInfo = new CachedJobInfo();
  345. jobInfo->m_elementId.SetSourceAssetReference(SourceAssetReference(scanFolder.m_scanFolder.c_str(), source.m_sourceName.c_str()));
  346. jobInfo->m_elementId.SetPlatform(entry.m_platform.c_str());
  347. jobInfo->m_elementId.SetJobDescriptor(entry.m_jobKey.c_str());
  348. jobInfo->m_jobState = entry.m_status;
  349. jobInfo->m_jobRunKey = aznumeric_cast<uint32_t>(entry.m_jobRunKey);
  350. jobInfo->m_builderGuid = entry.m_builderGuid;
  351. jobInfo->m_completedTime = QDateTime::fromMSecsSinceEpoch(entry.m_lastLogTime);
  352. jobInfo->m_warningCount = entry.m_warningCount;
  353. jobInfo->m_errorCount = entry.m_errorCount;
  354. if (historicalStats.count(jobInfo->m_elementId))
  355. {
  356. jobInfo->m_processDuration =
  357. QTime::fromMSecsSinceStartOfDay(aznumeric_cast<int>(historicalStats.at(jobInfo->m_elementId)));
  358. }
  359. m_cachedJobs.push_back(jobInfo);
  360. m_cachedJobsLookup.insert(jobInfo->m_elementId, aznumeric_caster(m_cachedJobs.size() - 1));
  361. return true;
  362. };
  363. assetDatabaseConnection.QueryJobsTable(jobsFunction);
  364. }
  365. endResetModel();
  366. }
  367. QModelIndex JobsModel::GetJobFromProduct(const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& productEntry, AzToolsFramework::AssetDatabase::AssetDatabaseConnection& assetDatabaseConnection)
  368. {
  369. AZStd::string sourceForProduct;
  370. AZ::s64 scanFolderId;
  371. assetDatabaseConnection.QuerySourceByProductID(
  372. productEntry.m_productID,
  373. [&](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry)
  374. {
  375. sourceForProduct = sourceEntry.m_sourceName;
  376. scanFolderId = sourceEntry.m_scanFolderPK;
  377. return false;
  378. });
  379. if (sourceForProduct.empty())
  380. {
  381. return QModelIndex();
  382. }
  383. AZStd::string scanFolderPath;
  384. assetDatabaseConnection.QueryScanFolderByScanFolderID(scanFolderId, [&](AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& entry)
  385. {
  386. scanFolderPath = entry.m_scanFolder;
  387. return false;
  388. });
  389. if (scanFolderPath.empty())
  390. {
  391. return QModelIndex();
  392. }
  393. AzToolsFramework::AssetDatabase::JobDatabaseEntry foundJobEntry;
  394. assetDatabaseConnection.QueryJobByProductID(
  395. productEntry.m_productID,
  396. [&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
  397. {
  398. foundJobEntry = jobEntry;
  399. return false;
  400. });
  401. if (foundJobEntry.m_jobID == AzToolsFramework::AssetDatabase::InvalidEntryId)
  402. {
  403. return QModelIndex();
  404. }
  405. return GetJobFromSourceAndJobInfo(SourceAssetReference(scanFolderPath.c_str(), sourceForProduct.c_str()), foundJobEntry.m_platform, foundJobEntry.m_jobKey);
  406. }
  407. QModelIndex JobsModel::GetJobFromSourceAndJobInfo(const SourceAssetReference& sourceAsset, const AZStd::string& platform, const AZStd::string& jobKey)
  408. {
  409. QueueElementID elementId(sourceAsset, platform.c_str(), jobKey.c_str());
  410. auto iter = m_cachedJobsLookup.find(elementId);
  411. if (iter == m_cachedJobsLookup.end())
  412. {
  413. return QModelIndex();
  414. }
  415. return index(iter.value(), 0, QModelIndex());
  416. }
  417. void JobsModel::OnJobStatusChanged(JobEntry entry, AzToolsFramework::AssetSystem::JobStatus status)
  418. {
  419. QueueElementID elementId(entry.m_sourceAssetReference, entry.m_platformInfo.m_identifier.c_str(), entry.m_jobKey);
  420. CachedJobInfo* jobInfo = nullptr;
  421. unsigned int jobIndex = 0;
  422. JobDiagnosticInfo jobDiagnosticInfo{};
  423. JobDiagnosticRequestBus::BroadcastResult(jobDiagnosticInfo, &JobDiagnosticRequestBus::Events::GetDiagnosticInfo, entry.m_jobRunKey);
  424. auto iter = m_cachedJobsLookup.find(elementId);
  425. if (iter == m_cachedJobsLookup.end())
  426. {
  427. jobInfo = new CachedJobInfo();
  428. jobInfo->m_elementId.SetSourceAssetReference(entry.m_sourceAssetReference);
  429. jobInfo->m_elementId.SetPlatform(entry.m_platformInfo.m_identifier.c_str());
  430. jobInfo->m_elementId.SetJobDescriptor(entry.m_jobKey.toUtf8().data());
  431. jobInfo->m_jobRunKey = aznumeric_cast<uint32_t>(entry.m_jobRunKey);
  432. jobInfo->m_builderGuid = entry.m_builderGuid;
  433. jobInfo->m_jobState = status;
  434. jobInfo->m_warningCount = jobDiagnosticInfo.m_warningCount;
  435. jobInfo->m_errorCount = jobDiagnosticInfo.m_errorCount;
  436. jobIndex = aznumeric_caster(m_cachedJobs.size());
  437. beginInsertRows(QModelIndex(), jobIndex, jobIndex);
  438. m_cachedJobs.push_back(jobInfo);
  439. m_cachedJobsLookup.insert(jobInfo->m_elementId, jobIndex);
  440. endInsertRows();
  441. }
  442. else
  443. {
  444. jobIndex = iter.value();
  445. jobInfo = m_cachedJobs[jobIndex];
  446. jobInfo->m_jobState = status;
  447. jobInfo->m_jobRunKey = aznumeric_cast<uint32_t>(entry.m_jobRunKey);
  448. jobInfo->m_builderGuid = entry.m_builderGuid;
  449. jobInfo->m_warningCount = jobDiagnosticInfo.m_warningCount;
  450. jobInfo->m_errorCount = jobDiagnosticInfo.m_errorCount;
  451. if (jobInfo->m_jobState == AzToolsFramework::AssetSystem::JobStatus::Completed || jobInfo->m_jobState == AzToolsFramework::AssetSystem::JobStatus::Failed)
  452. {
  453. jobInfo->m_completedTime = QDateTime::currentDateTime();
  454. }
  455. else
  456. {
  457. jobInfo->m_completedTime = QDateTime();
  458. }
  459. Q_EMIT dataChanged(index(jobIndex, 0, QModelIndex()), index(jobIndex, columnCount() - 1, QModelIndex()));
  460. }
  461. }
  462. void JobsModel::OnJobProcessDurationChanged(JobEntry jobEntry, int durationMs)
  463. {
  464. QueueElementID elementId(jobEntry.m_sourceAssetReference, jobEntry.m_platformInfo.m_identifier.c_str(), jobEntry.m_jobKey);
  465. if (auto iter = m_cachedJobsLookup.find(elementId); iter != m_cachedJobsLookup.end())
  466. {
  467. unsigned int jobIndex = iter.value();
  468. CachedJobInfo* jobInfo = m_cachedJobs[jobIndex];
  469. jobInfo->m_processDuration = QTime::fromMSecsSinceStartOfDay(durationMs);
  470. Q_EMIT dataChanged(
  471. index(jobIndex, ColumnProcessDuration, QModelIndex()), index(jobIndex, ColumnProcessDuration, QModelIndex()));
  472. }
  473. }
  474. void JobsModel::OnSourceRemoved(const SourceAssetReference& sourceAsset)
  475. {
  476. // when a source is removed, we need to eliminate all job entries for that source regardless of all other details of it.
  477. QList<AssetProcessor::QueueElementID> elementsToRemove;
  478. for (int index = 0; index < m_cachedJobs.size(); ++index)
  479. {
  480. if (m_cachedJobs[index]->m_elementId.GetSourceAssetReference() == sourceAsset)
  481. {
  482. elementsToRemove.push_back(m_cachedJobs[index]->m_elementId);
  483. }
  484. }
  485. // now that we've collected all the elements to remove, we can remove them.
  486. // Doing it this way avoids problems with mutating these cache structures while iterating them.
  487. for (const AssetProcessor::QueueElementID& removal : elementsToRemove)
  488. {
  489. RemoveJob(removal);
  490. }
  491. }
  492. void JobsModel::OnJobRemoved(AzToolsFramework::AssetSystem::JobInfo jobInfo)
  493. {
  494. RemoveJob(QueueElementID(SourceAssetReference(jobInfo.m_watchFolder.c_str(), jobInfo.m_sourceFile.c_str()), jobInfo.m_platform.c_str(), jobInfo.m_jobKey.c_str()));
  495. }
  496. void JobsModel::RemoveJob(const AssetProcessor::QueueElementID& elementId)
  497. {
  498. auto iter = m_cachedJobsLookup.find(elementId);
  499. if (iter != m_cachedJobsLookup.end())
  500. {
  501. unsigned int jobIndex = iter.value();
  502. CachedJobInfo* jobInfo = m_cachedJobs[jobIndex];
  503. beginRemoveRows(QModelIndex(), jobIndex, jobIndex);
  504. m_cachedJobs.erase(m_cachedJobs.begin() + jobIndex);
  505. delete jobInfo;
  506. m_cachedJobsLookup.erase(iter);
  507. // Since we are storing the jobIndex for each job for faster lookup therefore
  508. // we need to update the jobIndex for jobs that were after the removed job.
  509. for (int idx = jobIndex; idx < m_cachedJobs.size(); idx++)
  510. {
  511. jobInfo = m_cachedJobs[idx];
  512. auto iterator = m_cachedJobsLookup.find(jobInfo->m_elementId);
  513. if (iterator != m_cachedJobsLookup.end())
  514. {
  515. unsigned int index = iterator.value();
  516. m_cachedJobsLookup[jobInfo->m_elementId] = --index;
  517. }
  518. }
  519. endRemoveRows();
  520. }
  521. }
  522. } //namespace AssetProcessor