AnimGraphModelTests.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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 <gtest/gtest.h>
  9. #include <gmock/gmock.h>
  10. #include <QApplication>
  11. #include <QtTest>
  12. #include <QAbstractItemModelTester>
  13. #include <AzCore/Asset/AssetCommon.h>
  14. #include <AzCore/RTTI/AttributeReader.h>
  15. #include <AzCore/StringFunc/StringFunc.h>
  16. #include <AzCore/std/smart_ptr/unique_ptr.h>
  17. #include <EMotionFX/CommandSystem/Source/AnimGraphCommands.h>
  18. #include <EMotionFX/CommandSystem/Source/AnimGraphNodeCommands.h>
  19. #include <EMotionFX/CommandSystem/Source/CommandManager.h>
  20. #include <EMotionFX/Source/ActorInstance.h>
  21. #include <EMotionFX/Source/AnimGraphInstance.h>
  22. #include <EMotionFX/Source/AnimGraphManager.h>
  23. #include <EMotionFX/Source/AnimGraphMotionNode.h>
  24. #include <EMotionFX/Source/AnimGraphReferenceNode.h>
  25. #include <EMotionFX/Source/AnimGraphStateMachine.h>
  26. #include <EMotionFX/Source/EMotionFXManager.h>
  27. #include <EMotionFX/Source/Parameter/BoolParameter.h>
  28. #include <EMotionFX/Source/Parameter/FloatSliderParameter.h>
  29. #include <EMotionFX/Source/Parameter/Vector2Parameter.h>
  30. #include <EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
  31. #include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/AnimGraphModel.h>
  32. #include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/AnimGraphPlugin.h>
  33. #include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterWindow.h>
  34. #include <Source/Integration/Assets/AnimGraphAsset.h>
  35. #include <Tests/TestAssetCode/AnimGraphAssetFactory.h>
  36. #include <Tests/TestAssetCode/AnimGraphFactory.h>
  37. #include <Tests/TestAssetCode/SimpleActors.h>
  38. #include <Tests/ProvidesUI/AnimGraph/SimpleAnimGraphUIFixture.h>
  39. #include <Tests/TestAssetCode/ActorFactory.h>
  40. #include <Tests/TestAssetCode/TestActorAssets.h>
  41. #include <Tests/Mocks/EventHandler.h>
  42. namespace EMotionFX
  43. {
  44. TEST_F(SimpleAnimGraphUIFixture, ResetAnimGraph)
  45. {
  46. // This test checks that we can reset a graph without any problem.
  47. EMStudio::AnimGraphModel& animGraphModel = m_animGraphPlugin->GetAnimGraphModel();
  48. AZStd::string commandResult;
  49. MCore::CommandGroup group;
  50. CommandSystem::ClearAnimGraphsCommand(&group);
  51. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommandGroup(group, commandResult)) << commandResult.c_str();
  52. EXPECT_EQ(GetAnimGraphManager().GetNumAnimGraphs(), 0);
  53. EXPECT_EQ(animGraphModel.GetFocusedAnimGraph(), nullptr);
  54. m_animGraph = nullptr;
  55. }
  56. TEST_F(SimpleAnimGraphUIFixture, FocusRemainValidAfterDeleteFocus)
  57. {
  58. // This test checks that a focused item can be deleted, and afterward the focus will get set correctly.
  59. EMStudio::AnimGraphModel& animGraphModel = m_animGraphPlugin->GetAnimGraphModel();
  60. AnimGraphNode* motionNode = m_animGraph->RecursiveFindNodeByName("testMotion");
  61. EXPECT_TRUE(motionNode);
  62. AnimGraphNode* blendTreeNode = m_animGraph->RecursiveFindNodeByName("testBlendTree");
  63. EXPECT_TRUE(blendTreeNode);
  64. // Focus on the motion node.
  65. const QModelIndex motionNodeModelIndex = animGraphModel.FindFirstModelIndex(motionNode);
  66. animGraphModel.Focus(motionNodeModelIndex);
  67. EXPECT_EQ(motionNodeModelIndex, animGraphModel.GetFocus());
  68. // Delete the motion Node.
  69. AZStd::string removeCommand = AZStd::string::format("AnimGraphRemoveNode -animGraphID %d -name testMotion", m_animGraphId);
  70. AZStd::string commandResult;
  71. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(removeCommand, commandResult)) << commandResult.c_str();
  72. // The focus should change.
  73. const QModelIndex focusIndex = animGraphModel.GetFocus();
  74. EXPECT_TRUE(focusIndex.isValid()) << "AnimGraphModel should have a valid index after removing the focus node.";
  75. EXPECT_EQ(focusIndex, animGraphModel.FindFirstModelIndex(m_animGraph->GetRootStateMachine())) << "the root statemachine node should become the new focus";
  76. }
  77. TEST_F(SimpleAnimGraphUIFixture, ParametersWindowFocusChange)
  78. {
  79. // This test checks that parameters window behave expected after model changes.
  80. EMStudio::AnimGraphModel& animGraphModel = m_animGraphPlugin->GetAnimGraphModel();
  81. AnimGraphNode* motionNode = m_animGraph->RecursiveFindNodeByName("testMotion");
  82. EXPECT_TRUE(motionNode);
  83. AnimGraphNode* blendTreeNode = m_animGraph->RecursiveFindNodeByName("testBlendTree");
  84. EXPECT_TRUE(blendTreeNode);
  85. // Focus on the motion node.
  86. const QModelIndex motionNodeModelIndex = animGraphModel.FindFirstModelIndex(motionNode);
  87. animGraphModel.Focus(motionNodeModelIndex);
  88. // Check the parameters window
  89. const EMStudio::ParameterWindow* parameterWindow = m_animGraphPlugin->GetParameterWindow();
  90. EXPECT_EQ(parameterWindow->GetTopLevelItemCount(), 3) << "Should be 3 parameters added in the parameters window.";
  91. // Force the model to look at an invalid index. This should reset the parameters window.
  92. animGraphModel.Focus(QModelIndex());
  93. EXPECT_EQ(parameterWindow->GetTopLevelItemCount(), 0) << "Should be 0 parameters in the parameters window after reset.";
  94. // Force the model to look back at the motion node.
  95. animGraphModel.Focus(motionNodeModelIndex);
  96. EXPECT_EQ(parameterWindow->GetTopLevelItemCount(), 3) << "Should be 3 parameters added in the parameters window.";
  97. // Delete the motion node.
  98. AZStd::string removeCommand = AZStd::string::format("AnimGraphRemoveNode -animGraphID %d -name testMotion", m_animGraphId);
  99. AZStd::string commandResult;
  100. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(removeCommand, commandResult)) << commandResult.c_str();
  101. // The parameter windows shouldn't be affected
  102. EXPECT_EQ(parameterWindow->GetTopLevelItemCount(), 3) << "Should be 3 parameters added in the parameters window.";
  103. }
  104. // Turns Qt messages into Google Test assertions
  105. class AssertNoQtLogWarnings
  106. {
  107. static void messageHandlerTest(QtMsgType type, [[maybe_unused]] const QMessageLogContext& context, const QString& msg)
  108. {
  109. QByteArray localMsg = msg.toLocal8Bit();
  110. switch (type)
  111. {
  112. case QtDebugMsg:
  113. //printf("Debug: %s: %s\n", context.category, localMsg.constData());
  114. break;
  115. case QtInfoMsg:
  116. //printf("Info: %s: %s\n", context.category, localMsg.constData());
  117. break;
  118. case QtWarningMsg:
  119. case QtCriticalMsg:
  120. case QtFatalMsg:
  121. if (msg.contains("has active key-value observers (KVO)! These will stop working now that the window is recreated, and will result in exceptions when the observers are removed"))
  122. {
  123. break;
  124. }
  125. FAIL() << msg.toStdString();
  126. break;
  127. }
  128. }
  129. public:
  130. AssertNoQtLogWarnings()
  131. : m_oldHandler(qInstallMessageHandler(messageHandlerTest))
  132. {
  133. QLoggingCategory::setFilterRules(QStringLiteral("qt.modeltest=true"));
  134. }
  135. ~AssertNoQtLogWarnings()
  136. {
  137. // Install default message handler
  138. qInstallMessageHandler(m_oldHandler);
  139. }
  140. private:
  141. QtMessageHandler m_oldHandler;
  142. };
  143. // Tests that use this fixture validate the AnimGraphModel by using the
  144. // QAbstractItemModelTester. It will trigger Qt warnings if any action made
  145. // by the model violates the API contract that QAbstractItemModels must
  146. // adhere to. Those warnings are then turned into Google Test failures
  147. // using the Qt warning redirector class above.
  148. class AnimGraphModelFixture
  149. : public UIFixture
  150. {
  151. public:
  152. void SetUp() override
  153. {
  154. UIFixture::SetUp();
  155. auto* animGraphPlugin = EMStudio::GetPluginManager()->FindActivePlugin<EMStudio::AnimGraphPlugin>();
  156. m_model = &animGraphPlugin->GetAnimGraphModel();
  157. m_modelTester = AZStd::make_unique<QAbstractItemModelTester>(m_model, QAbstractItemModelTester::FailureReportingMode::Warning);
  158. m_model->connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, [this](const QModelIndex& parent, int startRow, int endRow)
  159. {
  160. for (int i = startRow; i <= endRow; ++i)
  161. {
  162. QModelIndex aboutToBeRemoved = m_model->index(i, 0, parent);
  163. EXPECT_TRUE(aboutToBeRemoved.isValid());
  164. EXPECT_FALSE(m_model->data(aboutToBeRemoved).isNull());
  165. //qDebug(QLoggingCategory("qt.modeltest")) << " Data that will be removed:" << m_model->data(aboutToBeRemoved);
  166. }
  167. });
  168. EmptyAnimGraph::Reflect(GetSerializeContext());
  169. TwoMotionNodeAnimGraph::Reflect(GetSerializeContext());
  170. OneBlendTreeNodeAnimGraph::Reflect(GetSerializeContext());
  171. }
  172. void CallOnAnimGraphAssetChanged(AnimGraphReferenceNode* referenceNode)
  173. {
  174. // AnimGraphReferenceNode::OnAnimGraphAssetChanged is registered as the ChangeNotify method when the
  175. // reference node's m_animGraphAsset member is changed by the reflected property editor. In a test, the RPE
  176. // is not used to change the value, so the ChangeNotify callback must be invoked directly. That method is
  177. // also private, so a direct call is not possible. Look up the ChangeNotify handler for that class member,
  178. // and invoke it.
  179. const auto& referenceNodeClassElements = GetSerializeContext()->FindClassData(azrtti_typeid<AnimGraphReferenceNode>())->m_editData->m_elements;
  180. const auto animGraphElement = AZStd::find_if(referenceNodeClassElements.begin(), referenceNodeClassElements.end(), [](const AZ::Edit::ElementData& elementData)
  181. {
  182. return AZ::StringFunc::Equal(elementData.m_name, "Anim graph");
  183. });
  184. ASSERT_NE(animGraphElement, referenceNodeClassElements.end());
  185. animGraphElement->FindAttribute(AZ::Edit::Attributes::ChangeNotify);
  186. bool somethingCalled = false;
  187. for (const auto& [attributeId, attribute] : animGraphElement->m_attributes)
  188. {
  189. if (attributeId != AZ::Edit::Attributes::ChangeNotify)
  190. {
  191. continue;
  192. }
  193. somethingCalled |= AZ::AttributeInvoker(referenceNode, attribute).Invoke<void>();
  194. }
  195. EXPECT_TRUE(somethingCalled) << "No call made to OnAnimGraphAssetChanged";
  196. }
  197. EMStudio::AnimGraphModel* GetModel() const { return m_model; }
  198. private:
  199. EMStudio::AnimGraphModel* m_model;
  200. AZStd::unique_ptr<QAbstractItemModelTester> m_modelTester;
  201. AssertNoQtLogWarnings m_warningRedirector;
  202. };
  203. TEST_F(AnimGraphModelFixture, CanAddASingleNodeToTheAnimGraphModel)
  204. {
  205. {
  206. AZStd::string result;
  207. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 100", result)) << result.c_str();
  208. }
  209. auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(100);
  210. CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphMotionNode>(), "Motion", animGraph->GetRootStateMachine(), 0, 0);
  211. }
  212. TEST_F(AnimGraphModelFixture, CanAddAndRemoveASingleNodeToTheAnimGraphModel)
  213. {
  214. {
  215. AZStd::string result;
  216. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 100", result)) << result.c_str();
  217. }
  218. auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(100);
  219. CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphMotionNode>(), "Motion", animGraph->GetRootStateMachine(), 0, 0);
  220. CommandSystem::DeleteNodes(animGraph, {"Motion0"});
  221. }
  222. TEST_F(AnimGraphModelFixture, CanAddAndRemoveNestedNodesToTheAnimGraphModel)
  223. {
  224. {
  225. AZStd::string result;
  226. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 100", result)) << result.c_str();
  227. }
  228. auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(100);
  229. CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::BlendTree>(), "BlendTree", animGraph->GetRootStateMachine(), 0, 0);
  230. AnimGraphNode* blendTree = animGraph->RecursiveFindNodeByName("BlendTree0");
  231. CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphMotionNode>(), "Motion", blendTree, 0, 0);
  232. CommandSystem::DeleteNodes(animGraph, {"BlendTree0"});
  233. }
  234. TEST_F(AnimGraphModelFixture, CanRemoveNodeFromInsideReferencedGraphThatAppearsInTwoPlacesInTheModel)
  235. {
  236. using testing::Eq;
  237. {
  238. AZStd::string result;
  239. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 100", result)) << result.c_str();
  240. }
  241. auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(100);
  242. CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphReferenceNode>(), "Reference", animGraph->GetRootStateMachine(), 0, 0);
  243. auto* referenceNode = static_cast<AnimGraphReferenceNode*>(animGraph->RecursiveFindNodeByName("Reference0"));
  244. AnimGraph* referenceAnimGraph = nullptr;
  245. {
  246. AZ::Data::Asset<Integration::AnimGraphAsset> referenceAnimGraphAsset =
  247. AnimGraphAssetFactory::Create(AZ::Data::AssetId("{EC53A3C1-DDAF-46AA-B091-041449FC7FEE}"), AnimGraphFactory::Create<OneBlendTreeParameterNodeAnimGraph>());
  248. referenceAnimGraph = referenceAnimGraphAsset->GetAnimGraph();
  249. referenceAnimGraph->SetFileName("ReferencedAnimGraph.animgraph");
  250. referenceAnimGraph->InitAfterLoading();
  251. referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Queued);
  252. referenceNode->SetAnimGraphAsset(referenceAnimGraphAsset);
  253. CallOnAnimGraphAssetChanged(referenceNode);
  254. referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Ready);
  255. // Let the AnimGraphModel know that the anim graph asset has been loaded
  256. AZ::Data::AssetBus::Broadcast(&AZ::Data::AssetBus::Events::OnAssetReady, referenceAnimGraphAsset);
  257. }
  258. {
  259. auto* parameterNode = static_cast<BlendTreeParameterNode*>(referenceAnimGraph->GetRootStateMachine()->GetChildNode(0)->GetChildNode(0));
  260. EXPECT_TRUE(parameterNode);
  261. const QModelIndexList modelIndexes = GetModel()->FindModelIndexes(parameterNode);
  262. QList<QPersistentModelIndex> modelIndexesForParameterNode;
  263. AZStd::copy(modelIndexes.begin(), modelIndexes.end(), AZStd::back_inserter(modelIndexesForParameterNode));
  264. EXPECT_THAT(modelIndexesForParameterNode.size(), Eq(2));
  265. const auto modelIndexIsValid = testing::Truly([](const QPersistentModelIndex& i) { return i.isValid(); });
  266. const auto eachModelIndexIsValid = testing::Each(modelIndexIsValid);
  267. const auto eachModelIndexIsInvalid = testing::Each(testing::Not(modelIndexIsValid));
  268. EXPECT_THAT(modelIndexesForParameterNode, eachModelIndexIsValid);
  269. CommandSystem::DeleteNodes(referenceAnimGraph, {parameterNode->GetNameString()});
  270. EXPECT_THAT(modelIndexesForParameterNode, eachModelIndexIsInvalid);
  271. }
  272. QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
  273. }
  274. // This test simulates an asset reload. It ensures that the model stays
  275. // stable while the new reference graph is loaded.
  276. // To reload an asset, a separate asset is created with its own AssetData
  277. // pointer, but the same AssetId.
  278. // Normally, the only holder of an Asset reference is the ReferenceNode
  279. // itself. The Asset variables are scoped so that the ReferenceNode is the
  280. // only holder of the Asset. When the asset is reloaded, the ReferenceNode
  281. // assigns over its old Asset. Since it was the last holder, the asset is
  282. // released, and the underlying AnimGraph is destroyed.
  283. TEST_F(AnimGraphModelFixture, CanReloadAReferenceNodesReferencedGraph)
  284. {
  285. using testing::Eq;
  286. {
  287. AZStd::string result;
  288. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 100", result)) << result.c_str();
  289. }
  290. auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(100);
  291. CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphReferenceNode>(), "Reference", animGraph->GetRootStateMachine(), 0, 0);
  292. auto* referenceNode = static_cast<AnimGraphReferenceNode*>(animGraph->RecursiveFindNodeByName("Reference0"));
  293. const AZ::Data::AssetId assetId("{B359FEA1-7628-4981-91E2-63F58413EEF5}");
  294. AnimGraph* referenceAnimGraph = nullptr;
  295. {
  296. AZ::Data::Asset<Integration::AnimGraphAsset> referenceAnimGraphAsset =
  297. AnimGraphAssetFactory::Create(assetId, AnimGraphFactory::Create<OneBlendTreeParameterNodeAnimGraph>());
  298. referenceAnimGraph = referenceAnimGraphAsset->GetAnimGraph();
  299. referenceAnimGraph->SetFileName("ReferencedAnimGraph.animgraph");
  300. referenceAnimGraph->InitAfterLoading();
  301. referenceAnimGraph->SetIsOwnedByRuntime(true);
  302. referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Queued);
  303. referenceNode->SetAnimGraphAsset(referenceAnimGraphAsset);
  304. CallOnAnimGraphAssetChanged(referenceNode);
  305. referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Ready);
  306. // In normal operation, asset loading results in this sequence of events:
  307. //
  308. // AnimGraphAssetHandler::OnInitAsset
  309. // sets owned by runtime = true
  310. // AnimGraphModel::OnAssetReady
  311. // not added to top-level because is owned by runtime = true
  312. // AnimGraphReferenceNode::OnAssetReady
  313. // sets owned by runtime = false
  314. // emits OnReferenceAnimGraphChanged
  315. // AnimGraphModel::OnReferenceAnimGraphChanged
  316. // adds nodes of the graph to the right places in the model
  317. // Let the AnimGraphModel know that the anim graph asset has been loaded
  318. AZ::Data::AssetBus::Broadcast(&AZ::Data::AssetBus::Events::OnAssetReady, referenceAnimGraphAsset);
  319. }
  320. auto* parameterNode = static_cast<BlendTreeParameterNode*>(referenceAnimGraph->GetRootStateMachine()->GetChildNode(0)->GetChildNode(0));
  321. EXPECT_TRUE(parameterNode);
  322. QModelIndexList modelIndexesForParameterNode = GetModel()->FindModelIndexes(parameterNode);
  323. EXPECT_THAT(modelIndexesForParameterNode.size(), Eq(1));
  324. QPersistentModelIndex index = modelIndexesForParameterNode[0];
  325. EXPECT_TRUE(index.isValid());
  326. {
  327. auto* handler = static_cast<Integration::AnimGraphAssetHandler*>(AZ::Data::AssetManager::Instance().GetHandler(azrtti_typeid<Integration::AnimGraphAsset>()));
  328. AZ::Data::Asset<Integration::AnimGraphAsset> newAsset{handler->CreateAsset(assetId, AZ::AzTypeInfo<Integration::AnimGraphAsset>::Uuid()), AZ::Data::AssetLoadBehavior::Default};
  329. newAsset->SetData(AnimGraphFactory::Create<OneBlendTreeParameterNodeAnimGraph>().release());
  330. newAsset->GetAnimGraph()->SetFileName("ReferencedAnimGraph.animgraph");
  331. newAsset->GetAnimGraph()->InitAfterLoading();
  332. newAsset->GetAnimGraph()->SetIsOwnedByRuntime(true);
  333. newAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Ready);
  334. // In normal operation, asset reloading results in this sequence of events:
  335. //
  336. // AnimGraphAssetHandler::OnInitAsset
  337. // sets owned by runtime = true
  338. // AnimGraphModel::OnAssetReloaded
  339. // not added to top-level because is owned by runtime = true
  340. // AnimGraphReferenceNode::OnAssetReloaded
  341. // sets owned by runtime = false
  342. // emits OnReferenceAnimGraphAboutToBeChanged
  343. // AnimGraphModel::OnReferenceAnimGraphAboutToBeChanged
  344. // removes child nodes of the existing reference node
  345. // releases reference to old asset, potentially deleting the old anim graph
  346. // emits OnReferenceAnimGraphChanged
  347. // AnimGraphModel::OnReferenceAnimGraphChanged
  348. // adds nodes of the graph to the right places in the model
  349. AZ::Data::AssetBus::Broadcast(&AZ::Data::AssetBus::Events::OnAssetReloaded, newAsset);
  350. }
  351. EXPECT_FALSE(index.isValid());
  352. QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
  353. }
  354. TEST_F(AnimGraphModelFixture, CanReloadAnActivatedReferenceNodesReferencedGraph)
  355. {
  356. using testing::Eq;
  357. using testing::Not;
  358. AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}");
  359. AZ::Data::Asset<Integration::ActorAsset> actorAsset =
  360. TestActorAssets::CreateActorAssetAndRegister<SimpleJointChainActor>(actorAssetId, 1);
  361. auto motionSet = AZStd::make_unique<EMotionFX::MotionSet>();
  362. {
  363. AZStd::string result;
  364. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 100", result)) << result.c_str();
  365. }
  366. auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(100);
  367. auto* actorInstance = EMotionFX::ActorInstance::Create(actorAsset->GetActor());
  368. auto* animGraphInstance = EMotionFX::AnimGraphInstance::Create(animGraph, actorInstance, motionSet.get());
  369. actorInstance->SetAnimGraphInstance(animGraphInstance);
  370. GetEMotionFX().Update(0.0f);
  371. CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphReferenceNode>(), "Reference", animGraph->GetRootStateMachine(), 0, 0);
  372. auto* referenceNode = static_cast<AnimGraphReferenceNode*>(animGraph->RecursiveFindNodeByName("Reference0"));
  373. const AZ::Data::AssetId assetId("{B359FEA1-7628-4981-91E2-63F58413EEF5}");
  374. AnimGraph* referenceAnimGraph = nullptr;
  375. {
  376. // Using blocks here ensure that we don't keep an extra reference to the Asset
  377. AZ::Data::Asset<Integration::AnimGraphAsset> referenceAnimGraphAsset =
  378. AnimGraphAssetFactory::Create(assetId, AnimGraphFactory::Create<OneBlendTreeParameterNodeAnimGraph>());
  379. referenceAnimGraph = referenceAnimGraphAsset->GetAnimGraph();
  380. referenceAnimGraph->SetFileName("ReferencedAnimGraph.animgraph");
  381. referenceAnimGraph->SetIsOwnedByRuntime(true);
  382. referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Queued);
  383. referenceNode->SetAnimGraphAsset(referenceAnimGraphAsset);
  384. CallOnAnimGraphAssetChanged(referenceNode);
  385. referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Ready);
  386. AZ::Data::AssetBus::Broadcast(&AZ::Data::AssetBus::Events::OnAssetReady, referenceAnimGraphAsset);
  387. }
  388. const auto* refNodeUniqueData = static_cast<AnimGraphReferenceNode::UniqueData*>(referenceNode->FindOrCreateUniqueNodeData(animGraphInstance));
  389. ASSERT_TRUE(refNodeUniqueData);
  390. const auto* referenceAnimGraphInstance = refNodeUniqueData->m_referencedAnimGraphInstance;
  391. EXPECT_TRUE(referenceAnimGraphInstance);
  392. {
  393. auto* handler = static_cast<Integration::AnimGraphAssetHandler*>(AZ::Data::AssetManager::Instance().GetHandler(azrtti_typeid<Integration::AnimGraphAsset>()));
  394. AZ::Data::Asset<Integration::AnimGraphAsset> newAsset{handler->CreateAsset(assetId, AZ::AzTypeInfo<Integration::AnimGraphAsset>::Uuid()), AZ::Data::AssetLoadBehavior::Default};
  395. newAsset->SetData(AnimGraphFactory::Create<OneBlendTreeParameterNodeAnimGraph>().release());
  396. newAsset->GetAnimGraph()->SetFileName("ReferencedAnimGraph.animgraph");
  397. newAsset->GetAnimGraph()->SetIsOwnedByRuntime(true);
  398. EMotionFX::MockEventHandler eventHandler;
  399. EXPECT_CALL(eventHandler, GetHandledEventTypes())
  400. .WillRepeatedly(testing::Return(AZStd::vector<EventTypes> { EVENT_TYPE_ON_CREATE_ANIM_GRAPH_INSTANCE, EVENT_TYPE_ON_DELETE_ANIM_GRAPH_INSTANCE, }));
  401. {
  402. testing::InSequence deleteThenCreateCalledInSequence;
  403. EXPECT_CALL(eventHandler, OnDeleteAnimGraphInstance(refNodeUniqueData->m_referencedAnimGraphInstance))
  404. .Times(1);
  405. EXPECT_CALL(eventHandler, OnCreateAnimGraphInstance(testing::_))
  406. .Times(1);
  407. }
  408. GetEventManager().AddEventHandler(&eventHandler);
  409. AZ::Data::AssetBus::Broadcast(&AZ::Data::AssetBus::Events::OnAssetReloaded, newAsset);
  410. GetEventManager().RemoveEventHandler(&eventHandler);
  411. }
  412. GetEMotionFX().Update(0.1f);
  413. QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
  414. }
  415. } // namespace EMotionFX