You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphModelTests.cpp

496 lines
25 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <QApplication>
#include <QtTest>
#include <QAbstractItemModelTester>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/RTTI/AttributeReader.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <EMotionFX/CommandSystem/Source/AnimGraphCommands.h>
#include <EMotionFX/CommandSystem/Source/AnimGraphNodeCommands.h>
#include <EMotionFX/CommandSystem/Source/CommandManager.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/AnimGraphInstance.h>
#include <EMotionFX/Source/AnimGraphManager.h>
#include <EMotionFX/Source/AnimGraphMotionNode.h>
#include <EMotionFX/Source/AnimGraphReferenceNode.h>
#include <EMotionFX/Source/AnimGraphStateMachine.h>
#include <EMotionFX/Source/AutoRegisteredActor.h>
#include <EMotionFX/Source/EMotionFXManager.h>
#include <EMotionFX/Source/Parameter/BoolParameter.h>
#include <EMotionFX/Source/Parameter/FloatSliderParameter.h>
#include <EMotionFX/Source/Parameter/Vector2Parameter.h>
#include <EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
#include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/AnimGraphModel.h>
#include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/AnimGraphPlugin.h>
#include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterWindow.h>
#include <Source/Integration/Assets/AnimGraphAsset.h>
#include <Tests/TestAssetCode/AnimGraphAssetFactory.h>
#include <Tests/TestAssetCode/AnimGraphFactory.h>
#include <Tests/TestAssetCode/SimpleActors.h>
#include <Tests/ProvidesUI/AnimGraph/SimpleAnimGraphUIFixture.h>
#include <Tests/TestAssetCode/ActorFactory.h>
#include <Tests/Mocks/EventHandler.h>
namespace EMotionFX
{
TEST_F(SimpleAnimGraphUIFixture, ResetAnimGraph)
{
// This test checks that we can reset a graph without any problem.
EMStudio::AnimGraphModel& animGraphModel = m_animGraphPlugin->GetAnimGraphModel();
AZStd::string commandResult;
MCore::CommandGroup group;
CommandSystem::ClearAnimGraphsCommand(&group);
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommandGroup(group, commandResult)) << commandResult.c_str();
EXPECT_EQ(GetAnimGraphManager().GetNumAnimGraphs(), 0);
EXPECT_EQ(animGraphModel.GetFocusedAnimGraph(), nullptr);
m_animGraph = nullptr;
}
TEST_F(SimpleAnimGraphUIFixture, FocusRemainValidAfterDeleteFocus)
{
// This test checks that a focused item can be deleted, and afterward the focus will get set correctly.
EMStudio::AnimGraphModel& animGraphModel = m_animGraphPlugin->GetAnimGraphModel();
AnimGraphNode* motionNode = m_animGraph->RecursiveFindNodeByName("testMotion");
EXPECT_TRUE(motionNode);
AnimGraphNode* blendTreeNode = m_animGraph->RecursiveFindNodeByName("testBlendTree");
EXPECT_TRUE(blendTreeNode);
// Focus on the motion node.
const QModelIndex motionNodeModelIndex = animGraphModel.FindFirstModelIndex(motionNode);
animGraphModel.Focus(motionNodeModelIndex);
EXPECT_EQ(motionNodeModelIndex, animGraphModel.GetFocus());
// Delete the motion Node.
AZStd::string removeCommand = AZStd::string::format("AnimGraphRemoveNode -animGraphID %d -name testMotion", m_animGraphId);
AZStd::string commandResult;
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(removeCommand, commandResult)) << commandResult.c_str();
// The focus should change.
const QModelIndex focusIndex = animGraphModel.GetFocus();
EXPECT_TRUE(focusIndex.isValid()) << "AnimGraphModel should have a valid index after removing the focus node.";
EXPECT_EQ(focusIndex, animGraphModel.FindFirstModelIndex(m_animGraph->GetRootStateMachine())) << "the root statemachine node should become the new focus";
}
TEST_F(SimpleAnimGraphUIFixture, ParametersWindowFocusChange)
{
// This test checks that parameters window behave expected after model changes.
EMStudio::AnimGraphModel& animGraphModel = m_animGraphPlugin->GetAnimGraphModel();
AnimGraphNode* motionNode = m_animGraph->RecursiveFindNodeByName("testMotion");
EXPECT_TRUE(motionNode);
AnimGraphNode* blendTreeNode = m_animGraph->RecursiveFindNodeByName("testBlendTree");
EXPECT_TRUE(blendTreeNode);
// Focus on the motion node.
const QModelIndex motionNodeModelIndex = animGraphModel.FindFirstModelIndex(motionNode);
animGraphModel.Focus(motionNodeModelIndex);
// Check the parameters window
const EMStudio::ParameterWindow* parameterWindow = m_animGraphPlugin->GetParameterWindow();
EXPECT_EQ(parameterWindow->GetTopLevelItemCount(), 3) << "Should be 3 parameters added in the parameters window.";
// Force the model to look at an invalid index. This should reset the parameters window.
animGraphModel.Focus(QModelIndex());
EXPECT_EQ(parameterWindow->GetTopLevelItemCount(), 0) << "Should be 0 parameters in the parameters window after reset.";
// Force the model to look back at the motion node.
animGraphModel.Focus(motionNodeModelIndex);
EXPECT_EQ(parameterWindow->GetTopLevelItemCount(), 3) << "Should be 3 parameters added in the parameters window.";
// Delete the motion node.
AZStd::string removeCommand = AZStd::string::format("AnimGraphRemoveNode -animGraphID %d -name testMotion", m_animGraphId);
AZStd::string commandResult;
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(removeCommand, commandResult)) << commandResult.c_str();
// The parameter windows shouldn't be affected
EXPECT_EQ(parameterWindow->GetTopLevelItemCount(), 3) << "Should be 3 parameters added in the parameters window.";
}
// Turns Qt messages into Google Test assertions
class AssertNoQtLogWarnings
{
static void messageHandlerTest(QtMsgType type, [[maybe_unused]] const QMessageLogContext& context, const QString& msg)
{
QByteArray localMsg = msg.toLocal8Bit();
switch (type)
{
case QtDebugMsg:
//printf("Debug: %s: %s\n", context.category, localMsg.constData());
break;
case QtInfoMsg:
//printf("Info: %s: %s\n", context.category, localMsg.constData());
break;
case QtWarningMsg:
case QtCriticalMsg:
case QtFatalMsg:
FAIL() << msg.toStdString();
break;
}
}
public:
AssertNoQtLogWarnings()
: m_oldHandler(qInstallMessageHandler(messageHandlerTest))
{
QLoggingCategory::setFilterRules(QStringLiteral("qt.modeltest=true"));
}
~AssertNoQtLogWarnings()
{
// Install default message handler
qInstallMessageHandler(m_oldHandler);
}
private:
QtMessageHandler m_oldHandler;
};
// Tests that use this fixture validate the AnimGraphModel by using the
// QAbstractItemModelTester. It will trigger Qt warnings if any action made
// by the model violates the API contract that QAbstractItemModels must
// adhere to. Those warnings are then turned into Google Test failures
// using the Qt warning redirector class above.
class AnimGraphModelFixture
: public UIFixture
{
public:
void SetUp() override
{
UIFixture::SetUp();
auto* animGraphPlugin = EMStudio::GetPluginManager()->FindActivePlugin<EMStudio::AnimGraphPlugin>();
m_model = &animGraphPlugin->GetAnimGraphModel();
m_modelTester = AZStd::make_unique<QAbstractItemModelTester>(m_model, QAbstractItemModelTester::FailureReportingMode::Warning);
m_model->connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, [this](const QModelIndex& parent, int startRow, int endRow)
{
for (int i = startRow; i <= endRow; ++i)
{
QModelIndex aboutToBeRemoved = m_model->index(i, 0, parent);
EXPECT_TRUE(aboutToBeRemoved.isValid());
EXPECT_FALSE(m_model->data(aboutToBeRemoved).isNull());
//qDebug(QLoggingCategory("qt.modeltest")) << " Data that will be removed:" << m_model->data(aboutToBeRemoved);
}
});
EmptyAnimGraph::Reflect(GetSerializeContext());
TwoMotionNodeAnimGraph::Reflect(GetSerializeContext());
OneBlendTreeNodeAnimGraph::Reflect(GetSerializeContext());
}
void CallOnAnimGraphAssetChanged(AnimGraphReferenceNode* referenceNode)
{
// AnimGraphReferenceNode::OnAnimGraphAssetChanged is registered as the ChangeNotify method when the
// reference node's m_animGraphAsset member is changed by the reflected property editor. In a test, the RPE
// is not used to change the value, so the ChangeNotify callback must be invoked directly. That method is
// also private, so a direct call is not possible. Look up the ChangeNotify handler for that class member,
// and invoke it.
const auto& referenceNodeClassElements = GetSerializeContext()->FindClassData(azrtti_typeid<AnimGraphReferenceNode>())->m_editData->m_elements;
const auto animGraphElement = AZStd::find_if(referenceNodeClassElements.begin(), referenceNodeClassElements.end(), [](const AZ::Edit::ElementData& elementData)
{
return AZ::StringFunc::Equal(elementData.m_name, "Anim graph");
});
ASSERT_NE(animGraphElement, referenceNodeClassElements.end());
animGraphElement->FindAttribute(AZ::Edit::Attributes::ChangeNotify);
bool somethingCalled = false;
for (const auto& [attributeId, attribute] : animGraphElement->m_attributes)
{
if (attributeId != AZ::Edit::Attributes::ChangeNotify)
{
continue;
}
somethingCalled |= AZ::AttributeInvoker(referenceNode, attribute).Invoke<void>();
}
EXPECT_TRUE(somethingCalled) << "No call made to OnAnimGraphAssetChanged";
}
EMStudio::AnimGraphModel* GetModel() const { return m_model; }
private:
EMStudio::AnimGraphModel* m_model;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTester;
AssertNoQtLogWarnings m_warningRedirector;
};
TEST_F(AnimGraphModelFixture, CanAddASingleNodeToTheAnimGraphModel)
{
{
AZStd::string result;
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 0", result)) << result.c_str();
}
auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(0);
CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphMotionNode>(), "Motion", animGraph->GetRootStateMachine(), 0, 0);
}
TEST_F(AnimGraphModelFixture, CanAddAndRemoveASingleNodeToTheAnimGraphModel)
{
{
AZStd::string result;
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 0", result)) << result.c_str();
}
auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(0);
CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphMotionNode>(), "Motion", animGraph->GetRootStateMachine(), 0, 0);
CommandSystem::DeleteNodes(animGraph, {"Motion0"});
}
TEST_F(AnimGraphModelFixture, CanAddAndRemoveNestedNodesToTheAnimGraphModel)
{
{
AZStd::string result;
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 0", result)) << result.c_str();
}
auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(0);
CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::BlendTree>(), "BlendTree", animGraph->GetRootStateMachine(), 0, 0);
AnimGraphNode* blendTree = animGraph->RecursiveFindNodeByName("BlendTree0");
CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphMotionNode>(), "Motion", blendTree, 0, 0);
CommandSystem::DeleteNodes(animGraph, {"BlendTree0"});
}
TEST_F(AnimGraphModelFixture, CanRemoveNodeFromInsideReferencedGraphThatAppearsInTwoPlacesInTheModel)
{
using testing::Eq;
{
AZStd::string result;
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 0", result)) << result.c_str();
}
auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(0);
CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphReferenceNode>(), "Reference", animGraph->GetRootStateMachine(), 0, 0);
auto* referenceNode = static_cast<AnimGraphReferenceNode*>(animGraph->RecursiveFindNodeByName("Reference0"));
AnimGraph* referenceAnimGraph = nullptr;
{
AZ::Data::Asset<Integration::AnimGraphAsset> referenceAnimGraphAsset =
AnimGraphAssetFactory::Create(AZ::Data::AssetId("{EC53A3C1-DDAF-46AA-B091-041449FC7FEE}"), AnimGraphFactory::Create<OneBlendTreeParameterNodeAnimGraph>());
referenceAnimGraph = referenceAnimGraphAsset->GetAnimGraph();
referenceAnimGraph->SetFileName("ReferencedAnimGraph.animgraph");
referenceAnimGraph->InitAfterLoading();
referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Queued);
referenceNode->SetAnimGraphAsset(referenceAnimGraphAsset);
CallOnAnimGraphAssetChanged(referenceNode);
referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Ready);
// Let the AnimGraphModel know that the anim graph asset has been loaded
AZ::Data::AssetBus::Broadcast(&AZ::Data::AssetBus::Events::OnAssetReady, referenceAnimGraphAsset);
}
{
auto* parameterNode = static_cast<BlendTreeParameterNode*>(referenceAnimGraph->GetRootStateMachine()->GetChildNode(0)->GetChildNode(0));
EXPECT_TRUE(parameterNode);
const QModelIndexList modelIndexes = GetModel()->FindModelIndexes(parameterNode);
QList<QPersistentModelIndex> modelIndexesForParameterNode;
AZStd::copy(modelIndexes.begin(), modelIndexes.end(), AZStd::back_inserter(modelIndexesForParameterNode));
EXPECT_THAT(modelIndexesForParameterNode.size(), Eq(2));
const auto modelIndexIsValid = testing::Truly([](const QPersistentModelIndex& i) { return i.isValid(); });
const auto eachModelIndexIsValid = testing::Each(modelIndexIsValid);
const auto eachModelIndexIsInvalid = testing::Each(testing::Not(modelIndexIsValid));
EXPECT_THAT(modelIndexesForParameterNode, eachModelIndexIsValid);
CommandSystem::DeleteNodes(referenceAnimGraph, {parameterNode->GetNameString()});
EXPECT_THAT(modelIndexesForParameterNode, eachModelIndexIsInvalid);
}
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
// This test simulates an asset reload. It ensures that the model stays
// stable while the new reference graph is loaded.
// To reload an asset, a separate asset is created with its own AssetData
// pointer, but the same AssetId.
// Normally, the only holder of an Asset reference is the ReferenceNode
// itself. The Asset variables are scoped so that the ReferenceNode is the
// only holder of the Asset. When the asset is reloaded, the ReferenceNode
// assigns over its old Asset. Since it was the last holder, the asset is
// released, and the underlying AnimGraph is destroyed.
TEST_F(AnimGraphModelFixture, CanReloadAReferenceNodesReferencedGraph)
{
using testing::Eq;
{
AZStd::string result;
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 0", result)) << result.c_str();
}
auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(0);
CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphReferenceNode>(), "Reference", animGraph->GetRootStateMachine(), 0, 0);
auto* referenceNode = static_cast<AnimGraphReferenceNode*>(animGraph->RecursiveFindNodeByName("Reference0"));
const AZ::Data::AssetId assetId("{B359FEA1-7628-4981-91E2-63F58413EEF5}");
AnimGraph* referenceAnimGraph = nullptr;
{
AZ::Data::Asset<Integration::AnimGraphAsset> referenceAnimGraphAsset =
AnimGraphAssetFactory::Create(assetId, AnimGraphFactory::Create<OneBlendTreeParameterNodeAnimGraph>());
referenceAnimGraph = referenceAnimGraphAsset->GetAnimGraph();
referenceAnimGraph->SetFileName("ReferencedAnimGraph.animgraph");
referenceAnimGraph->InitAfterLoading();
referenceAnimGraph->SetIsOwnedByRuntime(true);
referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Queued);
referenceNode->SetAnimGraphAsset(referenceAnimGraphAsset);
CallOnAnimGraphAssetChanged(referenceNode);
referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Ready);
// In normal operation, asset loading results in this sequence of events:
//
// AnimGraphAssetHandler::OnInitAsset
// sets owned by runtime = true
// AnimGraphModel::OnAssetReady
// not added to top-level because is owned by runtime = true
// AnimGraphReferenceNode::OnAssetReady
// sets owned by runtime = false
// emits OnReferenceAnimGraphChanged
// AnimGraphModel::OnReferenceAnimGraphChanged
// adds nodes of the graph to the right places in the model
// Let the AnimGraphModel know that the anim graph asset has been loaded
AZ::Data::AssetBus::Broadcast(&AZ::Data::AssetBus::Events::OnAssetReady, referenceAnimGraphAsset);
}
auto* parameterNode = static_cast<BlendTreeParameterNode*>(referenceAnimGraph->GetRootStateMachine()->GetChildNode(0)->GetChildNode(0));
EXPECT_TRUE(parameterNode);
QModelIndexList modelIndexesForParameterNode = GetModel()->FindModelIndexes(parameterNode);
EXPECT_THAT(modelIndexesForParameterNode.size(), Eq(1));
QPersistentModelIndex index = modelIndexesForParameterNode[0];
EXPECT_TRUE(index.isValid());
{
auto* handler = static_cast<Integration::AnimGraphAssetHandler*>(AZ::Data::AssetManager::Instance().GetHandler(azrtti_typeid<Integration::AnimGraphAsset>()));
AZ::Data::Asset<Integration::AnimGraphAsset> newAsset{handler->CreateAsset(assetId, AZ::AzTypeInfo<Integration::AnimGraphAsset>::Uuid()), AZ::Data::AssetLoadBehavior::Default};
newAsset->SetData(AnimGraphFactory::Create<OneBlendTreeParameterNodeAnimGraph>().release());
newAsset->GetAnimGraph()->SetFileName("ReferencedAnimGraph.animgraph");
newAsset->GetAnimGraph()->InitAfterLoading();
newAsset->GetAnimGraph()->SetIsOwnedByRuntime(true);
newAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Ready);
// In normal operation, asset reloading results in this sequence of events:
//
// AnimGraphAssetHandler::OnInitAsset
// sets owned by runtime = true
// AnimGraphModel::OnAssetReloaded
// not added to top-level because is owned by runtime = true
// AnimGraphReferenceNode::OnAssetReloaded
// sets owned by runtime = false
// emits OnReferenceAnimGraphAboutToBeChanged
// AnimGraphModel::OnReferenceAnimGraphAboutToBeChanged
// removes child nodes of the existing reference node
// releases reference to old asset, potentially deleting the old anim graph
// emits OnReferenceAnimGraphChanged
// AnimGraphModel::OnReferenceAnimGraphChanged
// adds nodes of the graph to the right places in the model
AZ::Data::AssetBus::Broadcast(&AZ::Data::AssetBus::Events::OnAssetReloaded, newAsset);
}
EXPECT_FALSE(index.isValid());
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
TEST_F(AnimGraphModelFixture, CanReloadAnActivatedReferenceNodesReferencedGraph)
{
using testing::Eq;
using testing::Not;
AutoRegisteredActor actor = EMotionFX::ActorFactory::CreateAndInit<EMotionFX::SimpleJointChainActor>(1);
auto motionSet = AZStd::make_unique<EMotionFX::MotionSet>();
{
AZStd::string result;
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("CreateAnimGraph -animGraphId 0", result)) << result.c_str();
}
auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(0);
auto* actorInstance = EMotionFX::ActorInstance::Create(actor.get());
auto* animGraphInstance = EMotionFX::AnimGraphInstance::Create(animGraph, actorInstance, motionSet.get());
actorInstance->SetAnimGraphInstance(animGraphInstance);
GetEMotionFX().Update(0.0f);
CommandSystem::CreateAnimGraphNode(nullptr, animGraph, azrtti_typeid<EMotionFX::AnimGraphReferenceNode>(), "Reference", animGraph->GetRootStateMachine(), 0, 0);
auto* referenceNode = static_cast<AnimGraphReferenceNode*>(animGraph->RecursiveFindNodeByName("Reference0"));
const AZ::Data::AssetId assetId("{B359FEA1-7628-4981-91E2-63F58413EEF5}");
AnimGraph* referenceAnimGraph = nullptr;
{
// Using blocks here ensure that we don't keep an extra reference to the Asset
AZ::Data::Asset<Integration::AnimGraphAsset> referenceAnimGraphAsset =
AnimGraphAssetFactory::Create(assetId, AnimGraphFactory::Create<OneBlendTreeParameterNodeAnimGraph>());
referenceAnimGraph = referenceAnimGraphAsset->GetAnimGraph();
referenceAnimGraph->SetFileName("ReferencedAnimGraph.animgraph");
referenceAnimGraph->SetIsOwnedByRuntime(true);
referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Queued);
referenceNode->SetAnimGraphAsset(referenceAnimGraphAsset);
CallOnAnimGraphAssetChanged(referenceNode);
referenceAnimGraphAsset->SetStatus(AZ::Data::AssetData::AssetStatus::Ready);
AZ::Data::AssetBus::Broadcast(&AZ::Data::AssetBus::Events::OnAssetReady, referenceAnimGraphAsset);
}
const auto* refNodeUniqueData = static_cast<AnimGraphReferenceNode::UniqueData*>(referenceNode->FindOrCreateUniqueNodeData(animGraphInstance));
ASSERT_TRUE(refNodeUniqueData);
const auto* referenceAnimGraphInstance = refNodeUniqueData->m_referencedAnimGraphInstance;
EXPECT_TRUE(referenceAnimGraphInstance);
{
auto* handler = static_cast<Integration::AnimGraphAssetHandler*>(AZ::Data::AssetManager::Instance().GetHandler(azrtti_typeid<Integration::AnimGraphAsset>()));
AZ::Data::Asset<Integration::AnimGraphAsset> newAsset{handler->CreateAsset(assetId, AZ::AzTypeInfo<Integration::AnimGraphAsset>::Uuid()), AZ::Data::AssetLoadBehavior::Default};
newAsset->SetData(AnimGraphFactory::Create<OneBlendTreeParameterNodeAnimGraph>().release());
newAsset->GetAnimGraph()->SetFileName("ReferencedAnimGraph.animgraph");
newAsset->GetAnimGraph()->SetIsOwnedByRuntime(true);
EMotionFX::MockEventHandler eventHandler;
EXPECT_CALL(eventHandler, GetHandledEventTypes())
.WillRepeatedly(testing::Return(AZStd::vector<EventTypes> { EVENT_TYPE_ON_CREATE_ANIM_GRAPH_INSTANCE, EVENT_TYPE_ON_DELETE_ANIM_GRAPH_INSTANCE, }));
{
testing::InSequence deleteThenCreateCalledInSequence;
EXPECT_CALL(eventHandler, OnDeleteAnimGraphInstance(refNodeUniqueData->m_referencedAnimGraphInstance))
.Times(1);
EXPECT_CALL(eventHandler, OnCreateAnimGraphInstance(testing::_))
.Times(1);
}
GetEventManager().AddEventHandler(&eventHandler);
AZ::Data::AssetBus::Broadcast(&AZ::Data::AssetBus::Events::OnAssetReloaded, newAsset);
GetEventManager().RemoveEventHandler(&eventHandler);
}
GetEMotionFX().Update(0.1f);
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
} // namespace EMotionFX