Files
o3de/Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp
T
Chris Burel 8884227fe6 Remove MCore::Array
This translates all usages of MCore::Array to AZStd::vector. It is
designed to be as minimal of a change as possible (no changing to
range-for loops or other C++11 stuff).

We can decide to submit this wholesale, or submit it to a separate
branch that we can then integrate individual files from once we're ready
to do a specific class's transition.

It does not completely solve the `uint32`->`size_t` transition.

One important finding from doing this: `MCore::Array` uses a `memcpy`
when it reallocates. `AZStd::vector` will use the contained type's copy
or move constructor, per element. This is a significant change in
behavior. If you have type, `SomeStruct` that defines a destructor, that
type is copyable and not movable. So if you have a
`MCore::Array<SomeStruct>`, and you call `Add(); Add(); Add()`, that
reallocates 3 times, copying the contents using `memcpy`, and never
invokes `SomeStruct`'s copy constructor or destructor. Translating that
to `AZStd::vector<SomeStruct>` and calling `push_back(); push_back();
push_back();` will still reallocate 3 times, but it sees that
`SomeStruct` is non-movable, and uses the copy constructor to make the
copies, and then the destructor on the previous values. This call to the
destructor wasn't there before, and can cause things to be deleted that
weren't before. The solution to this is to make that struct be a
move-only type. Where possible, this was done by changing that type to
use `AZStd::unique_ptr` instead of a raw pointer, to get the proper move
behavior. Where that is not possible (types that inherit from
`MCore::MemoryObject`), a hand-written move constructor was created.

In general:
GetLength() becomes size()
GetMaxLength() becomes capacity()
GetIsEmpty() becomes empty()
Reserve() becomes reserve()
ReserveExact() becomes reserve()
Resize() becomes resize()
ResizeFast() becomes resize_no_construct()
Add() becomes emplace_back()
AddExact() becomes emplace_back()
AddEmpty() becomes emplace_back()
AddEmptyExact() becomes emplace_back()
GetPtr() becomes data()
GetItem() becomes at()
Shrink() becomes shrink_to_fit()
GetFirst() becomes front()
GetLast() becomes back()
Remove() becomes erase()
RemoveFirst() becomes erase()
RemoveLast() becomes pop_back()
RemoveByValue() becomes if (const auto it = AZStd::find(...); it != end(container)) container.erase(it);
Insert() becomes emplace()
Swap() becomes swap()
Clear(true) becomes clear(); shrink_to_fit()
Clear() becomes clear(); shrink_to_fit()
Clear(false) becomes clear()
Swap() becomes swap()
Find() becomes AZStd::find
MoveElements() becomes AZStd::move
SetMemoryCategory() is removed

Signed-off-by: Chris Burel <burelc@amazon.com>
2021-08-09 08:36:43 -07:00

