Files
UnrealEngine/Engine/Plugins/Experimental/StateGraph/Tests/StateGraphTests/Private/StateGraphTestsUnit.cpp
2025-05-18 13:04:45 +08:00

525 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Misc/ConfigCacheIni.h"
#include "Misc/CoreDelegates.h"
#include "StateGraph.h"
#include "TestHarness.h"
namespace UE::StateGraphTests::Unit
{
class StateGraphDisableWarningsLog
{
public:
StateGraphDisableWarningsLog()
: OldVerbosity(LogStateGraph.GetVerbosity())
{
LogStateGraph.SetVerbosity(ELogVerbosity::Error);
}
~StateGraphDisableWarningsLog()
{
if (OldVerbosity != LogStateGraph.GetVerbosity())
{
LogStateGraph.SetVerbosity(OldVerbosity);
}
}
ELogVerbosity::Type OldVerbosity;
};
TEST_CASE("FStateGraph Basic Tests", "[FStateGraph]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
CHECK(StateGraph->GetName() == "Test");
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::NotStarted);
CHECK(FString(StateGraph->GetStatusName()) == FString(TEXT("NotStarted")));
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Completed);
}
class FTestNode : public FStateGraphNode
{
public:
FTestNode(FName InName) :
FStateGraphNode(InName)
{}
FTestNode(FName InName, const TSet<FName>& InDependencies) :
FStateGraphNode(InName)
{
Dependencies.Append(InDependencies);
}
virtual void Start() override {}
virtual void Removed() override { bWasRemovedCalled = true; }
virtual void TimedOut() override { bWasTimedOutCalled = true; }
virtual void UpdateConfig() override
{
FStateGraphNode::UpdateConfig();
GConfig->GetInt(*GetConfigSectionName(), TEXT("CustomConfig"), CustomConfig, GEngineIni);
}
bool bWasRemovedCalled = false;
bool bWasTimedOutCalled = false;
int32 CustomConfig = 1;
};
using FTestNodeRef = TSharedRef<FTestNode, ESPMode::ThreadSafe>;
using FTestNodePtr = TSharedPtr<FTestNode, ESPMode::ThreadSafe>;
using FTestNodeWeakPtr = TWeakPtr<FTestNode, ESPMode::ThreadSafe>;
TEST_CASE("FStateGraphNode Basic Tests", "[FStateGraphNode]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
FStateGraphNodeRef TestNodeA = StateGraph->CreateNode<FTestNode>("TestNodeA");
CHECK(TestNodeA->GetName() == "TestNodeA");
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::NotStarted);
CHECK(FString(TestNodeA->GetStatusName()) == FString(TEXT("NotStarted")));
CHECK(&TestNodeA.Get() == &StateGraph->GetNodeRef("TestNodeA")->Get());
CHECK(&TestNodeA.Get() == StateGraph->GetNode<FTestNode>("TestNodeA").Get());
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Waiting);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Started);
TestNodeA->Complete();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Completed);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Completed);
}
TEST_CASE("FStateGraphNodeFunction Basic Tests", "[FStateGraphNodeFunction]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
FStateGraphNodeFunctionComplete Complete;
FStateGraphNodeFunctionRef TestNodeA = StateGraph->CreateNode("TestNodeA", [&Complete](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete InComplete) { Complete = InComplete; });
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::NotStarted);
CHECK(FString(TestNodeA->GetStatusName()) == FString(TEXT("NotStarted")));
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Waiting);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Started);
Complete();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Completed);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Completed);
}
TEST_CASE("FStateGraph ContextName", "[FStateGraphContextName]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test", TEXT("TestContextName")));
FStateGraphNodeFunctionRef TestNodeA = StateGraph->CreateNode("TestNodeA", [](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete) { Complete(); });
StateGraph->Run();
CHECK(StateGraph->GetContextName() == TEXT("TestContextName"));
CHECK(StateGraph->GetLogName() == TEXT("Test(TestContextName)"));
CHECK(TestNodeA->GetLogName() == TEXT("Test(TestContextName).TestNodeA"));
}
class FClassNode : public FStateGraphNode
{
public:
FClassNode(FName InName) : FStateGraphNode(InName) {}
virtual void Start() { Complete(); }
};
void StaticNode(FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete) { Complete(); }
class FRawClass
{
public:
void RawNode(FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete) { Complete(); }
};
class FSPClass : public TSharedFromThis<FSPClass>
{
public:
void SPNode(FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete) { Complete(); }
};
TEST_CASE("CreateNode for each supported type", "[CreateNodeFunctions]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
FRawClass RawClass;
TSharedRef<FSPClass> SPClass(MakeShared<FSPClass>());
StateGraph->CreateNode<FClassNode>("TestClass")
->Next("TestLambda", [](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete) { Complete(); })
->Next("TestStatic", &StaticNode)
->Next("TestRaw", &RawClass, &FRawClass::RawNode)
->Next("TestSP", &SPClass.Get(), &FSPClass::SPNode)
->Next("TestSPLambda", &SPClass.Get(), [](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete) { Complete(); });
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Completed);
}
TEST_CASE("Node dependencies", "[NodeDependencies]")
{
StateGraphDisableWarningsLog ScopeDisable;
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
FStateGraphNodeRef TestNodeA = StateGraph->CreateNode<FTestNode>("TestNodeA");
FStateGraphNodeRef TestNodeB = StateGraph->CreateNode<FTestNode>("TestNodeB", TSet<FName>({"TestNodeA"}));
FStateGraphNodeRef TestNodeC = StateGraph->CreateNode<FTestNode>("TestNodeC", TSet<FName>({"TestNodeB", "TestNodeD"}));
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Waiting);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Started);
CHECK(TestNodeB->GetStatus() == FStateGraphNode::EStatus::Blocked);
CHECK(TestNodeC->GetStatus() == FStateGraphNode::EStatus::Blocked);
TestNodeA->Complete();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Waiting);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Completed);
CHECK(TestNodeB->GetStatus() == FStateGraphNode::EStatus::Started);
CHECK(TestNodeC->GetStatus() == FStateGraphNode::EStatus::Blocked);
TestNodeB->Complete();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Blocked);
CHECK(TestNodeB->GetStatus() == FStateGraphNode::EStatus::Completed);
CHECK(TestNodeC->GetStatus() == FStateGraphNode::EStatus::Blocked);
FStateGraphNodeRef TestNodeD = StateGraph->CreateNode<FTestNode>("TestNodeD");
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Waiting);
CHECK(TestNodeC->GetStatus() == FStateGraphNode::EStatus::Blocked);
CHECK(TestNodeD->GetStatus() == FStateGraphNode::EStatus::Started);
TestNodeD->Complete();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Waiting);
CHECK(TestNodeC->GetStatus() == FStateGraphNode::EStatus::Started);
CHECK(TestNodeD->GetStatus() == FStateGraphNode::EStatus::Completed);
TestNodeC->Complete();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Completed);
CHECK(TestNodeC->GetStatus() == FStateGraphNode::EStatus::Completed);
}
TEST_CASE("Blocked graph", "[BlockedGraph]")
{
StateGraphDisableWarningsLog ScopeDisable;
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
FStateGraphNodeRef TestNodeA = StateGraph->CreateNode<FTestNode>("TestNodeA", TSet<FName>({ "TestNodeB" }));
FStateGraphNodeRef TestNodeB = StateGraph->CreateNode<FTestNode>("TestNodeB", TSet<FName>({ "TestNodeA" }));
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Blocked);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Blocked);
CHECK(TestNodeB->GetStatus() == FStateGraphNode::EStatus::Blocked);
}
TEST_CASE("Adding and reusing nodes", "[AddingNodes]")
{
FStateGraphRef StateGraphA(MakeShared<FStateGraph>("TestB"));
FStateGraphNodeRef TestNodeA = MakeShared<FTestNode>("TestNodeA");
CHECK(StateGraphA->AddNode(TestNodeA));
StateGraphA->Run();
CHECK(StateGraphA->GetStatus() == FStateGraph::EStatus::Waiting);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Started);
StateGraphA->RemoveNode(TestNodeA->GetName());
StateGraphA->Run();
CHECK(StateGraphA->GetStatus() == FStateGraph::EStatus::Completed);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Started);
FStateGraphRef StateGraphB(MakeShared<FStateGraph>("TestB"));
CHECK(StateGraphB->AddNode(TestNodeA));
StateGraphB->Run();
CHECK(StateGraphB->GetStatus() == FStateGraph::EStatus::Waiting);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Started);
TestNodeA->Complete();
CHECK(StateGraphB->GetStatus() == FStateGraph::EStatus::Completed);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Completed);
{
StateGraphDisableWarningsLog ScopeDisable;
StateGraphA->Reset();
CHECK(StateGraphA->GetStatus() == FStateGraph::EStatus::NotStarted);
CHECK(!StateGraphA->AddNode(TestNodeA));
}
StateGraphB->RemoveNode(TestNodeA->GetName());
CHECK(StateGraphA->AddNode(TestNodeA));
StateGraphA->Run();
CHECK(StateGraphA->GetStatus() == FStateGraph::EStatus::Completed);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Completed);
{
StateGraphDisableWarningsLog ScopeDisable;
FStateGraphNodeRef TestNodeA2 = MakeShared<FTestNode>("TestNodeA");
CHECK(!StateGraphA->AddNode(TestNodeA2));
}
}
TEST_CASE("Removing nodes", "[RemoveNodes]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
FTestNodePtr TestNodeA = StateGraph->CreateNode<FTestNode>("TestNodeA");
FTestNodeWeakPtr WeakTestNodeA = TestNodeA;
StateGraph->RemoveNode("TestNodeA");
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::NotStarted);
CHECK(TestNodeA->bWasRemovedCalled);
TestNodeA.Reset();
CHECK(!WeakTestNodeA.IsValid());
WeakTestNodeA = StateGraph->CreateNode<FTestNode>("TestNodeA");
StateGraph->RemoveNode("TestNodeA");
CHECK(!WeakTestNodeA.IsValid());
WeakTestNodeA = StateGraph->CreateNode<FTestNode>("TestNodeA");
FStateGraphNodePtr TestNodeB = StateGraph->CreateNode("TestNodeB", [](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete)
{
StateGraph.RemoveNode("TestNodeA");
StateGraph.RemoveNode("TestNodeC");
});
FStateGraphNodeWeakPtr WeakTestNodeC = StateGraph->CreateNode<FTestNode>("TestNodeC");
StateGraph->Run();
CHECK(!WeakTestNodeA.IsValid());
CHECK(!WeakTestNodeC.IsValid());
StateGraph->RemoveNode("TestNodeB");
StateGraph->CreateNode<FTestNode>("TestNodeA")
->Next<FTestNode>("TestNodeB")
->Next<FTestNode>("TestNodeC");
StateGraph->RemoveAllNodes();
CHECK(StateGraph->GetNodeRef("TestNodeA") == nullptr);
CHECK(StateGraph->GetNodeRef("TestNodeB") == nullptr);
CHECK(StateGraph->GetNodeRef("TestNodeC") == nullptr);
}
TEST_CASE("Resetting nodes", "[ResetNodes]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
int32 StartCount = 0;
FStateGraphNodeRef TestNodeA = StateGraph->CreateNode("TestNodeA", [&StartCount, &TestNodeA](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete)
{
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Started);
if (StartCount == 0)
{
TestNodeA->Reset();
}
else
{
Complete();
}
++StartCount;
});
StateGraph->Run();
CHECK(StartCount == 1);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::NotStarted);
StateGraph->Run();
CHECK(StartCount == 2);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Completed);
TestNodeA->Reset();
StateGraph->Run();
CHECK(StartCount == 3);
}
TEST_CASE("Resetting state graph", "[ResetStateGraph]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
int32 StartCount = 0;
FStateGraphNodeRef TestNodeA = StateGraph->CreateNode("TestNodeA", [&StartCount](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete)
{
if (StartCount == 0)
{
StateGraph.Reset();
}
else
{
Complete();
}
++StartCount;
});
StateGraph->Run();
CHECK(StartCount == 1);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::NotStarted);
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::NotStarted);
StateGraph->Run();
CHECK(StartCount == 2);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Completed);
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Completed);
}
TEST_CASE("Deleting state graph", "[DeleteStateGraph]")
{
FStateGraphPtr StateGraph(MakeShared<FStateGraph>("Test"));
FStateGraphWeakPtr WeakStateGraph(StateGraph);
FStateGraphNodeRef TestNodeA = StateGraph->CreateNode("TestNodeA", [&StateGraph, &WeakStateGraph](FStateGraph& InStateGraph, FStateGraphNodeFunctionComplete Complete)
{
StateGraph.Reset();
CHECK(!WeakStateGraph.IsValid());
});
StateGraph->Run();
CHECK(!WeakStateGraph.IsValid());
}
TEST_CASE("Pausing state graph", "[PauseStateGraph]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
FStateGraphNodeRef TestNodeA = StateGraph->CreateNode("TestNodeA", [](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete) { StateGraph.Pause(); });
FStateGraphNodeRef TestNodeB = StateGraph->CreateNode<FTestNode>("TestNodeB", TSet<FName>({"TestNodeA"}));
FStateGraphNodeRef TestNodeC = StateGraph->CreateNode("TestNodeC", [](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete) { Complete(); });
TestNodeC->Dependencies.Add("TestNodeB");
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Paused);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Started);
TestNodeA->Complete();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Paused);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Completed);
CHECK(TestNodeB->GetStatus() != FStateGraphNode::EStatus::Started);
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Waiting);
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::Completed);
CHECK(TestNodeB->GetStatus() == FStateGraphNode::EStatus::Started);
CHECK(TestNodeC->GetStatus() == FStateGraphNode::EStatus::Blocked);
StateGraph->Pause();
TestNodeB->Complete();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Paused);
CHECK(TestNodeB->GetStatus() == FStateGraphNode::EStatus::Completed);
CHECK(TestNodeC->GetStatus() == FStateGraphNode::EStatus::Blocked);
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Completed);
CHECK(TestNodeC->GetStatus() == FStateGraphNode::EStatus::Completed);
}
TEST_CASE("State graph timeout", "[StateGraphTimeout]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
StateGraph->SetTimeout(0.01);
bool bOnTimedOutCalled = false;
StateGraph->OnStatusChanged.AddLambda([&bOnTimedOutCalled](FStateGraph&, FStateGraph::EStatus OldStatus, FStateGraph::EStatus NewStatus)
{
if (NewStatus == FStateGraph::EStatus::TimedOut)
{
bOnTimedOutCalled = true;
}
});
FStateGraphNodeRef TestNodeA = StateGraph->CreateNode("TestNodeA", [](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete) {});
StateGraph->Run();
while (StateGraph->GetStatus() == FStateGraph::EStatus::Waiting)
{
FTSTicker::GetCoreTicker().Tick(0.1f);
}
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::TimedOut);
CHECK(bOnTimedOutCalled);
}
TEST_CASE("Node timeout", "[NodeTimeout]")
{
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
bool bOnNodeTimedOutCalled = false;
StateGraph->OnNodeStatusChanged.AddLambda([&bOnNodeTimedOutCalled](FStateGraphNode&, FStateGraphNode::EStatus OldStatus, FStateGraphNode::EStatus NewStatus)
{
if (NewStatus == FStateGraphNode::EStatus::TimedOut)
{
bOnNodeTimedOutCalled = true;
}
});
FTestNodeRef TestNodeA = StateGraph->CreateNode<FTestNode>("TestNodeA");
TestNodeA->SetTimeout(0.01);
StateGraph->Run();
while (StateGraph->GetStatus() == FStateGraph::EStatus::Waiting)
{
StateGraphDisableWarningsLog ScopeDisable;
FTSTicker::GetCoreTicker().Tick(0.1f);
}
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::TimedOut);
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Blocked);
CHECK(TestNodeA->bWasTimedOutCalled);
CHECK(bOnNodeTimedOutCalled);
TestNodeA->Complete();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Completed);
}
TEST_CASE("State graph config", "[StateGraphConfig]")
{
StateGraphDisableWarningsLog ScopeDisable;
LogConfig.SetVerbosity(ELogVerbosity::Error);
FConfigCacheIni::InitializeConfigSystem();
FConfigFile* EngineIni = GConfig->FindConfigFile(GEngineIni);
check(EngineIni);
EngineIni->CombineFromBuffer(TEXT("[StateGraph.Test]\nTimeout=0.01\n"), GEngineIni);
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
StateGraph->Initialize();
FStateGraphNodeRef TestNodeA = StateGraph->CreateNode("TestNodeA", [](FStateGraph& StateGraph, FStateGraphNodeFunctionComplete Complete) {});
StateGraph->Run();
while (StateGraph->GetStatus() == FStateGraph::EStatus::Waiting)
{
FTSTicker::GetCoreTicker().Tick(0.1f);
}
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::TimedOut);
TestNodeA->Complete();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::TimedOut);
EngineIni->CombineFromBuffer(TEXT("[StateGraph.Test]\nTimeout=0\n"), GEngineIni);
FCoreDelegates::TSOnConfigSectionsChanged().Broadcast(GEngineIni, TSet<FString>({ TEXT("StateGraph.Test") }));
StateGraph->Run();
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Completed);
}
TEST_CASE("Node config", "[NodeConfig]")
{
StateGraphDisableWarningsLog ScopeDisable;
LogConfig.SetVerbosity(ELogVerbosity::Error);
FConfigCacheIni::InitializeConfigSystem();
FConfigFile* EngineIni = GConfig->FindConfigFile(GEngineIni);
check(EngineIni);
EngineIni->CombineFromBuffer(TEXT("[StateGraph.Test.TestNodeA]\nTimeout=0.01\nCustomConfig=2\n"), GEngineIni);
FStateGraphRef StateGraph(MakeShared<FStateGraph>("Test"));
StateGraph->Initialize();
FTestNodeRef TestNodeA = StateGraph->CreateNode<FTestNode>("TestNodeA");
StateGraph->Run();
while (StateGraph->GetStatus() == FStateGraph::EStatus::Waiting)
{
FTSTicker::GetCoreTicker().Tick(0.1f);
}
CHECK(TestNodeA->GetStatus() == FStateGraphNode::EStatus::TimedOut);
CHECK(StateGraph->GetStatus() == FStateGraph::EStatus::Blocked);
CHECK(TestNodeA->bWasTimedOutCalled);
CHECK(TestNodeA->CustomConfig == 2);
EngineIni->CombineFromBuffer(TEXT("[StateGraph.Test.TestNodeA]\nTimeout=0.01\nCustomConfig=3\n"), GEngineIni);
FCoreDelegates::TSOnConfigSectionsChanged().Broadcast(GEngineIni, TSet<FString>({ TEXT("StateGraph.Test.TestNodeA") }));
CHECK(TestNodeA->CustomConfig == 3);
}
} // UE::StateGraphTests