Node.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  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. // AZ
  9. #include <AzCore/RTTI/BehaviorContext.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/std/smart_ptr/make_shared.h>
  13. // Graph Model
  14. #include <GraphModel/Model/Connection.h>
  15. #include <GraphModel/Model/Graph.h>
  16. #include <GraphModel/Model/Node.h>
  17. #include <GraphModel/Model/Slot.h>
  18. namespace GraphModel
  19. {
  20. static constexpr int InvalidExtendableSlot = -1;
  21. void Node::Reflect(AZ::ReflectContext* context)
  22. {
  23. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  24. {
  25. serializeContext->Class<Node, GraphElement>()
  26. ->Version(0)
  27. // m_id isn't reflected because this information is already stored in the Graph's node map
  28. // m_outputDataSlots isn't reflected because its Slot::m_value field is unused
  29. // m_inputEventSlots isn't reflected because its Slot::m_value field is unused
  30. // m_outputEventSlots isn't reflected because its Slot::m_value field is unused
  31. ->Field("m_propertySlots", &Node::m_propertySlots)
  32. ->Field("m_inputDataSlots", &Node::m_inputDataSlots)
  33. ->Field("m_extendableSlots", &Node::m_extendableSlots)
  34. ;
  35. serializeContext->RegisterGenericType<NodePtr>();
  36. serializeContext->RegisterGenericType<NodePtrList>();
  37. }
  38. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  39. {
  40. behaviorContext->Class<Node>("GraphModelNode")
  41. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  42. ->Attribute(AZ::Script::Attributes::Category, "Editor")
  43. ->Attribute(AZ::Script::Attributes::Module, "editor.graph")
  44. ->Method("GetTitle", &Node::GetTitle)
  45. ->Method("GetSubTitle", &Node::GetSubTitle)
  46. ->Method("GetNodeType", &Node::GetNodeType)
  47. ->Method("GetId", &Node::GetId)
  48. ->Method("GetMaxInputDepth", &Node::GetMaxInputDepth)
  49. ->Method("GetMaxOutputDepth", &Node::GetMaxOutputDepth)
  50. ->Method("HasSlots", &Node::HasSlots)
  51. ->Method("HasInputSlots", &Node::HasInputSlots)
  52. ->Method("HasOutputSlots", &Node::HasOutputSlots)
  53. ->Method("HasConnections", &Node::HasConnections)
  54. ->Method("HasInputConnections", &Node::HasInputConnections)
  55. ->Method("HasOutputConnections", &Node::HasOutputConnections)
  56. ->Method("HasInputConnectionFromNode", &Node::HasInputConnectionFromNode)
  57. ->Method("HasOutputConnectionToNode", &Node::HasOutputConnectionToNode)
  58. ->Method("Contains", &Node::Contains)
  59. ->Method("GetSlotDefinitions", &Node::GetSlotDefinitions)
  60. ->Method("GetSlots", static_cast<const Node::SlotMap& (Node::*)()>(&Node::GetSlots))
  61. ->Method("GetSlot", static_cast<SlotPtr (Node::*)(const SlotId&)>(&Node::GetSlot))
  62. ->Method("GetExtendableSlots", &Node::GetExtendableSlots)
  63. ->Method("GetExtendableSlotCount", &Node::GetExtendableSlotCount)
  64. ->Method("DeleteSlot", &Node::DeleteSlot)
  65. ->Method("CanDeleteSlot", &Node::CanDeleteSlot)
  66. ->Method("AddExtendedSlot", &Node::AddExtendedSlot)
  67. ->Method("ClearCachedData", &Node::ClearCachedData)
  68. ;
  69. }
  70. }
  71. Node::Node(GraphPtr graph)
  72. : GraphElement(graph)
  73. {
  74. }
  75. void Node::PostLoadSetup(GraphPtr graph, NodeId id)
  76. {
  77. AZ_Assert(nullptr == GetGraph(), "Node isn't freshly loaded.");
  78. AZ_Assert(m_id == INVALID_NODE_ID, "Node isn't freshly loaded.");
  79. m_graph = graph;
  80. m_id = id;
  81. PostLoadSetup();
  82. }
  83. void Node::PostLoadSetup()
  84. {
  85. RegisterSlots();
  86. // Moving slots between input data slot and property slot containers in case the layout of the slot definitions change.
  87. for (const auto& def : m_inputDataSlotDefinitions)
  88. {
  89. for (const auto& slot : m_propertySlots)
  90. {
  91. if (def->GetName() == slot.first.m_name)
  92. {
  93. m_inputDataSlots.insert(slot);
  94. m_propertySlots.erase(slot.first);
  95. break;
  96. }
  97. }
  98. }
  99. for (const auto& def : m_propertySlotDefinitions)
  100. {
  101. for (const auto& slot : m_inputDataSlots)
  102. {
  103. if (def->GetName() == slot.first.m_name)
  104. {
  105. m_propertySlots.insert(slot);
  106. m_inputDataSlots.erase(slot.first);
  107. break;
  108. }
  109. }
  110. }
  111. // Make sure the loaded Slot data aligns with the Node's input slot descriptions
  112. SyncAndSetupSlots(m_propertySlots, m_propertySlotDefinitions);
  113. SyncAndSetupSlots(m_inputDataSlots, m_inputDataSlotDefinitions);
  114. SyncAndSetupExtendableSlots();
  115. // These slots types are a bit different, because they don't actually have any data to be serialized, so instead of SyncAndSetupSlots we need to create them.
  116. CreateSlotData(m_outputDataSlots, m_outputDataSlotDefinitions);
  117. CreateSlotData(m_inputEventSlots, m_inputEventSlotDefinitions);
  118. CreateSlotData(m_outputEventSlots, m_outputEventSlotDefinitions);
  119. [[maybe_unused]] int numExtendableSlots = 0;
  120. for (const auto& slotPair : m_extendableSlots)
  121. {
  122. numExtendableSlots += aznumeric_cast<int>(slotPair.second.size());
  123. }
  124. AZ_Assert(m_allSlots.size() == m_propertySlots.size() + m_inputDataSlots.size() + m_outputDataSlots.size() + m_inputEventSlots.size() + m_outputEventSlots.size() + numExtendableSlots, "Slot counts don't match");
  125. AZ_Assert(m_allSlotDefinitions.size() == m_propertySlotDefinitions.size() + m_inputDataSlotDefinitions.size() + m_outputDataSlotDefinitions.size() + m_inputEventSlotDefinitions.size() + m_outputEventSlotDefinitions.size() + m_extendableSlotDefinitions.size(), "SlotDefinition counts don't match");
  126. ClearCachedData();
  127. }
  128. //! Returns the name that will be displayed as the sub-title of the Node in the UI
  129. const char* Node::GetSubTitle() const
  130. {
  131. return "";
  132. }
  133. void Node::CreateSlotData()
  134. {
  135. AZ_Assert(m_allSlots.empty(), "CreateSlotData() should only be called once after creating a new node.");
  136. CreateSlotData(m_propertySlots, m_propertySlotDefinitions);
  137. CreateSlotData(m_inputDataSlots, m_inputDataSlotDefinitions);
  138. CreateSlotData(m_outputDataSlots, m_outputDataSlotDefinitions);
  139. CreateSlotData(m_inputEventSlots, m_inputEventSlotDefinitions);
  140. CreateSlotData(m_outputEventSlots, m_outputEventSlotDefinitions);
  141. CreateExtendableSlotData();
  142. }
  143. void Node::ClearCachedData()
  144. {
  145. {
  146. AZStd::scoped_lock lock(m_maxInputDepthMutex);
  147. m_maxInputDepth = AZStd::numeric_limits<uint32_t>::max();
  148. }
  149. {
  150. AZStd::scoped_lock lock(m_maxOutputDepthMutex);
  151. m_maxOutputDepth = AZStd::numeric_limits<uint32_t>::max();
  152. }
  153. for (auto& slotPair : m_allSlots)
  154. {
  155. slotPair.second->ClearCachedData();
  156. }
  157. }
  158. void Node::CreateSlotData(SlotMap& slotMap, const SlotDefinitionList& slotDefinitionList)
  159. {
  160. AZ_Assert(slotMap.empty(), "This node isn't freshly initialized");
  161. for (SlotDefinitionPtr slotDefinition : slotDefinitionList)
  162. {
  163. SlotPtr slot = AZStd::make_shared<Slot>(GetGraph(), slotDefinition);
  164. slot->SetValue(slotDefinition->GetDefaultValue());
  165. SlotId slotId(slotDefinition->GetName());
  166. auto newEntry = AZStd::make_pair(slotId, slot);
  167. slotMap.insert(newEntry);
  168. m_allSlots.insert(newEntry);
  169. }
  170. }
  171. void Node::CreateExtendableSlotData()
  172. {
  173. for (SlotDefinitionPtr slotDefinition : m_extendableSlotDefinitions)
  174. {
  175. // Skip creating slots for this definition if a set already exists in the map, since we
  176. // use this method to populate slots that have been loaded on existing nodes where
  177. // new slots have been added in addition to creating slots on nodes for the first time
  178. const SlotName& slotName = slotDefinition->GetName();
  179. if (m_extendableSlots.find(slotName) != m_extendableSlots.end())
  180. {
  181. continue;
  182. }
  183. ExtendableSlotSet extendableSet;
  184. // When creating a new node, we need to populate enough extendable slots to satisfy
  185. // the minimum requirement of the definition.
  186. int minimumSlots = slotDefinition->GetMinimumSlots();
  187. for (int i = 0; i < minimumSlots; ++i)
  188. {
  189. SlotPtr slot = AZStd::make_shared<Slot>(GetGraph(), slotDefinition, i);
  190. slot->SetValue(slotDefinition->GetDefaultValue());
  191. m_allSlots.insert(AZStd::make_pair(slot->GetSlotId(), slot));
  192. extendableSet.insert(slot);
  193. }
  194. m_extendableSlots.insert(AZStd::make_pair(slotName, extendableSet));
  195. }
  196. }
  197. void Node::SyncAndSetupSlots(SlotMap& slotData, Node::SlotDefinitionList& slotDefinitions)
  198. {
  199. // Do PostLoadSetup to attach each Slot to its SlotDefinition
  200. // Also remove any Slot that doesn't have a corresponding SlotDefinition
  201. for (auto slotDataIter = slotData.begin(); slotDataIter != slotData.end(); /* increment in loop */)
  202. {
  203. const SlotId& slotId = slotDataIter->first;
  204. const SlotName& slotName = slotId.m_name;
  205. SlotPtr slot = slotDataIter->second;
  206. auto slotDefinitionIter = AZStd::find_if(
  207. slotDefinitions.begin(),
  208. slotDefinitions.end(),
  209. [&slotName](SlotDefinitionPtr slotDefinition)
  210. {
  211. return slotDefinition->GetName() == slotName;
  212. });
  213. if (slotDefinitionIter == slotDefinitions.end())
  214. {
  215. AZ_Warning(
  216. GetGraph()->GetSystemName(), false, "Found data for unrecognized slot [%s]. It will be ignored.", slotName.c_str());
  217. slotDataIter = slotData.erase(slotDataIter);
  218. }
  219. else
  220. {
  221. // If PostLoadSetup fails (could be due to type mismatch)
  222. slot->PostLoadSetup(GetGraph(), *slotDefinitionIter);
  223. ++slotDataIter;
  224. }
  225. }
  226. // Make sure all SlotDefinitions have slot data. This would normally happen when the code for a Node class has been changed to add a new slot.
  227. for (SlotDefinitionPtr slotDefinition : slotDefinitions)
  228. {
  229. SlotId slotId(slotDefinition->GetName());
  230. if (slotData.find(slotId) == slotData.end())
  231. {
  232. AZ_Warning(GetGraph()->GetSystemName(), false, "No data found for slot [%s]. It will be filled with default values.", slotDefinition->GetName().c_str());
  233. SlotPtr slot = AZStd::make_shared<Slot>(GetGraph(), slotDefinition);
  234. slotData[slotId] = slot;
  235. }
  236. }
  237. m_allSlots.insert(slotData.begin(), slotData.end());
  238. }
  239. void Node::SyncAndSetupExtendableSlots()
  240. {
  241. // Do PostLoadSetup to attach each Slot to its SlotDefinition
  242. // Also remove any Slot that doesn't have a corresponding SlotDefinition
  243. for (auto slotDataIter = m_extendableSlots.begin(); slotDataIter != m_extendableSlots.end(); /* increment in loop */)
  244. {
  245. const SlotName& slotName = slotDataIter->first;
  246. auto slotDefinitionIter = AZStd::find_if(
  247. m_extendableSlotDefinitions.begin(),
  248. m_extendableSlotDefinitions.end(),
  249. [&slotName](SlotDefinitionPtr slotDefinition)
  250. {
  251. return slotDefinition->GetName() == slotName;
  252. });
  253. if (slotDefinitionIter == m_extendableSlotDefinitions.end())
  254. {
  255. AZ_Warning(
  256. GetGraph()->GetSystemName(), false, "Found data for unrecognized slot [%s]. It will be ignored.", slotName.c_str());
  257. slotDataIter = m_extendableSlots.erase(slotDataIter);
  258. }
  259. else
  260. {
  261. for (SlotPtr slot : slotDataIter->second)
  262. {
  263. // If PostLoadSetup fails (could be due to type mismatch)
  264. slot->PostLoadSetup(GetGraph(), *slotDefinitionIter);
  265. m_allSlots.insert(AZStd::make_pair(slot->GetSlotId(), slot));
  266. }
  267. ++slotDataIter;
  268. }
  269. }
  270. // Make sure all SlotDefinitions have slot data. This would normally happen when the code for a Node class has been changed to add a new slot.
  271. CreateExtendableSlotData();
  272. }
  273. //! Returns node type (general by default) which can be overriden for other types, such as wrapper nodes
  274. NodeType Node::GetNodeType() const
  275. {
  276. return NodeType::GeneralNode;
  277. }
  278. NodeId Node::GetId() const
  279. {
  280. return m_id;
  281. }
  282. uint32_t Node::GetMaxInputDepth() const
  283. {
  284. AZStd::scoped_lock lock(m_maxInputDepthMutex);
  285. if (m_maxInputDepth == AZStd::numeric_limits<uint32_t>::max())
  286. {
  287. m_maxInputDepth = 0;
  288. for (const auto& slotPair : m_inputDataSlots)
  289. {
  290. const auto& slot = slotPair.second;
  291. AZ_Assert(slot->GetSlotDirection() == GraphModel::SlotDirection::Input, "Slots in this container must be input slots.");
  292. for (const auto& connection : slot->GetConnections())
  293. {
  294. AZ_Assert(connection->GetSourceNode().get() != this, "This should never be the source node on an input connection.");
  295. AZ_Assert(connection->GetTargetNode().get() == this, "This should always be the target node on an input connection.");
  296. m_maxInputDepth = AZStd::max(m_maxInputDepth, connection->GetSourceNode()->GetMaxInputDepth() + 1);
  297. }
  298. }
  299. }
  300. return m_maxInputDepth;
  301. }
  302. uint32_t Node::GetMaxOutputDepth() const
  303. {
  304. AZStd::scoped_lock lock(m_maxOutputDepthMutex);
  305. if (m_maxOutputDepth == AZStd::numeric_limits<uint32_t>::max())
  306. {
  307. m_maxOutputDepth = 0;
  308. for (const auto& slotPair : m_outputDataSlots)
  309. {
  310. const auto& slot = slotPair.second;
  311. AZ_Assert(slot->GetSlotDirection() == GraphModel::SlotDirection::Output, "Slots in this container must be output slots.");
  312. for (const auto& connection : slot->GetConnections())
  313. {
  314. AZ_Assert(connection->GetSourceNode().get() == this, "This should always be the source node on an output connection.");
  315. AZ_Assert(connection->GetTargetNode().get() != this, "This should never be the target node on an output connection.");
  316. m_maxOutputDepth = AZStd::max(m_maxOutputDepth, connection->GetTargetNode()->GetMaxOutputDepth() + 1);
  317. }
  318. }
  319. }
  320. return m_maxOutputDepth;
  321. }
  322. bool Node::HasSlots() const
  323. {
  324. return !m_allSlots.empty();
  325. }
  326. bool Node::HasInputSlots() const
  327. {
  328. return !m_inputDataSlots.empty() || !m_inputEventSlots.empty();
  329. }
  330. bool Node::HasOutputSlots() const
  331. {
  332. return !m_outputDataSlots.empty() || !m_outputEventSlots.empty();
  333. }
  334. bool Node::HasConnections() const
  335. {
  336. return AZStd::any_of(m_allSlots.begin(), m_allSlots.end(), [](const auto& slotPair) {
  337. const auto& slot = slotPair.second;
  338. return !slot->GetConnections().empty();
  339. });
  340. }
  341. bool Node::HasInputConnections() const
  342. {
  343. return AZStd::any_of(m_inputDataSlots.begin(), m_inputDataSlots.end(), [](const auto& slotPair) {
  344. const auto& slot = slotPair.second;
  345. AZ_Assert(slot->GetSlotDirection() == GraphModel::SlotDirection::Input, "Slots in this container must be input slots.");
  346. return !slot->GetConnections().empty();
  347. });
  348. }
  349. bool Node::HasOutputConnections() const
  350. {
  351. return AZStd::any_of(m_outputDataSlots.begin(), m_outputDataSlots.end(), [](const auto& slotPair) {
  352. const auto& slot = slotPair.second;
  353. AZ_Assert(slot->GetSlotDirection() == GraphModel::SlotDirection::Output, "Slots in this container must be output slots.");
  354. return !slot->GetConnections().empty();
  355. });
  356. }
  357. bool Node::HasInputConnectionFromNode(ConstNodePtr node) const
  358. {
  359. return AZStd::any_of(m_inputDataSlots.begin(), m_inputDataSlots.end(), [&](const auto& slotPair) {
  360. const auto& slot = slotPair.second;
  361. AZ_Assert(slot->GetSlotDirection() == GraphModel::SlotDirection::Input, "Slots in this container must be input slots.");
  362. const auto& connections = slot->GetConnections();
  363. return AZStd::any_of(connections.begin(), connections.end(), [&](const auto& connection) {
  364. AZ_Assert(connection->GetSourceNode().get() != this, "This should never be the source node on an input connection.");
  365. AZ_Assert(connection->GetTargetNode().get() == this, "This should always be the target node on an input connection.");
  366. return connection->GetSourceNode() == node || connection->GetSourceNode()->HasInputConnectionFromNode(node);
  367. });
  368. });
  369. }
  370. bool Node::HasOutputConnectionToNode(ConstNodePtr node) const
  371. {
  372. return AZStd::any_of(m_outputDataSlots.begin(), m_outputDataSlots.end(), [&](const auto& slotPair) {
  373. const auto& slot = slotPair.second;
  374. AZ_Assert(slot->GetSlotDirection() == GraphModel::SlotDirection::Output, "Slots in this container must be output slots.");
  375. const auto& connections = slot->GetConnections();
  376. return AZStd::any_of(connections.begin(), connections.end(), [&](const auto& connection) {
  377. AZ_Assert(connection->GetSourceNode().get() == this, "This should always be the source node on an output connection.");
  378. AZ_Assert(connection->GetTargetNode().get() != this, "This should never be the target node on an output connection.");
  379. return connection->GetTargetNode() == node || connection->GetTargetNode()->HasOutputConnectionToNode(node);
  380. });
  381. });
  382. }
  383. bool Node::Contains(ConstSlotPtr slot) const
  384. {
  385. const auto slotItr = slot ? m_allSlots.find(slot->GetSlotId()) : m_allSlots.end();
  386. return slotItr != m_allSlots.end() && slotItr->second == slot;
  387. }
  388. const Node::SlotDefinitionList& Node::GetSlotDefinitions() const
  389. {
  390. return m_allSlotDefinitions;
  391. }
  392. const Node::SlotMap& Node::GetSlots()
  393. {
  394. return m_allSlots;
  395. }
  396. Node::ConstSlotMap Node::GetSlots() const
  397. {
  398. return ConstSlotMap(m_allSlots.begin(), m_allSlots.end());
  399. }
  400. ConstSlotPtr Node::GetSlot(const SlotId& slotId) const
  401. {
  402. const auto slotItr = m_allSlots.find(slotId);
  403. return slotItr != m_allSlots.end() ? slotItr->second : nullptr;
  404. }
  405. SlotPtr Node::GetSlot(const SlotId& slotId)
  406. {
  407. const auto slotItr = m_allSlots.find(slotId);
  408. return slotItr != m_allSlots.end() ? slotItr->second : nullptr;
  409. }
  410. SlotPtr Node::GetSlot(const SlotName& name)
  411. {
  412. return GetSlot(SlotId(name));
  413. }
  414. ConstSlotPtr Node::GetSlot(const SlotName& name) const
  415. {
  416. return GetSlot(SlotId(name));
  417. }
  418. const Node::ExtendableSlotSet& Node::GetExtendableSlots(const SlotName& name)
  419. {
  420. static Node::ExtendableSlotSet defaultSet;
  421. const auto slotItr = m_extendableSlots.find(name);
  422. return slotItr != m_extendableSlots.end() ? slotItr->second : defaultSet;
  423. }
  424. int Node::GetExtendableSlotCount(const SlotName& name) const
  425. {
  426. const auto slotItr = m_extendableSlots.find(name);
  427. return slotItr != m_extendableSlots.end() ? aznumeric_cast<int>(slotItr->second.size()) : InvalidExtendableSlot;
  428. }
  429. void Node::DeleteSlot(SlotPtr slot)
  430. {
  431. if (CanDeleteSlot(slot))
  432. {
  433. // Remove this slot from our map tracking all slots on the node, as well as the extendable slots
  434. m_allSlots.erase(slot->GetSlotId());
  435. auto extendableSlotsIt = m_extendableSlots.find(slot->GetName());
  436. if (extendableSlotsIt != m_extendableSlots.end())
  437. {
  438. extendableSlotsIt->second.erase(slot);
  439. }
  440. ClearCachedData();
  441. }
  442. }
  443. bool Node::CanDeleteSlot(ConstSlotPtr slot) const
  444. {
  445. // Only extendable slots can be removed
  446. if (slot->SupportsExtendability())
  447. {
  448. // Only allow this slot to be deleted if there are more than the required minimum
  449. const int currentNumSlots = GetExtendableSlotCount(slot->GetName());
  450. return currentNumSlots >= 0 && currentNumSlots > slot->GetMinimumSlots();
  451. }
  452. return false;
  453. }
  454. bool Node::CanExtendSlot(SlotDefinitionPtr slotDefinition) const
  455. {
  456. if (slotDefinition->SupportsExtendability())
  457. {
  458. // Only allow this slot to extended if we haven't reached the maximum
  459. const int currentNumSlots = GetExtendableSlotCount(slotDefinition->GetName());
  460. return currentNumSlots >= 0 && currentNumSlots < slotDefinition->GetMaximumSlots();
  461. }
  462. return false;
  463. }
  464. SlotPtr Node::AddExtendedSlot(const SlotName& slotName)
  465. {
  466. SlotDefinitionPtr slotDefinition;
  467. for (auto definition : m_extendableSlotDefinitions)
  468. {
  469. if (definition->GetName() == slotName)
  470. {
  471. slotDefinition = definition;
  472. break;
  473. }
  474. }
  475. if (!slotDefinition)
  476. {
  477. AZ_Assert(false, "No slot definitions with registered slotName");
  478. return nullptr;
  479. }
  480. if (!CanExtendSlot(slotDefinition))
  481. {
  482. return nullptr;
  483. }
  484. // Find the existing slots (if any) for this definition, so that we can set the subId
  485. // for the newly created slot
  486. auto extendableIt = m_extendableSlots.find(slotName);
  487. AZ_Assert(extendableIt != m_extendableSlots.end(), "Extendable slot definition name should always exist in the mapping.");
  488. int newSubId = 0;
  489. ExtendableSlotSet& currentSlots = extendableIt->second;
  490. if (!currentSlots.empty())
  491. {
  492. newSubId = (*currentSlots.rbegin())->GetSlotId().m_subId + 1;
  493. }
  494. SlotPtr slot = AZStd::make_shared<Slot>(GetGraph(), slotDefinition, newSubId);
  495. slot->SetValue(slotDefinition->GetDefaultValue());
  496. m_allSlots.insert(AZStd::make_pair(slot->GetSlotId(), slot));
  497. currentSlots.insert(slot);
  498. ClearCachedData();
  499. return slot;
  500. }
  501. void Node::RegisterSlot(SlotDefinitionPtr slotDefinition, SlotDefinitionList& slotDefinitionList)
  502. {
  503. AssertPointerIsNew(slotDefinition, m_allSlotDefinitions);
  504. AssertPointerIsNew(slotDefinition, m_propertySlotDefinitions);
  505. AssertPointerIsNew(slotDefinition, m_inputDataSlotDefinitions);
  506. AssertPointerIsNew(slotDefinition, m_outputDataSlotDefinitions);
  507. AssertPointerIsNew(slotDefinition, m_extendableSlotDefinitions);
  508. AssertNameIsNew(slotDefinition, m_allSlotDefinitions);
  509. AssertNameIsNew(slotDefinition, m_propertySlotDefinitions);
  510. AssertNameIsNew(slotDefinition, m_inputDataSlotDefinitions);
  511. AssertNameIsNew(slotDefinition, m_outputDataSlotDefinitions);
  512. AssertNameIsNew(slotDefinition, m_extendableSlotDefinitions);
  513. // Only check the target list because we could allow the same display name for an input and an output.
  514. // Only check if DisplayName is not empty, because Name will be used for display in that case.
  515. if (!slotDefinition->GetDisplayName().empty())
  516. {
  517. AssertDisplayNameIsNew(slotDefinition, slotDefinitionList);
  518. }
  519. slotDefinitionList.push_back(slotDefinition);
  520. m_allSlotDefinitions.push_back(slotDefinition);
  521. }
  522. void Node::RegisterSlot(SlotDefinitionPtr slotDefinition)
  523. {
  524. if (slotDefinition->SupportsExtendability())
  525. {
  526. RegisterSlot(slotDefinition, m_extendableSlotDefinitions);
  527. return;
  528. }
  529. if (slotDefinition->Is(SlotDirection::Input, SlotType::Data))
  530. {
  531. RegisterSlot(slotDefinition, m_inputDataSlotDefinitions);
  532. return;
  533. }
  534. if (slotDefinition->Is(SlotDirection::Output, SlotType::Data))
  535. {
  536. RegisterSlot(slotDefinition, m_outputDataSlotDefinitions);
  537. return;
  538. }
  539. if (slotDefinition->Is(SlotDirection::Input, SlotType::Property))
  540. {
  541. RegisterSlot(slotDefinition, m_propertySlotDefinitions);
  542. return;
  543. }
  544. if (slotDefinition->Is(SlotDirection::Input, SlotType::Event))
  545. {
  546. RegisterSlot(slotDefinition, m_inputEventSlotDefinitions);
  547. return;
  548. }
  549. if (slotDefinition->Is(SlotDirection::Output, SlotType::Event))
  550. {
  551. RegisterSlot(slotDefinition, m_outputEventSlotDefinitions);
  552. return;
  553. }
  554. AZ_Assert(false, "Unsupported slot configuration");
  555. }
  556. void Node::AssertPointerIsNew([[maybe_unused]] SlotDefinitionPtr newSlotDefinition, [[maybe_unused]] const SlotDefinitionList& existingSlotDefinitions) const
  557. {
  558. #if defined(AZ_ENABLE_TRACING)
  559. auto slotItr = AZStd::find(existingSlotDefinitions.begin(), existingSlotDefinitions.end(), newSlotDefinition);
  560. AZ_Assert(slotItr == existingSlotDefinitions.end(), "This slot has already been registered");
  561. #endif
  562. }
  563. void Node::AssertNameIsNew([[maybe_unused]] SlotDefinitionPtr newSlotDefinition, [[maybe_unused]] const SlotDefinitionList& existingSlotDefinitions) const
  564. {
  565. #if defined(AZ_ENABLE_TRACING)
  566. auto slotItr = AZStd::find_if(existingSlotDefinitions.begin(), existingSlotDefinitions.end(),
  567. [newSlotDefinition](SlotDefinitionPtr existingSlotDefinition) { return newSlotDefinition->GetName() == existingSlotDefinition->GetName(); });
  568. AZ_Assert(slotItr == existingSlotDefinitions.end(), "Another slot with name [%s] already exists", newSlotDefinition->GetName().c_str());
  569. #endif
  570. }
  571. void Node::AssertDisplayNameIsNew([[maybe_unused]] SlotDefinitionPtr newSlotDefinition, [[maybe_unused]] const SlotDefinitionList& existingSlotDefinitions) const
  572. {
  573. #if defined(AZ_ENABLE_TRACING)
  574. auto slotItr = AZStd::find_if(existingSlotDefinitions.begin(), existingSlotDefinitions.end(),
  575. [newSlotDefinition](SlotDefinitionPtr existingSlotDefinition) { return newSlotDefinition->GetDisplayName() == existingSlotDefinition->GetDisplayName(); });
  576. AZ_Assert(slotItr == existingSlotDefinitions.end(), "Another slot with display name [%s] already exists", newSlotDefinition->GetDisplayName().c_str());
  577. #endif
  578. }
  579. } // namespace GraphModel