241 lines
11 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 "AnimGraphFixture.h"
#include <EMotionFX/Source/AnimGraph.h>
#include <EMotionFX/Source/AnimGraphMotionNode.h>
#include <EMotionFX/Source/AnimGraphStateMachine.h>
#include <EMotionFX/Source/BlendTree.h>
#include <EMotionFX/Source/BlendTreeBlendNNode.h>
#include <EMotionFX/Source/BlendTreeBoolLogicNode.h>
#include <EMotionFX/Source/BlendTreeParameterNode.h>
#include <EMotionFX/Source/EMotionFXManager.h>
#include <EMotionFX/Source/Motion.h>
#include <EMotionFX/Source/MotionInstance.h>
#include <EMotionFX/Source/MotionSet.h>
#include <EMotionFX/Source/Parameter/BoolParameter.h>
#include <EMotionFX/Source/Parameter/ParameterFactory.h>
#include <EMotionFX/Source/MotionData/NonUniformMotionData.h>
#include <AzCore/std/containers/vector.h>
namespace EMotionFX
{
class BoolLogicNodeTests : public AnimGraphFixture
{
public:
void TearDown() override
{
if (m_motionNodes)
{
delete m_motionNodes;
}
AnimGraphFixture::TearDown();
}
void ConstructGraph() override
{
AnimGraphFixture::ConstructGraph();
m_blendTreeAnimGraph = AnimGraphFactory::Create<OneBlendTreeNodeAnimGraph>();
m_rootStateMachine = m_blendTreeAnimGraph->GetRootStateMachine();
m_blendTree = m_blendTreeAnimGraph->GetBlendTreeNode();
m_blendNNode = aznew BlendTreeBlendNNode();
BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode();
m_blendTree->AddChildNode(m_blendNNode);
m_blendTree->AddChildNode(finalNode);
finalNode->AddConnection(m_blendNNode, BlendTreeBlendNNode::PORTID_OUTPUT_POSE, BlendTreeFinalNode::PORTID_INPUT_POSE);
const int motionNodeCount = 2;
for (int i = 0; i < motionNodeCount; ++i)
{
AnimGraphMotionNode* motionNode = aznew AnimGraphMotionNode();
m_blendTree->AddChildNode(motionNode);
m_blendNNode->AddConnection(motionNode, AnimGraphMotionNode::PORTID_OUTPUT_POSE, i);
m_motionNodes->push_back(motionNode);
}
m_blendTreeAnimGraph->InitAfterLoading();
}
void SetUp() override
{
m_motionNodes = new AZStd::vector<AnimGraphMotionNode*>();
AnimGraphFixture::SetUp();
m_animGraphInstance->Destroy();
m_animGraphInstance = m_blendTreeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet);
for (size_t i = 0; i < m_motionNodes->size(); ++i)
{
// The motion set keeps track of motions by their name. Each motion
// within the motion set must have a unique name.
AZStd::string motionId = AZStd::string::format("testSkeletalMotion%zu", i);
Motion* motion = aznew Motion(motionId.c_str());
motion->SetMotionData(aznew NonUniformMotionData());
motion->GetMotionData()->SetDuration(1.0f);
MotionSet::MotionEntry* motionEntry = aznew MotionSet::MotionEntry(motion->GetName(), motion->GetName(), motion);
m_motionSet->AddMotionEntry(motionEntry);
(*m_motionNodes)[i]->AddMotionId(motionId.c_str());
}
}
void AddValueParameter(const AZ::TypeId& typeId, const AZStd::string& name)
{
Parameter* parameter = ParameterFactory::Create(typeId);
parameter->SetName(name);
m_blendTreeAnimGraph->AddParameter(parameter);
m_animGraphInstance->AddMissingParameterValues();
}
bool CalculateExpectedResult(BlendTreeBoolLogicNode::EFunction functionEnum, bool x, bool y, bool& error)
{
bool result = false;
switch (functionEnum)
{
case BlendTreeBoolLogicNode::EFunction::FUNCTION_AND:
result = x && y;
break;
case BlendTreeBoolLogicNode::EFunction::FUNCTION_OR:
result = x || y;
break;
case BlendTreeBoolLogicNode::EFunction::FUNCTION_XOR:
result = x ^ y;
break;
case BlendTreeBoolLogicNode::EFunction::FUNCTION_NAND:
result = !(x && y);
break;
case BlendTreeBoolLogicNode::EFunction::FUNCTION_NOR:
result = !(x || y);
break;
case BlendTreeBoolLogicNode::EFunction::FUNCTION_XNOR:
result = !(x ^ y);
break;
case BlendTreeBoolLogicNode::EFunction::FUNCTION_NOT_X:
result = !x;
break;
case BlendTreeBoolLogicNode::EFunction::FUNCTION_NOT_Y:
result = !y;
break;
default:
error = true;
break;
}
return result;
}
AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
AZStd::vector<AnimGraphMotionNode*>* m_motionNodes = nullptr;
BlendTreeBlendNNode* m_blendNNode = nullptr;
BlendTree* m_blendTree = nullptr;
};
TEST_F(BoolLogicNodeTests, TestBoolLogic)
{
bool success = true;
const AZStd::string nameBoolX("parameter_bool_x_test");
const AZStd::string nameBoolY("parameter_bool_y_test");
AddValueParameter(azrtti_typeid<BoolParameter>(), nameBoolX);
AddValueParameter(azrtti_typeid<BoolParameter>(), nameBoolY);
BlendTreeParameterNode* parameterNode = aznew BlendTreeParameterNode();
m_blendTree->AddChildNode(parameterNode);
parameterNode->InitAfterLoading(m_blendTreeAnimGraph.get());
parameterNode->InvalidateUniqueData(m_animGraphInstance);
BlendTreeBoolLogicNode* boolLogicNode = aznew BlendTreeBoolLogicNode();
m_blendTree->AddChildNode(boolLogicNode);
boolLogicNode->InitAfterLoading(m_blendTreeAnimGraph.get());
boolLogicNode->InvalidateUniqueData(m_animGraphInstance);
const AZ::Outcome<size_t> boolXParamIndexOutcome = m_animGraphInstance->FindParameterIndex(nameBoolX);
const AZ::Outcome<size_t> boolYParamIndexOutcome = m_animGraphInstance->FindParameterIndex(nameBoolY);
success = boolXParamIndexOutcome.IsSuccess() && boolYParamIndexOutcome.IsSuccess();
uint32 boolXOutputPortIndex = InvalidIndex32;
uint32 boolYOutputPortIndex = InvalidIndex32;
const int portIndicesTosetCount = 2;
int portIndicesFound = 0;
const AZStd::vector<EMotionFX::AnimGraphNode::Port>& parameterNodeOutputPorts = parameterNode->GetOutputPorts();
for (const EMotionFX::AnimGraphNode::Port& port : parameterNodeOutputPorts)
{
uint32 paramIndex = parameterNode->GetParameterIndex(port.mPortID);
if (paramIndex == boolXParamIndexOutcome.GetValue())
{
boolXOutputPortIndex = port.mPortID;
portIndicesFound++;
}
else if (paramIndex == boolYParamIndexOutcome.GetValue())
{
boolYOutputPortIndex = port.mPortID;
portIndicesFound++;
}
}
success = success && (portIndicesFound == portIndicesTosetCount);
bool expectedResultSuccessful = true;
if (success)
{
boolLogicNode->AddConnection(parameterNode, static_cast<uint16>(boolXOutputPortIndex), BlendTreeBoolLogicNode::INPUTPORT_X);
boolLogicNode->AddConnection(parameterNode, static_cast<uint16>(boolYOutputPortIndex), BlendTreeBoolLogicNode::INPUTPORT_Y);
m_blendNNode->AddConnection(boolLogicNode, BlendTreeBoolLogicNode::OUTPUTPORT_BOOL, BlendTreeBlendNNode::INPUTPORT_WEIGHT);
m_blendTreeAnimGraph->RecursiveReinit();
MCore::AttributeBool* testBoolXParameter = static_cast<MCore::AttributeBool*>(m_animGraphInstance->FindParameter(nameBoolX));
testBoolXParameter->SetValue(false);
MCore::AttributeBool* testBoolYParameter = static_cast<MCore::AttributeBool*>(m_animGraphInstance->FindParameter(nameBoolY));
testBoolYParameter->SetValue(false);
Evaluate();
MCore::Attribute* attribute = m_blendNNode->GetInputAttribute(m_animGraphInstance, BlendTreeBlendNNode::INPUTPORT_WEIGHT);
if (!attribute)
{
success = false;
}
else
{
// Odds are X even are Y
const bool tableOfTruthInput[8] = { false, false, false, true, true, false, true, true };
BlendTreeBoolLogicNode::EFunction functions[8] = {
BlendTreeBoolLogicNode::EFunction::FUNCTION_AND, BlendTreeBoolLogicNode::EFunction::FUNCTION_OR,
BlendTreeBoolLogicNode::EFunction::FUNCTION_XOR, BlendTreeBoolLogicNode::EFunction::FUNCTION_NAND,
BlendTreeBoolLogicNode::EFunction::FUNCTION_NOR, BlendTreeBoolLogicNode::EFunction::FUNCTION_XNOR,
BlendTreeBoolLogicNode::EFunction::FUNCTION_NOT_X, BlendTreeBoolLogicNode::EFunction::FUNCTION_NOT_Y
};
for (int functionIndex = 0; functionIndex < 8; ++functionIndex)
{
boolLogicNode->SetFunction(functions[functionIndex]);
for (int inputIndex = 0; inputIndex < 8; inputIndex += 2)
{
testBoolXParameter->SetValue(tableOfTruthInput[inputIndex]);
testBoolYParameter->SetValue(tableOfTruthInput[inputIndex + 1]);
Evaluate();
bool error = false;
const bool expectedResult = CalculateExpectedResult(boolLogicNode->GetFunction(), testBoolXParameter->GetValue(), testBoolYParameter->GetValue(), error);
const bool result = m_blendNNode->GetInputNumberAsBool(m_animGraphInstance, BlendTreeBlendNNode::INPUTPORT_WEIGHT);
EXPECT_FALSE(error) << "Boolean logic node: CalculateExpectedResult returned error";
EXPECT_EQ(result, expectedResult) << "Boolean logic node: function " << functions[functionIndex] << " did not return the expected result";
expectedResultSuccessful = expectedResultSuccessful && !error && (result == expectedResult);
}
}
}
}
ASSERT_TRUE(success && expectedResultSuccessful);
}
} // end namespace EMotionFX