GraphDocument.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  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 <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
  9. #include <AtomToolsFramework/Graph/GraphDocument.h>
  10. #include <AtomToolsFramework/Graph/GraphDocumentNotificationBus.h>
  11. #include <AtomToolsFramework/Graph/GraphUtil.h>
  12. #include <AtomToolsFramework/Util/Util.h>
  13. #include <AzCore/IO/ByteContainerStream.h>
  14. #include <AzCore/Jobs/JobFunction.h>
  15. #include <AzCore/RTTI/BehaviorContext.h>
  16. #include <AzCore/RTTI/RTTI.h>
  17. #include <AzCore/Serialization/EditContext.h>
  18. #include <AzCore/Serialization/Json/JsonUtils.h>
  19. #include <AzCore/Serialization/ObjectStream.h>
  20. #include <AzCore/Serialization/SerializeContext.h>
  21. #include <AzCore/Serialization/Utils.h>
  22. #include <AzCore/Utils/Utils.h>
  23. #include <AzCore/std/containers/vector.h>
  24. #include <AzCore/std/smart_ptr/make_shared.h>
  25. #include <AzCore/std/sort.h>
  26. #include <AzToolsFramework/Entity/EditorEntityHelpers.h>
  27. #include <GraphCanvas/Components/GraphCanvasPropertyBus.h>
  28. #include <GraphCanvas/Components/SceneBus.h>
  29. #include <GraphCanvas/GraphCanvasBus.h>
  30. #include <GraphModel/Model/Connection.h>
  31. #include <GraphModel/Model/Graph.h>
  32. namespace AtomToolsFramework
  33. {
  34. void GraphDocument::Reflect(AZ::ReflectContext* context)
  35. {
  36. if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
  37. {
  38. serialize->Class<GraphDocument, AtomToolsDocument>()
  39. ->Version(0);
  40. }
  41. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  42. {
  43. behaviorContext->EBus<GraphDocumentRequestBus>("GraphDocumentRequestBus")
  44. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  45. ->Attribute(AZ::Script::Attributes::Category, "Editor")
  46. ->Attribute(AZ::Script::Attributes::Module, "atomtools")
  47. ->Event("GetGraph", &GraphDocumentRequests::GetGraph)
  48. ->Event("GetGraphId", &GraphDocumentRequests::GetGraphId)
  49. ->Event("GetGraphName", &GraphDocumentRequests::GetGraphName)
  50. ->Event("SetGeneratedFilePaths", &GraphDocumentRequests::SetGeneratedFilePaths)
  51. ->Event("GetGeneratedFilePaths", &GraphDocumentRequests::GetGeneratedFilePaths)
  52. ->Event("CompileGraph", &GraphDocumentRequests::CompileGraph)
  53. ->Event("QueueCompileGraph", &GraphDocumentRequests::QueueCompileGraph)
  54. ->Event("IsCompileGraphQueued", &GraphDocumentRequests::IsCompileGraphQueued)
  55. ;
  56. }
  57. }
  58. GraphDocument::GraphDocument(
  59. const AZ::Crc32& toolId,
  60. const DocumentTypeInfo& documentTypeInfo,
  61. AZStd::shared_ptr<GraphModel::GraphContext> graphContext,
  62. AZStd::shared_ptr<GraphCompiler> graphCompiler)
  63. : AtomToolsDocument(toolId, documentTypeInfo)
  64. , m_graphContext(graphContext)
  65. , m_graphCompiler(graphCompiler)
  66. {
  67. AZ_Assert(m_graphContext, "Graph context must be valid in order to create a graph document.");
  68. // Creating the scene entity and graph for this document. This may end up moving to the view.
  69. m_graph = AZStd::make_shared<GraphModel::Graph>(m_graphContext);
  70. AZ_Assert(m_graph, "Failed to create graph object.");
  71. GraphModelIntegration::GraphManagerRequestBus::BroadcastResult(
  72. m_sceneEntity, &GraphModelIntegration::GraphManagerRequests::CreateScene, m_graph, m_toolId);
  73. AZ_Assert(m_sceneEntity, "Failed to create graph scene entity.");
  74. m_graphId = m_sceneEntity->GetId();
  75. AZ_Assert(m_graphId.IsValid(), "Graph scene entity ID is not valid.");
  76. RecordGraphState();
  77. // Listen for GraphController notifications on the new graph.
  78. GraphModelIntegration::GraphControllerNotificationBus::Handler::BusConnect(m_graphId);
  79. GraphCanvas::SceneNotificationBus::Handler::BusConnect(m_graphId);
  80. GraphDocumentRequestBus::Handler::BusConnect(m_id);
  81. AZ::SystemTickBus::Handler::BusConnect();
  82. m_graphCompiler->SetStateChangeHandler(
  83. [toolId, documentId = m_id](const GraphCompiler* graphCompiler)
  84. {
  85. AZ::SystemTickBus::QueueFunction(
  86. [toolId, documentId, state = graphCompiler->GetState(), generatedFiles = graphCompiler->GetGeneratedFilePaths()]()
  87. {
  88. switch (state)
  89. {
  90. case GraphCompiler::State::Idle:
  91. break;
  92. case GraphCompiler::State::Compiling:
  93. GraphDocumentRequestBus::Event(
  94. documentId, &GraphDocumentRequestBus::Events::SetGeneratedFilePaths, AZStd::vector<AZStd::string>{});
  95. GraphDocumentNotificationBus::Event(
  96. toolId, &GraphDocumentNotificationBus::Events::OnCompileGraphStarted, documentId);
  97. break;
  98. case GraphCompiler::State::Processing:
  99. break;
  100. case GraphCompiler::State::Complete:
  101. GraphDocumentRequestBus::Event(
  102. documentId, &GraphDocumentRequestBus::Events::SetGeneratedFilePaths, generatedFiles);
  103. GraphDocumentNotificationBus::Event(
  104. toolId, &GraphDocumentNotificationBus::Events::OnCompileGraphCompleted, documentId);
  105. break;
  106. case GraphCompiler::State::Failed:
  107. GraphDocumentNotificationBus::Event(
  108. toolId, &GraphDocumentNotificationBus::Events::OnCompileGraphFailed, documentId);
  109. break;
  110. case GraphCompiler::State::Canceled:
  111. break;
  112. }
  113. });
  114. });
  115. }
  116. GraphDocument::~GraphDocument()
  117. {
  118. AZ::SystemTickBus::Handler::BusDisconnect();
  119. GraphDocumentRequestBus::Handler::BusDisconnect();
  120. GraphCanvas::SceneNotificationBus::Handler::BusDisconnect();
  121. GraphModelIntegration::GraphControllerNotificationBus::Handler::BusDisconnect();
  122. DestroyGraph();
  123. m_graphId = GraphCanvas::GraphId();
  124. delete m_sceneEntity;
  125. m_sceneEntity = {};
  126. }
  127. DocumentTypeInfo GraphDocument::BuildDocumentTypeInfo(
  128. const AZStd::string& documentTypeName,
  129. const AZStd::vector<AZStd::string>& documentTypeExtensions,
  130. const AZStd::vector<AZStd::string>& documentTypeTemplateExtensions,
  131. const AZStd::string& defaultDocumentTypeTemplatePath,
  132. AZStd::shared_ptr<GraphModel::GraphContext> graphContext,
  133. AZStd::function<AZStd::shared_ptr<GraphCompiler>()> graphCompilerCreateFn)
  134. {
  135. DocumentTypeInfo documentType;
  136. documentType.m_documentTypeName = documentTypeName;
  137. documentType.m_documentFactoryCallback = [graphContext, graphCompilerCreateFn](const AZ::Crc32& toolId, const DocumentTypeInfo& documentTypeInfo) {
  138. return aznew GraphDocument(
  139. toolId, documentTypeInfo, graphContext, graphCompilerCreateFn ? graphCompilerCreateFn() : AZStd::shared_ptr<GraphCompiler>());
  140. };
  141. for (const auto& extension : documentTypeExtensions)
  142. {
  143. documentType.m_supportedExtensionsToOpen.push_back({ documentTypeName, extension });
  144. documentType.m_supportedExtensionsToSave.push_back({ documentTypeName, extension });
  145. }
  146. for (const auto& extension : documentTypeTemplateExtensions)
  147. {
  148. documentType.m_supportedExtensionsToCreate.push_back({ documentTypeName + " Template", extension });
  149. }
  150. documentType.m_defaultDocumentTemplate = defaultDocumentTypeTemplatePath;
  151. return documentType;
  152. }
  153. DocumentObjectInfoVector GraphDocument::GetObjectInfo() const
  154. {
  155. DocumentObjectInfoVector objects = AtomToolsDocument::GetObjectInfo();
  156. // Build a container of reflected object info specifically for the specialized graph canvas nodes that are not covered by graph model.
  157. DocumentObjectInfoVector objectInfoForGraphCanvasNodes = GetObjectInfoForGraphCanvasNodes();
  158. objects.insert(objects.end(), objectInfoForGraphCanvasNodes.begin(), objectInfoForGraphCanvasNodes.end());
  159. // Reserve and register reflected objects for all of the property group in the document.
  160. objects.reserve(objects.size() + m_groups.size());
  161. for (const auto& group : m_groups)
  162. {
  163. if (!group->m_properties.empty())
  164. {
  165. DocumentObjectInfo objectInfo;
  166. objectInfo.m_visible = group->m_visible;
  167. objectInfo.m_name = group->m_name;
  168. objectInfo.m_displayName = group->m_displayName;
  169. objectInfo.m_description = group->m_description;
  170. objectInfo.m_objectType = azrtti_typeid<DynamicPropertyGroup>();
  171. objectInfo.m_objectPtr = const_cast<DynamicPropertyGroup*>(group.get());
  172. objects.emplace_back(AZStd::move(objectInfo));
  173. }
  174. }
  175. return objects;
  176. }
  177. bool GraphDocument::Open(const AZStd::string& loadPath)
  178. {
  179. if (!AtomToolsDocument::Open(loadPath))
  180. {
  181. return false;
  182. }
  183. auto loadResult = AZ::JsonSerializationUtils::LoadAnyObjectFromFile(m_absolutePath);
  184. if (!loadResult || !loadResult.GetValue().is<GraphModel::Graph>())
  185. {
  186. return OpenFailed();
  187. }
  188. // Cloning loaded data using the serialize context because the graph does not have a copy or move constructor
  189. AZ::SerializeContext* serializeContext = {};
  190. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  191. AZ_Assert(serializeContext, "Failed to acquire application serialize context.");
  192. GraphModel::GraphPtr graph;
  193. graph.reset(serializeContext->CloneObject(AZStd::any_cast<const GraphModel::Graph>(&loadResult.GetValue())));
  194. m_modified = false;
  195. CreateGraph(graph);
  196. m_compileGraphQueued |= GetSettingsValue("/O3DE/AtomToolsFramework/GraphCompiler/CompileOnOpen", true);
  197. return OpenSucceeded();
  198. }
  199. bool GraphDocument::Save()
  200. {
  201. if (!AtomToolsDocument::Save())
  202. {
  203. // SaveFailed has already been called so just forward the result without additional notifications.
  204. // TODO Replace bool return value with enum for open and save states.
  205. return false;
  206. }
  207. AZ_Error("GraphDocument", m_graph, "Attempting to save invalid graph object.");
  208. if (!m_graph || !AZ::JsonSerializationUtils::SaveObjectToFile(m_graph.get(), m_savePathNormalized))
  209. {
  210. return SaveFailed();
  211. }
  212. m_modified = false;
  213. m_absolutePath = m_savePathNormalized;
  214. m_compileGraphQueued |= GetSettingsValue("/O3DE/AtomToolsFramework/GraphCompiler/CompileOnSave", true);
  215. return SaveSucceeded();
  216. }
  217. bool GraphDocument::SaveAsCopy(const AZStd::string& savePath)
  218. {
  219. if (!AtomToolsDocument::SaveAsCopy(savePath))
  220. {
  221. // SaveFailed has already been called so just forward the result without additional notifications.
  222. // TODO Replace bool return value with enum for open and save states.
  223. return false;
  224. }
  225. AZ_Error("GraphDocument", m_graph, "Attempting to save invalid graph object.");
  226. if (!m_graph || !AZ::JsonSerializationUtils::SaveObjectToFile(m_graph.get(), m_savePathNormalized))
  227. {
  228. return SaveFailed();
  229. }
  230. m_modified = false;
  231. m_absolutePath = m_savePathNormalized;
  232. m_compileGraphQueued |= GetSettingsValue("/O3DE/AtomToolsFramework/GraphCompiler/CompileOnSave", true);
  233. return SaveSucceeded();
  234. }
  235. bool GraphDocument::SaveAsChild(const AZStd::string& savePath)
  236. {
  237. if (!AtomToolsDocument::SaveAsChild(savePath))
  238. {
  239. // SaveFailed has already been called so just forward the result without additional notifications.
  240. // TODO Replace bool return value with enum for open and save states.
  241. return false;
  242. }
  243. AZ_Error("GraphDocument", m_graph, "Attempting to save invalid graph object. ");
  244. if (!m_graph || !AZ::JsonSerializationUtils::SaveObjectToFile(m_graph.get(), m_savePathNormalized))
  245. {
  246. return SaveFailed();
  247. }
  248. m_modified = false;
  249. m_absolutePath = m_savePathNormalized;
  250. m_compileGraphQueued |= GetSettingsValue("/O3DE/AtomToolsFramework/GraphCompiler/CompileOnSave", true);
  251. return SaveSucceeded();
  252. }
  253. bool GraphDocument::IsModified() const
  254. {
  255. return m_modified;
  256. }
  257. bool GraphDocument::BeginEdit()
  258. {
  259. RecordGraphState();
  260. return true;
  261. }
  262. bool GraphDocument::EndEdit()
  263. {
  264. auto undoState = m_graphStateForUndoRedo;
  265. RecordGraphState();
  266. auto redoState = m_graphStateForUndoRedo;
  267. if (undoState != redoState)
  268. {
  269. AddUndoRedoHistory(
  270. [this, undoState]() { RestoreGraphState(undoState); },
  271. [this, redoState]() { RestoreGraphState(redoState); });
  272. m_modified = true;
  273. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
  274. GraphCanvas::ViewRequestBus::Event(m_graphId, &GraphCanvas::ViewRequests::RefreshView);
  275. m_compileGraphQueued |= GetSettingsValue("/O3DE/AtomToolsFramework/GraphCompiler/CompileOnEdit", true);
  276. }
  277. return true;
  278. }
  279. void GraphDocument::Clear()
  280. {
  281. DestroyGraph();
  282. m_graphStateForUndoRedo.clear();
  283. m_groups.clear();
  284. m_modified = false;
  285. AtomToolsDocument::Clear();
  286. }
  287. GraphModel::GraphPtr GraphDocument::GetGraph() const
  288. {
  289. return m_graph;
  290. }
  291. GraphCanvas::GraphId GraphDocument::GetGraphId() const
  292. {
  293. return m_graphId;
  294. }
  295. AZStd::string GraphDocument::GetGraphName() const
  296. {
  297. if (m_absolutePath.empty())
  298. {
  299. return "untitled";
  300. }
  301. // Sanitize the document name to remove any illegal characters that could not be used as symbols in generated code
  302. AZStd::string documentName;
  303. AZ::StringFunc::Path::GetFileName(m_absolutePath.c_str(), documentName);
  304. return GetSymbolNameFromText(documentName);
  305. }
  306. void GraphDocument::SetGeneratedFilePaths(const AZStd::vector<AZStd::string>& pathas)
  307. {
  308. m_generatedFiles = pathas;
  309. }
  310. const AZStd::vector<AZStd::string>& GraphDocument::GetGeneratedFilePaths() const
  311. {
  312. return m_generatedFiles;
  313. }
  314. bool GraphDocument::CompileGraph()
  315. {
  316. // If a compiler was supplied But not in a state that can be reinitialized then return failure. If compiling was queued, attempts
  317. // will continue to be made until the background compilation job is cancelled or complete.
  318. if (!m_graphCompiler || !m_graphCompiler->Reset())
  319. {
  320. return false;
  321. }
  322. m_compileGraphQueued = false;
  323. // Serialize the graph data into a buffer that's copied and deserialized in the compilation job. This will allow
  324. // editing to continue while the last serialized version of the graph is compiled in the background.
  325. AZStd::vector<AZ::u8> graphBuffer;
  326. AZ::IO::ByteContainerStream<decltype(graphBuffer)> graphBufferStream(&graphBuffer);
  327. AZ::Utils::SaveObjectToStream(graphBufferStream, AZ::ObjectStream::ST_BINARY, m_graph.get());
  328. auto compileJobFn = [graphBuffer,
  329. graphCompiler = m_graphCompiler,
  330. graphContext = m_graphContext,
  331. graphName = GetGraphName(),
  332. graphPath = GetAbsolutePath()]()
  333. {
  334. // Deserialize the buffer to create a copy of the graph that can be safely transformed from the job thread.
  335. GraphModel::GraphPtr graph = AZStd::make_shared<GraphModel::Graph>(graphContext);
  336. AZ::Utils::LoadObjectFromBufferInPlace(graphBuffer.data(), graphBuffer.size(), *graph.get());
  337. graph->PostLoadSetup(graphContext);
  338. graphCompiler->CompileGraph(graph, graphName, graphPath);
  339. };
  340. auto job = AZ::CreateJobFunction(compileJobFn, true);
  341. job->Start();
  342. return true;
  343. }
  344. void GraphDocument::QueueCompileGraph()
  345. {
  346. m_compileGraphQueued = true;
  347. }
  348. bool GraphDocument::IsCompileGraphQueued() const
  349. {
  350. return m_compileGraphQueued;
  351. }
  352. void GraphDocument::OnSystemTick()
  353. {
  354. if (m_buildPropertiesQueued)
  355. {
  356. BuildEditablePropertyGroups();
  357. }
  358. if (IsCompileGraphQueued())
  359. {
  360. if (m_compileGraphQueueTime <= AZStd::chrono::steady_clock::now())
  361. {
  362. if (CompileGraph())
  363. {
  364. const AZ::u64 intervalMs =
  365. GetSettingsValue("/O3DE/AtomToolsFramework/GraphCompiler/QueueGraphCompileIntervalMs", (AZ::u64)500);
  366. m_compileGraphQueueTime = AZStd::chrono::steady_clock::now() + AZStd::chrono::milliseconds(intervalMs);
  367. }
  368. }
  369. }
  370. }
  371. void GraphDocument::OnGraphModelSlotModified([[maybe_unused]] GraphModel::SlotPtr slot)
  372. {
  373. m_modified = true;
  374. m_buildPropertiesQueued = true;
  375. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
  376. }
  377. void GraphDocument::OnGraphModelRequestUndoPoint()
  378. {
  379. // Undo and redo is being handled differently for edits received directly from graph model and graph canvas. By the time this is
  380. // reached, changes have already been applied to the graph. Other operations performed in the document class ensure that a last
  381. // known good graph state was recorded after every change to be able to undo this operation. .
  382. auto undoState = m_graphStateForUndoRedo;
  383. RecordGraphState();
  384. auto redoState = m_graphStateForUndoRedo;
  385. if (undoState != redoState)
  386. {
  387. AddUndoRedoHistory(
  388. [this, undoState]() { RestoreGraphState(undoState); },
  389. [this, redoState]() { RestoreGraphState(redoState); });
  390. m_modified = true;
  391. m_buildPropertiesQueued = true;
  392. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
  393. m_compileGraphQueued |= GetSettingsValue("/O3DE/AtomToolsFramework/GraphCompiler/CompileOnEdit", true);
  394. }
  395. }
  396. void GraphDocument::OnGraphModelTriggerUndo()
  397. {
  398. Undo();
  399. }
  400. void GraphDocument::OnGraphModelTriggerRedo()
  401. {
  402. Redo();
  403. }
  404. void GraphDocument::OnSelectionChanged()
  405. {
  406. m_buildPropertiesQueued = true;
  407. }
  408. void GraphDocument::RecordGraphState()
  409. {
  410. // Forcing all of the graph model metadata to be updated before serializing to the binary stream. This will ensure that data for
  411. // bookmarks, comments, and groups is recorded.
  412. GraphCanvas::GraphModelRequestBus::Event(m_graphId, &GraphCanvas::GraphModelRequests::OnSaveDataDirtied, m_graphId);
  413. // Serialize the current graph to a byte stream so that it can be restored with undo redo operations.
  414. m_graphStateForUndoRedo.clear();
  415. AZ::IO::ByteContainerStream<decltype(m_graphStateForUndoRedo)> undoGraphStateStream(&m_graphStateForUndoRedo);
  416. AZ::Utils::SaveObjectToStream(undoGraphStateStream, AZ::ObjectStream::ST_BINARY, m_graph.get());
  417. }
  418. void GraphDocument::RestoreGraphState(const AZStd::vector<AZ::u8>& graphState)
  419. {
  420. // Restore a version of the graph that was previously serialized to a byte stream
  421. m_graphStateForUndoRedo = graphState;
  422. GraphModel::GraphPtr graph = AZStd::make_shared<GraphModel::Graph>(m_graphContext);
  423. AZ::Utils::LoadObjectFromBufferInPlace(m_graphStateForUndoRedo.data(), m_graphStateForUndoRedo.size(), *graph.get());
  424. m_modified = true;
  425. CreateGraph(graph);
  426. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
  427. m_compileGraphQueued |= GetSettingsValue("/O3DE/AtomToolsFramework/GraphCompiler/CompileOnEdit", true);
  428. }
  429. void GraphDocument::CreateGraph(GraphModel::GraphPtr graph)
  430. {
  431. DestroyGraph();
  432. if (graph)
  433. {
  434. m_graph = graph;
  435. m_graph->PostLoadSetup(m_graphContext);
  436. // The graph controller will create all of the scene items on construction.
  437. GraphModelIntegration::GraphManagerRequestBus::Broadcast(
  438. &GraphModelIntegration::GraphManagerRequests::CreateGraphController, m_graphId, m_graph);
  439. RecordGraphState();
  440. m_buildPropertiesQueued = true;
  441. }
  442. }
  443. void GraphDocument::DestroyGraph()
  444. {
  445. // The graph controller does not currently delete all of the scene items when it's destroyed.
  446. GraphModelIntegration::GraphManagerRequestBus::Broadcast(
  447. &GraphModelIntegration::GraphManagerRequests::DeleteGraphController, m_graphId);
  448. m_graph.reset();
  449. // This needs to be done whenever the graph is destroyed during undo and redo so that the previous version of the data is deleted.
  450. GraphCanvas::GraphModelRequestBus::Event(m_graphId, &GraphCanvas::GraphModelRequests::RequestPushPreventUndoStateUpdate);
  451. GraphCanvas::SceneRequestBus::Event(m_graphId, &GraphCanvas::SceneRequests::ClearScene);
  452. GraphCanvas::GraphModelRequestBus::Event(m_graphId, &GraphCanvas::GraphModelRequests::RequestPopPreventUndoStateUpdate);
  453. }
  454. void GraphDocument::BuildEditablePropertyGroups()
  455. {
  456. m_buildPropertiesQueued = false;
  457. // Sort nodes according to their connection so they appear in a consistent order in the inspector
  458. GraphModel::NodePtrList selectedNodes;
  459. GraphModelIntegration::GraphControllerRequestBus::EventResult(
  460. selectedNodes, m_graphId, &GraphModelIntegration::GraphControllerRequests::GetSelectedNodes);
  461. SortNodesInExecutionOrder(selectedNodes);
  462. m_groups.clear();
  463. m_groups.reserve(selectedNodes.size());
  464. for (const auto& currentNode : selectedNodes)
  465. {
  466. // Create a new property group and set up the header to match the node
  467. AZStd::shared_ptr<DynamicPropertyGroup> group;
  468. group.reset(aznew DynamicPropertyGroup);
  469. group->m_displayName = GetDisplayNameFromText(AZStd::string::format("Node%u %s", currentNode->GetId(), currentNode->GetTitle()));
  470. group->m_name = GetSymbolNameFromText(group->m_displayName);
  471. group->m_description = currentNode->GetSubTitle();
  472. group->m_properties.reserve(currentNode->GetSlotDefinitions().size());
  473. // Visit all of the slots in the order to add properties to the container for the inspector.
  474. for (const auto& slotDefinition : currentNode->GetSlotDefinitions())
  475. {
  476. if (auto currentSlot = currentNode->GetSlot(slotDefinition->GetName()))
  477. {
  478. if (currentSlot->GetSlotDirection() == GraphModel::SlotDirection::Input)
  479. {
  480. // Create and add a dynamic property for each input slot on the node
  481. DynamicPropertyConfig propertyConfig;
  482. propertyConfig.m_id = currentSlot->GetName();
  483. propertyConfig.m_name = currentSlot->GetName();
  484. propertyConfig.m_displayName = currentSlot->GetDisplayName();
  485. propertyConfig.m_groupName = group->m_name;
  486. propertyConfig.m_groupDisplayName = group->m_displayName;
  487. propertyConfig.m_description = currentSlot->GetDescription();
  488. propertyConfig.m_enumValues = currentSlot->GetEnumValues();
  489. propertyConfig.m_defaultValue = currentSlot->GetDefaultValue();
  490. propertyConfig.m_originalValue = currentSlot->GetValue();
  491. propertyConfig.m_parentValue = currentSlot->GetDefaultValue();
  492. propertyConfig.m_readOnly = !currentSlot->GetConnections().empty();
  493. propertyConfig.m_showThumbnail = true;
  494. // Set up the change call back to apply the value of the property from the inspector to the slot. This could
  495. // also send a document modified notifications and queue regeneration of shader and material assets but the
  496. // compilation process and going through the ap is not responsive enough for this to matter.
  497. propertyConfig.m_dataChangeCallback = [currentSlot, graphId = m_graphId](const AZStd::any& value)
  498. {
  499. currentSlot->SetValue(value);
  500. // Retrieve and refresh the node property displays with the updated slot value.
  501. GraphCanvas::SlotId slotId{};
  502. GraphModelIntegration::GraphControllerRequestBus::EventResult(
  503. slotId, graphId, &GraphModelIntegration::GraphControllerRequests::GetSlotIdBySlot, currentSlot);
  504. GraphCanvas::NodePropertyRequestBus::Event(slotId, [](GraphCanvas::NodePropertyRequests* nodePropertyRequests) {
  505. if (auto display = nodePropertyRequests->GetNodePropertyDisplay())
  506. {
  507. display->UpdateDisplay();
  508. }
  509. });
  510. return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
  511. };
  512. group->m_properties.emplace_back(AZStd::move(propertyConfig));
  513. }
  514. }
  515. }
  516. m_groups.emplace_back(group);
  517. }
  518. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentObjectInfoInvalidated, m_id);
  519. }
  520. DocumentObjectInfoVector GraphDocument::GetObjectInfoForGraphCanvasNodes() const
  521. {
  522. DocumentObjectInfoVector objects;
  523. // Reserve and register reflected objects for all of the selected graph canvas nodes that do not mirror any of the graph model nodes
  524. // that have been added to the graph. This should cover bookmarks, comments, and groups.
  525. AZStd::vector<AZ::EntityId> selectedItems;
  526. GraphCanvas::SceneRequestBus::EventResult(selectedItems, m_graphId, &GraphCanvas::SceneRequests::GetSelectedItems);
  527. // Optimizing the container to only have nodes with property components before sorting.
  528. AZStd::erase_if(
  529. selectedItems,
  530. [](const auto& selectedItem)
  531. {
  532. return GraphCanvas::GraphCanvasPropertyBus::FindFirstHandler(selectedItem) == nullptr;
  533. });
  534. objects.reserve(objects.size() + selectedItems.size());
  535. // The order that selected nodes appear in the container is not deterministic. To compensate for this, we sort by position to ensure
  536. // that nodes always appear in the inspector in a consistent order.
  537. AZStd::sort(
  538. selectedItems.begin(),
  539. selectedItems.end(),
  540. [](const auto& selectedItem1, const auto& selectedItem2)
  541. {
  542. AZ::Vector2 selectedItemPosition1{};
  543. GraphCanvas::GeometryRequestBus::EventResult(
  544. selectedItemPosition1, selectedItem1, &GraphCanvas::GeometryRequests::GetPosition);
  545. AZ::Vector2 selectedItemPosition2{};
  546. GraphCanvas::GeometryRequestBus::EventResult(
  547. selectedItemPosition2, selectedItem2, &GraphCanvas::GeometryRequests::GetPosition);
  548. return selectedItemPosition1.IsLessThan(selectedItemPosition2);
  549. });
  550. // Some graph canvas node property components do not have any visible properties, like the bookmark anchor visual component. These
  551. // will not be added to the graph document inspector.
  552. const AZStd::unordered_set<AZ::Uuid> ignoredTypeIds{
  553. AZ::Uuid("{AD921E77-962B-417F-88FB-500FA679DFDF}") // BookmarkAnchorVisualComponent
  554. };
  555. // After all of the selected graph canvas nodes have been sorted, search for those with editable property components and add them to
  556. // the list of reflected objects.
  557. for (const auto& selectedItem : selectedItems)
  558. {
  559. // Some graph canvas nodes have multiple editable property components, like groups and bookmarks. All of the property components
  560. // will be added in relative order except for those in the ignore list.
  561. DocumentObjectInfoVector selectedItemObjects;
  562. GraphCanvas::GraphCanvasPropertyBus::EnumerateHandlersId(
  563. selectedItem,
  564. [&](GraphCanvas::GraphCanvasPropertyInterface* propertyInterface) -> bool
  565. {
  566. AZ::Component* component = propertyInterface->GetPropertyComponent();
  567. if (AzToolsFramework::ShouldInspectorShowComponent(component) && !ignoredTypeIds.contains(component->RTTI_GetType()))
  568. {
  569. DocumentObjectInfo objectInfo;
  570. objectInfo.m_visible = true;
  571. objectInfo.m_name = GetSymbolNameFromText(component->RTTI_GetTypeName());
  572. objectInfo.m_displayName = objectInfo.m_description = GetDisplayNameFromText(component->RTTI_GetTypeName());
  573. objectInfo.m_objectType = component->RTTI_GetType();
  574. objectInfo.m_objectPtr = component;
  575. selectedItemObjects.emplace_back(AZStd::move(objectInfo));
  576. }
  577. // Continue enumeration.
  578. return true;
  579. });
  580. // In addition to presorting nodes by position we will sort all of the property components by name to guarantee a consistent
  581. // order in the inspector.
  582. AZStd::sort(
  583. selectedItemObjects.begin(),
  584. selectedItemObjects.end(),
  585. [](const auto& objectInfo1, const auto& objectInfo2)
  586. {
  587. return objectInfo1.m_displayName < objectInfo2.m_displayName;
  588. });
  589. objects.insert(objects.end(), selectedItemObjects.begin(), selectedItemObjects.end());
  590. }
  591. return objects;
  592. }
  593. } // namespace AtomToolsFramework