3
0

Modifier.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  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 <Editor/Include/ScriptCanvas/Components/EditorGraph.h>
  9. #include <Editor/View/Windows/Tools/UpgradeTool/LogTraits.h>
  10. #include <Editor/View/Windows/Tools/UpgradeTool/Modifier.h>
  11. #include <ScriptCanvas/Asset/SubgraphInterfaceAsset.h>
  12. #include <ScriptCanvas/Assets/ScriptCanvasFileHandling.h>
  13. #include <ScriptCanvas/Core/Graph.h>
  14. namespace ScriptCanvasEditor
  15. {
  16. namespace VersionExplorer
  17. {
  18. Modifier::Modifier
  19. ( const ModifyConfiguration& modification
  20. , AZStd::vector<SourceHandle>&& assets
  21. , AZStd::function<void()> onComplete)
  22. : m_state(State::GatheringDependencies)
  23. , m_config(modification)
  24. , m_assets(assets)
  25. , m_onComplete(onComplete)
  26. {
  27. AZ_Assert(m_config.modification, "No modification function provided");
  28. ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeBegin, modification, m_assets);
  29. AZ::SystemTickBus::Handler::BusConnect();
  30. AzFramework::AssetSystemInfoBus::Handler::BusConnect();
  31. m_result.asset = m_assets[GetCurrentIndex()];
  32. }
  33. Modifier::~Modifier()
  34. {
  35. AzFramework::AssetSystemInfoBus::Handler::BusDisconnect();
  36. }
  37. bool Modifier::AllDependenciesCleared(const AZStd::unordered_set<size_t>& dependencies) const
  38. {
  39. for (auto index : dependencies)
  40. {
  41. SourceHandle dependency = m_assets[index];
  42. CompleteDescriptionInPlace(dependency);
  43. if (dependency.Id().IsNull() || !m_assetsCompletedByAP.contains(dependency.Id()))
  44. {
  45. return false;
  46. }
  47. }
  48. return true;
  49. }
  50. bool Modifier::AnyDependenciesFailed(const AZStd::unordered_set<size_t>& dependencies) const
  51. {
  52. for (auto index : dependencies)
  53. {
  54. SourceHandle dependency = m_assets[index];
  55. CompleteDescriptionInPlace(dependency);
  56. if (dependency.Id().IsNull() || m_assetsFailedByAP.contains(dependency.Id()))
  57. {
  58. return true;
  59. }
  60. }
  61. return false;
  62. }
  63. void Modifier::AssetCompilationSuccess([[maybe_unused]] const AZStd::string& assetPath)
  64. {
  65. const AZStd::string assetPathValue(assetPath);
  66. AZ::SystemTickBus::QueueFunction([this, assetPathValue]()
  67. {
  68. m_successNotifications.insert(assetPathValue);
  69. });
  70. }
  71. void Modifier::AssetCompilationFailed(const AZStd::string& assetPath)
  72. {
  73. const AZStd::string assetPathValue(assetPath);
  74. AZ::SystemTickBus::QueueFunction([this, assetPathValue]()
  75. {
  76. m_failureNotifications.insert(assetPathValue);
  77. });
  78. }
  79. AZStd::sys_time_t Modifier::CalculateRemainingWaitTime(const AZStd::unordered_set<size_t>& dependencies) const
  80. {
  81. auto maxSeconds = AZStd::chrono::seconds(dependencies.size() * m_config.perDependencyWaitSecondsMax);
  82. auto waitedSeconds = AZStd::chrono::duration_cast<AZStd::chrono::seconds>(AZStd::chrono::steady_clock::now() - m_waitTimeStamp);
  83. return (maxSeconds - waitedSeconds).count();
  84. }
  85. void Modifier::CheckDependencies()
  86. {
  87. ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeModificationBegin, m_config, m_result.asset);
  88. if (auto dependencies = GetDependencies(GetCurrentIndex()); dependencies != nullptr && !dependencies->empty())
  89. {
  90. VE_LOG
  91. ( "dependencies found for %s, update will wait for the AP to finish processing them"
  92. , m_result.asset.RelativePath().c_str());
  93. m_waitTimeStamp = AZStd::chrono::steady_clock::now();
  94. m_waitLogTimeStamp = AZStd::chrono::steady_clock::time_point{};
  95. m_modifyState = ModifyState::WaitingForDependencyProcessing;
  96. }
  97. else
  98. {
  99. m_modifyState = ModifyState::StartModification;
  100. }
  101. }
  102. void Modifier::GatherDependencies()
  103. {
  104. AZ::SerializeContext* serializeContext{};
  105. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  106. AZ_Assert(serializeContext, "SerializeContext is required to enumerate dependent assets in the ScriptCanvas file");
  107. LoadAsset();
  108. bool anyFailures = false;
  109. if (m_result.asset.Get() && m_result.asset.Mod()->GetGraphData())
  110. {
  111. auto graphData = m_result.asset.Mod()->GetGraphData();
  112. auto dependencyGrabber = [this]
  113. ( void* instancePointer
  114. , const AZ::SerializeContext::ClassData* classData
  115. , [[maybe_unused]] const AZ::SerializeContext::ClassElement* classElement)
  116. {
  117. if (auto azTypeId = classData->m_azRtti->GetTypeId();
  118. azTypeId == azrtti_typeid<AZ::Data::Asset<ScriptCanvas::SubgraphInterfaceAsset>>())
  119. {
  120. const auto* subgraphAsset =
  121. reinterpret_cast<AZ::Data::Asset<const ScriptCanvas::SubgraphInterfaceAsset>*>(instancePointer);
  122. if (subgraphAsset->GetId().IsValid())
  123. {
  124. if (auto iter = m_assetInfoIndexById.find(subgraphAsset->GetId().m_guid); iter != m_assetInfoIndexById.end())
  125. {
  126. // insert the index of the dependency into the set that belongs to this asset
  127. GetOrCreateDependencyIndexSet().insert(iter->second);
  128. }
  129. }
  130. }
  131. // always continue, make note of the script canvas dependencies
  132. return true;
  133. };
  134. if (!serializeContext->EnumerateInstanceConst
  135. ( graphData
  136. , azrtti_typeid<ScriptCanvas::GraphData>()
  137. , dependencyGrabber
  138. , {}
  139. , AZ::SerializeContext::ENUM_ACCESS_FOR_READ
  140. , nullptr
  141. , nullptr))
  142. {
  143. anyFailures = true;
  144. VE_LOG("Modifier: ERROR - Failed to gather dependencies from graph data: %s"
  145. , m_result.asset.RelativePath().c_str())
  146. }
  147. }
  148. else
  149. {
  150. anyFailures = true;
  151. VE_LOG("Modifier: ERROR - Failed to load asset %s for modification, even though it scanned properly"
  152. , m_result.asset.RelativePath().c_str());
  153. }
  154. ModelNotificationsBus::Broadcast
  155. ( &ModelNotificationsTraits::OnUpgradeDependenciesGathered
  156. , m_result.asset
  157. , anyFailures ? Result::Failure : Result::Success);
  158. }
  159. size_t Modifier::GetCurrentIndex() const
  160. {
  161. return m_state == State::GatheringDependencies
  162. ? m_assetIndex
  163. : m_dependencyOrderedAssetIndicies[m_assetIndex];
  164. }
  165. const AZStd::unordered_set<size_t>* Modifier::GetDependencies(size_t index) const
  166. {
  167. auto iter = m_dependencies.find(index);
  168. return iter != m_dependencies.end() ? &iter->second : nullptr;
  169. }
  170. AZStd::unordered_set<size_t>& Modifier::GetOrCreateDependencyIndexSet()
  171. {
  172. auto iter = m_dependencies.find(m_assetIndex);
  173. if (iter == m_dependencies.end())
  174. {
  175. iter = m_dependencies.insert_or_assign(m_assetIndex, AZStd::unordered_set<size_t>()).first;
  176. }
  177. return iter->second;
  178. }
  179. const ModificationResults& Modifier::GetResult() const
  180. {
  181. return m_results;
  182. }
  183. void Modifier::InitializeResult()
  184. {
  185. m_result = {};
  186. if (m_assetIndex != m_assets.size())
  187. {
  188. m_result.asset = m_assets[GetCurrentIndex()];
  189. m_attemptedAssets.insert(m_result.asset.Id());
  190. }
  191. }
  192. void Modifier::LoadAsset()
  193. {
  194. auto& handle = m_result.asset;
  195. if (!handle.IsGraphValid())
  196. {
  197. auto result = ScriptCanvas::LoadFromFile(handle.AbsolutePath().c_str());
  198. if (result)
  199. {
  200. handle = result.m_handle;
  201. }
  202. }
  203. }
  204. void Modifier::ModificationComplete(const ModificationResult& result)
  205. {
  206. if (!result.errorMessage.empty())
  207. {
  208. ReportModificationError(result.errorMessage);
  209. }
  210. else if (m_result.asset.Describe() != result.asset.Describe())
  211. {
  212. ReportModificationError("Received modification complete notification for different result");
  213. }
  214. else
  215. {
  216. SaveModifiedGraph(result);
  217. }
  218. }
  219. void Modifier::ModifyCurrentAsset()
  220. {
  221. LoadAsset();
  222. if (m_result.asset.IsGraphValid())
  223. {
  224. ModificationNotificationsBus::Handler::BusConnect();
  225. m_modifyState = ModifyState::InProgress;
  226. m_config.modification(m_result.asset);
  227. }
  228. else
  229. {
  230. ReportModificationError("Failed to load during modification");
  231. }
  232. }
  233. void Modifier::NextAsset()
  234. {
  235. ++m_assetIndex;
  236. InitializeResult();
  237. }
  238. void Modifier::NextModification()
  239. {
  240. ModelNotificationsBus::Broadcast( &ModelNotificationsTraits::OnUpgradeModificationEnd, m_config, m_result.asset, m_result);
  241. ModificationNotificationsBus::Handler::BusDisconnect();
  242. NextAsset();
  243. m_fileSaveResult = {};
  244. m_modifyState = ModifyState::Idle;
  245. }
  246. void Modifier::OnFileSaveComplete(const FileSaveResult& result)
  247. {
  248. if (!result.tempFileRemovalError.empty())
  249. {
  250. VE_LOG
  251. ( "Temporary file not removed for %s: %s"
  252. , m_result.asset.RelativePath().c_str()
  253. , result.tempFileRemovalError.c_str());
  254. }
  255. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutex);
  256. m_modifyState = ModifyState::ReportResult;
  257. m_fileSaver.reset();
  258. m_fileSaveResult = result;
  259. }
  260. void Modifier::OnSystemTick()
  261. {
  262. switch (m_state)
  263. {
  264. case State::GatheringDependencies:
  265. TickGatherDependencies();
  266. break;
  267. case State::ModifyingGraphs:
  268. TickUpdateGraph();
  269. break;
  270. }
  271. AZ::Data::AssetManager::Instance().DispatchEvents();
  272. AZ::SystemTickBus::ExecuteQueuedEvents();
  273. }
  274. void Modifier::ProcessNotifications()
  275. {
  276. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutex);
  277. for (const auto& assetPath : m_successNotifications)
  278. {
  279. VE_LOG("received AssetCompilationSuccess: %s", assetPath.c_str());
  280. auto sourceHandle = SourceHandle::FromRelativePath(nullptr, AZ::Uuid::CreateNull(), assetPath.c_str());
  281. CompleteDescriptionInPlace(sourceHandle);
  282. if (m_attemptedAssets.contains(sourceHandle.Id()))
  283. {
  284. m_assetsCompletedByAP.insert(sourceHandle.Id());
  285. }
  286. }
  287. m_successNotifications.clear();
  288. for (const auto& assetPath : m_failureNotifications)
  289. {
  290. VE_LOG("received AssetCompilationFailed: %s", assetPath.c_str());
  291. auto sourceHandle = SourceHandle::FromRelativePath(nullptr, AZ::Uuid::CreateNull(), assetPath.c_str());
  292. CompleteDescriptionInPlace(sourceHandle);
  293. if (m_attemptedAssets.contains(sourceHandle.Id()))
  294. {
  295. m_assetsFailedByAP.insert(sourceHandle.Id());
  296. }
  297. }
  298. m_failureNotifications.clear();
  299. }
  300. void Modifier::ReleaseCurrentAsset()
  301. {
  302. m_result.asset = m_result.asset.Describe();
  303. // Flush asset database events to ensure no asset references are held by closures queued on Ebuses.
  304. AZ::Data::AssetManager::Instance().DispatchEvents();
  305. }
  306. void Modifier::ReportModificationError(AZStd::string_view report)
  307. {
  308. m_result.errorMessage = report;
  309. m_results.m_failures.push_back({ m_result.asset.Describe(), report });
  310. m_assetsFailedByAP.insert(m_result.asset.Id());
  311. NextModification();
  312. }
  313. void Modifier::ReportModificationSuccess()
  314. {
  315. using namespace AzFramework;
  316. // \note DO NOT put asset into the m_assetsCompletedByAP here. That can only be done when the message is received by the AP
  317. m_results.m_successes.push_back(m_result.asset.Describe());
  318. AssetSystemRequestBus::Broadcast(&AssetSystem::AssetSystemRequests::EscalateAssetByUuid, m_result.asset.Id());
  319. NextModification();
  320. }
  321. void Modifier::ReportSaveResult()
  322. {
  323. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutex);
  324. m_fileSaver.reset();
  325. if (m_fileSaveResult.IsSuccess())
  326. {
  327. ReportModificationSuccess();
  328. }
  329. else
  330. {
  331. ReportModificationError(m_fileSaveResult.fileSaveError);
  332. }
  333. }
  334. void Modifier::SaveModifiedGraph(const ModificationResult& result)
  335. {
  336. m_modifyState = ModifyState::Saving;
  337. m_fileSaver = AZStd::make_unique<FileSaver>
  338. ( m_config.onReadOnlyFile
  339. , [this](const FileSaveResult& fileSaveResult) { OnFileSaveComplete(fileSaveResult); });
  340. m_fileSaver->Save(result.asset, result.asset.AbsolutePath());
  341. }
  342. void Modifier::SortGraphsByDependencies()
  343. {
  344. m_dependencyOrderedAssetIndicies.reserve(m_assets.size());
  345. Sorter sorter;
  346. sorter.modifier = this;
  347. sorter.Sort();
  348. }
  349. ModificationResults&& Modifier::TakeResult()
  350. {
  351. return AZStd::move(m_results);
  352. }
  353. void Modifier::TickGatherDependencies()
  354. {
  355. if (m_assetIndex == 0)
  356. {
  357. if (m_config.successfulDependencyUpgradeRequired)
  358. {
  359. ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeDependencySortBegin, m_config, m_assets);
  360. m_assetInfoIndexById.reserve(m_assets.size());
  361. for (size_t index = 0; index != m_assets.size(); ++index)
  362. {
  363. m_assetInfoIndexById.insert({ m_assets[index].Id(), index });
  364. }
  365. }
  366. else
  367. {
  368. m_dependencyOrderedAssetIndicies.reserve(m_assets.size());
  369. for (size_t index = 0; index != m_assets.size(); ++index)
  370. {
  371. m_dependencyOrderedAssetIndicies.push_back(index);
  372. }
  373. // go straight into ModifyingGraphs
  374. m_assetIndex = m_assets.size();
  375. }
  376. }
  377. if (m_assetIndex == m_assets.size())
  378. {
  379. if (m_config.successfulDependencyUpgradeRequired)
  380. {
  381. SortGraphsByDependencies();
  382. ModelNotificationsBus::Broadcast
  383. ( &ModelNotificationsTraits::OnUpgradeDependencySortEnd
  384. , m_config
  385. , m_assets
  386. , m_dependencyOrderedAssetIndicies);
  387. }
  388. m_assetIndex = 0;
  389. m_state = State::ModifyingGraphs;
  390. InitializeResult();
  391. }
  392. else
  393. {
  394. GatherDependencies();
  395. NextAsset();
  396. }
  397. }
  398. void Modifier::TickUpdateGraph()
  399. {
  400. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutex);
  401. switch (m_modifyState)
  402. {
  403. case ScriptCanvasEditor::VersionExplorer::Modifier::ModifyState::Idle:
  404. if (m_assetIndex == m_assets.size())
  405. {
  406. VE_LOG("Modifier: Complete.");
  407. AZ::SystemTickBus::Handler::BusDisconnect();
  408. if (m_onComplete)
  409. {
  410. m_onComplete();
  411. }
  412. }
  413. else
  414. {
  415. CheckDependencies();
  416. }
  417. break;
  418. case ScriptCanvasEditor::VersionExplorer::Modifier::ModifyState::WaitingForDependencyProcessing:
  419. WaitForDependencies();
  420. break;
  421. case ScriptCanvasEditor::VersionExplorer::Modifier::ModifyState::StartModification:
  422. ModifyCurrentAsset();
  423. break;
  424. case ScriptCanvasEditor::VersionExplorer::Modifier::ModifyState::ReportResult:
  425. ReportSaveResult();
  426. break;
  427. default:
  428. break;
  429. }
  430. }
  431. void Modifier::WaitForDependencies()
  432. {
  433. const AZ::s32 LogPeriodSeconds = 5;
  434. ProcessNotifications();
  435. auto dependencies = GetDependencies(GetCurrentIndex());
  436. if (dependencies == nullptr || dependencies->empty() || AllDependenciesCleared(*dependencies))
  437. {
  438. m_modifyState = ModifyState::StartModification;
  439. }
  440. else if (AnyDependenciesFailed(*dependencies))
  441. {
  442. ReportModificationError("A required dependency failed to update, graph cannot update.");
  443. }
  444. else if (AZStd::chrono::seconds(CalculateRemainingWaitTime(*dependencies)).count() < 0)
  445. {
  446. ReportModificationError("Dependency update time has taken too long, aborting modification.");
  447. }
  448. else if (AZStd::chrono::duration_cast<AZStd::chrono::seconds>(AZStd::chrono::steady_clock::now() - m_waitLogTimeStamp).count() > LogPeriodSeconds)
  449. {
  450. m_waitLogTimeStamp = AZStd::chrono::steady_clock::now();
  451. AZ_TracePrintf
  452. ( ScriptCanvas::k_VersionExplorerWindow.data()
  453. , "Waiting for dependencies for %d more seconds: %s"
  454. , AZStd::chrono::seconds(CalculateRemainingWaitTime(*dependencies)).count()
  455. , m_result.asset.RelativePath().c_str());
  456. ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeDependencyWaitInterval, m_result.asset);
  457. }
  458. }
  459. const AZStd::unordered_set<size_t>* Modifier::Sorter::GetDependencies(size_t index) const
  460. {
  461. return modifier->GetDependencies(index);
  462. }
  463. void Modifier::Sorter::Sort()
  464. {
  465. for (size_t index = 0; index != modifier->m_assets.size(); ++index)
  466. {
  467. Visit(index);
  468. }
  469. }
  470. void Modifier::Sorter::Visit(size_t index)
  471. {
  472. if (markedPermanent.contains(index))
  473. {
  474. return;
  475. }
  476. if (markedTemporary.contains(index))
  477. {
  478. AZ_Error
  479. ( ScriptCanvas::k_VersionExplorerWindow.data()
  480. , false
  481. , "Modifier: Dependency sort has failed during, circular dependency detected for Asset: %s"
  482. , modifier->m_result.asset.RelativePath().c_str());
  483. return;
  484. }
  485. markedTemporary.insert(index);
  486. if (auto dependencies = GetDependencies(index))
  487. {
  488. for (auto& dependency : *dependencies)
  489. {
  490. Visit(dependency);
  491. }
  492. }
  493. markedTemporary.erase(index);
  494. markedPermanent.insert(index);
  495. modifier->m_dependencyOrderedAssetIndicies.push_back(index);
  496. }
  497. }
  498. }