// Copyright Epic Games, Inc. All Rights Reserved. #include "StateTreeTest.h" #include "StateTreeTestBase.h" #include "StateTreeTestTypes.h" #include "StateTreeCompilerLog.h" #include "StateTreeEditorData.h" #include "StateTreeCompiler.h" #include "Conditions/StateTreeCommonConditions.h" #define LOCTEXT_NAMESPACE "AITestSuite_StateTreeTest" UE_DISABLE_OPTIMIZATION_SHIP namespace UE::StateTree::Tests { struct FStateTreeTest_Delegate_ConcurrentListeners : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName("Root")); TStateTreeEditorNode& DispatcherTask = Root.AddTask(FName("DispatcherTask")); TStateTreeEditorNode& ListenerTaskA = Root.AddTask(FName("ListenerTaskA")); TStateTreeEditorNode& ListenerTaskB = Root.AddTask(FName("ListenerTaskB")); EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate), ListenerTaskB, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_ListenDelegate_InstanceData, Listener)); EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate), ListenerTaskA, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_ListenDelegate_InstanceData, Listener)); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE("StateTree should init", bInitSucceeded); Exec.Start(); AITEST_FALSE(TEXT("StateTree ListenerTaskA should not trigger."), Exec.Expect(ListenerTaskA.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); AITEST_FALSE(TEXT("StateTree ListenerTaskB should not trigger."), Exec.Expect(ListenerTaskB.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree ListenerTaskA should be triggered once."), Exec.Expect(ListenerTaskA.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); AITEST_TRUE(TEXT("StateTree ListenerTaskB should be triggered once."), Exec.Expect(ListenerTaskB.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree ListenerTaskA should be triggered twice."), Exec.Expect(ListenerTaskA.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 2))); AITEST_TRUE(TEXT("StateTree ListenerTaskB should be triggered twice."), Exec.Expect(ListenerTaskB.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 2))); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_STATE_TREE_INSTANT_TEST(FStateTreeTest_Delegate_ConcurrentListeners, "System.StateTree.Delegate.ConcurrentListeners"); struct FStateTreeTest_Delegate_MutuallyExclusiveListeners : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& StateA = Root.AddChildState("A"); UStateTreeState& StateB = Root.AddChildState("B"); StateA.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateB); StateB.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateA); TStateTreeEditorNode& DispatcherTask = Root.AddTask(FName(TEXT("DispatcherTask"))); TStateTreeEditorNode& ListenerTaskA0 = StateA.AddTask(FName(TEXT("ListenerTaskA0"))); TStateTreeEditorNode& ListenerTaskA1 = StateA.AddTask(FName(TEXT("ListenerTaskA1"))); TStateTreeEditorNode& ListenerTaskB = StateB.AddTask(FName(TEXT("ListenerTaskB"))); EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate), ListenerTaskA0, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_ListenDelegate_InstanceData, Listener)); EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate), ListenerTaskA1, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_ListenDelegate_InstanceData, Listener)); EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate), ListenerTaskB, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_ListenDelegate_InstanceData, Listener)); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE("StateTree should init", bInitSucceeded); Exec.Start(); AITEST_TRUE(TEXT("StateTree Active States should be in Root/A"), Exec.ExpectInActiveStates(Root.Name, StateA.Name)); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree ListenerTaskA0 should be triggered once"), Exec.Expect(ListenerTaskA0.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); AITEST_TRUE(TEXT("StateTree ListenerTaskA1 should be triggered once"), Exec.Expect(ListenerTaskA1.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); AITEST_TRUE(TEXT("StateTree ListenerTaskB shouldn't be triggered."), !Exec.Expect(ListenerTaskB.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); AITEST_TRUE("StateTree Active States should be in Root/B", Exec.ExpectInActiveStates(Root.Name, StateB.Name)); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree ListenerTaskB should be triggered once"), Exec.Expect(ListenerTaskB.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); AITEST_TRUE(TEXT("StateTree ListenerTaskA0 shouldn't be triggered."), !Exec.Expect(ListenerTaskA0.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); AITEST_TRUE(TEXT("StateTree ListenerTaskA1 shouldn't be triggered."), !Exec.Expect(ListenerTaskA1.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); AITEST_TRUE(TEXT("StateTree Active States should be in Root/A"), Exec.ExpectInActiveStates(Root.Name, StateA.Name)); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_STATE_TREE_INSTANT_TEST(FStateTreeTest_Delegate_MutuallyExclusiveListeners, "System.StateTree.Delegate.MutuallyExclusiveListeners"); struct FStateTreeTest_Delegate_Transitions : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& StateA = Root.AddChildState("A"); UStateTreeState& StateB = Root.AddChildState("B"); FStateTreeTransition& TransitionAToB = StateA.AddTransition(EStateTreeTransitionTrigger::OnDelegate, EStateTreeTransitionType::GotoState, &StateB); FStateTreeTransition& TransitionBToA = StateB.AddTransition(EStateTreeTransitionTrigger::OnDelegate, EStateTreeTransitionType::GotoState, &StateA); TStateTreeEditorNode& DispatcherTask0 = Root.AddTask(FName(TEXT("DispatcherTask0"))); TStateTreeEditorNode& DispatcherTask1 = Root.AddTask(FName(TEXT("DispatcherTask1"))); EditorData.AddPropertyBinding(FPropertyBindingPath(DispatcherTask0.ID, GET_MEMBER_NAME_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate)), FPropertyBindingPath(TransitionAToB.ID, GET_MEMBER_NAME_CHECKED(FStateTreeTransition, DelegateListener))); EditorData.AddPropertyBinding(FPropertyBindingPath(DispatcherTask1.ID, GET_MEMBER_NAME_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate)), FPropertyBindingPath(TransitionBToA.ID, GET_MEMBER_NAME_CHECKED(FStateTreeTransition, DelegateListener))); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE("StateTree should init", bInitSucceeded); Exec.Start(); AITEST_TRUE(TEXT("StateTree Active States should be in Root/A"), Exec.ExpectInActiveStates(Root.Name, StateA.Name)); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Active States should be in Root/B"), Exec.ExpectInActiveStates(Root.Name, StateB.Name)); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Active States should be in Root/A"), Exec.ExpectInActiveStates(Root.Name, StateA.Name)); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_STATE_TREE_INSTANT_TEST(FStateTreeTest_Delegate_Transitions, "System.StateTree.Delegate.Transitions"); struct FStateTreeTest_Delegate_Rebroadcasting : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); TStateTreeEditorNode& DispatcherTask = Root.AddTask(FName(TEXT("DispatcherTask"))); TStateTreeEditorNode& RedispatcherTask = Root.AddTask(FName(TEXT("RedispatcherTask"))); TStateTreeEditorNode& ListenerTask = Root.AddTask(FName(TEXT("ListenerTask"))); EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate), RedispatcherTask, TEXT("Listener")); EditorData.AddPropertyBinding(RedispatcherTask, TEXT("Dispatcher"), ListenerTask, TEXT("Listener")); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); Exec.Start(); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree ListenerTask should be triggered once."), Exec.Expect(ListenerTask.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree ListenerTask should be triggered twice."), Exec.Expect(ListenerTask.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 2))); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_STATE_TREE_INSTANT_TEST(FStateTreeTest_Delegate_Rebroadcasting, "System.StateTree.Delegate.Rebroadcasting"); struct FStateTreeTest_Delegate_SelfRemoval : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); TStateTreeEditorNode& DispatcherTask = Root.AddTask(FName(TEXT("DispatcherTask"))); TStateTreeEditorNode& CustomFuncTask = Root.AddTask(FName(TEXT("CustomFuncTask"))); uint32 TriggersCounter = 0; EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate), CustomFuncTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_CustomFuncOnDelegate_InstanceData, Listener)); CustomFuncTask.GetNode().CustomFunc = [&TriggersCounter](const FStateTreeWeakExecutionContext& WeakContext, FStateTreeDelegateListener Listener) { ++TriggersCounter; WeakContext.UnbindDelegate(Listener); }; FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); Exec.Start(); Exec.LogClear(); Exec.Tick(0.1f); AITEST_EQUAL(TEXT("StateTree Delegate should be triggered once"), TriggersCounter, 1); Exec.LogClear(); Exec.Tick(0.1f); AITEST_EQUAL(TEXT("StateTree Delegate should be triggered once"), TriggersCounter, 1); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_STATE_TREE_INSTANT_TEST(FStateTreeTest_Delegate_SelfRemoval, "System.StateTree.Delegate.SelfRemoval"); struct FStateTreeTest_Delegate_WithoutRemoval : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& StateA = Root.AddChildState("A"); UStateTreeState& StateB = Root.AddChildState("B"); FStateTreeTransition& TransitionAToB = StateA.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateB); TStateTreeEditorNode& DispatcherTask = Root.AddTask(FName(TEXT("DispatcherTask"))); TStateTreeEditorNode& ListenerTask = StateA.AddTask(FName(TEXT("ListenerTask"))); EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate), ListenerTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_ListenDelegate_InstanceData, Listener)); ListenerTask.GetNode().bRemoveOnExit = false; FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); Exec.Start(); Exec.LogClear(); AITEST_TRUE(TEXT("StateTree Active States should be in Root/A"), Exec.ExpectInActiveStates(Root.Name, StateA.Name)); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Delegate should be triggered once."), Exec.Expect(ListenerTask.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); AITEST_TRUE(TEXT("StateTree Active States should be in Root/B"), Exec.ExpectInActiveStates(Root.Name, StateB.Name)); Exec.LogClear(); Exec.Tick(0.1f); AITEST_FALSE(TEXT("StateTree Delegate shouldn't be triggered again."), Exec.Expect(ListenerTask.GetName())); AITEST_TRUE(TEXT("StateTree Active States should be in Root/B"), Exec.ExpectInActiveStates(Root.Name, StateB.Name)); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_STATE_TREE_INSTANT_TEST(FStateTreeTest_Delegate_WithoutRemoval, "System.StateTree.Delegate.WithoutRemoval"); struct FStateTreeTest_Delegate_GlobalDispatcherAndListener : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); TStateTreeEditorNode& RootTask = Root.AddTask(); RootTask.GetNode().TicksToCompletion = 100; TStateTreeEditorNode& DispatcherTask = EditorData.AddGlobalTask(FName(TEXT("DispatcherTask"))); TStateTreeEditorNode& ListenerTask = EditorData.AddGlobalTask(FName(TEXT("ListenerTask"))); ListenerTask.GetNode().bRemoveOnExit = false; EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnTickDelegate), ListenerTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_CustomFuncOnDelegate_InstanceData, Listener)); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); Exec.Start(); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Delegate should be triggered once."), Exec.Expect(ListenerTask.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 1))); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Delegate should be triggered twice."), Exec.Expect(ListenerTask.GetName(), *FString::Printf(TEXT("OnDelegate%d"), 2))); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_STATE_TREE_INSTANT_TEST(FStateTreeTest_Delegate_GlobalDispatcherAndListener, "System.StateTree.Delegate.GlobalDispatcherAndListener"); struct FStateTreeTest_Delegate_ListeningToDelegateOnExit : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); TStateTreeEditorNode& DispatcherTask = Root.AddTask(FName(TEXT("DispatcherTask"))); TStateTreeEditorNode& ListenerTask = Root.AddTask(FName(TEXT("ListenerTask"))); ListenerTask.GetNode().bRemoveOnExit = false; EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnExitDelegate), ListenerTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_CustomFuncOnDelegate_InstanceData, Listener)); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); Exec.Start(); AITEST_TRUE("Expected Root to be active.", Exec.ExpectInActiveStates(Root.Name)); Exec.LogClear(); Exec.Stop(); AITEST_FALSE(TEXT("StateTree Delegate shouldn't be triggered"), Exec.Expect(ListenerTask.GetName())); Exec.LogClear(); return true; } }; IMPLEMENT_STATE_TREE_INSTANT_TEST(FStateTreeTest_Delegate_ListeningToDelegateOnExit, "System.StateTree.Delegate.ListeningToDelegateOnExit"); /** Test same state and the state index < number of instance data. */ struct FStateTreeTest_Delegate_ListeningToDelegateOnExit2 : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& StateB = Root.AddChildState("StateA") .AddChildState("StateB"); UStateTreeState& StateC= Root.AddChildState("StateC"); { EditorData.AddGlobalTask().GetNode().TicksToCompletion = 100; EditorData.AddGlobalTask().GetNode().TicksToCompletion = 100; EditorData.AddGlobalTask().GetNode().TicksToCompletion = 100; EditorData.AddGlobalTask().GetNode().TicksToCompletion = 100; EditorData.AddGlobalTask().GetNode().TicksToCompletion = 100; EditorData.AddGlobalTask().GetNode().TicksToCompletion = 100; EditorData.AddGlobalTask().GetNode().TicksToCompletion = 100; EditorData.AddGlobalTask().GetNode().TicksToCompletion = 100; EditorData.AddGlobalTask().GetNode().TicksToCompletion = 100; EditorData.AddGlobalTask().GetNode().TicksToCompletion = 100; } { TStateTreeEditorNode& DispatcherTask = StateB.AddTask(FName(TEXT("DispatcherTask"))); TStateTreeEditorNode& ListenerTask = StateB.AddTask(FName(TEXT("ListenerTask"))); ListenerTask.GetNode().bRemoveOnExit = false; EditorData.AddPropertyBinding(DispatcherTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_BroadcastDelegate_InstanceData, OnExitDelegate), ListenerTask, GET_MEMBER_NAME_STRING_CHECKED(FTestTask_CustomFuncOnDelegate_InstanceData, Listener)); FStateTreeTransition& Transition = StateB.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateC); Transition.bDelayTransition = true; Transition.DelayDuration = 0.1f; } { FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); } FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE("StateTree should init", bInitSucceeded); Exec.Start(); AITEST_TRUE("Expected Root to be active.", Exec.ExpectInActiveStates("Root", "StateA", "StateB")); Exec.LogClear(); Exec.Tick(1.0f); AITEST_TRUE("Expected Root to be active.", Exec.ExpectInActiveStates("Root", "StateA", "StateB")); AITEST_FALSE(TEXT("StateTree Delegate shouldn't be triggered"), Exec.Expect("ListenerTask")); Exec.LogClear(); Exec.Tick(1.0f); AITEST_TRUE("Expected Root to be active.", Exec.ExpectInActiveStates("Root", "StateC")); AITEST_FALSE(TEXT("StateTree Delegate shouldn't be triggered"), Exec.Expect("ListenerTask")); Exec.LogClear(); Exec.Stop(); AITEST_FALSE(TEXT("StateTree Delegate shouldn't be triggered"), Exec.Expect("ListenerTask")); Exec.LogClear(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_Delegate_ListeningToDelegateOnExit2, "System.StateTree.Delegate.ListeningToDelegateOnExit2"); struct FStateTreeTest_Delegate_ListenerDispatcherOnNode : FStateTreeTestBase { virtual bool InstantTest() override { // Root(Dispatcher, ListenerOnNode1 -> DispatcherOnNode, ListenerOnNode2 -> DispatcherOnInstance, ListenerOnInstance -> DispatcherOnNode) UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); bool bListenerOnNode1Broadcasted = false; bool bListenerOnNode2Broadcasted = false; bool bListenerOnInstanceBroadcasted = false; { TStateTreeEditorNode& DispatcherTaskNode = Root.AddTask(FName(TEXT("Dispatcher"))); TStateTreeEditorNode& ListenerOnNode1TaskNode = Root.AddTask(FName(TEXT("ListenerOnNode1"))); ListenerOnNode1TaskNode.GetNode().CustomFunc = [&bListenerOnNode1Broadcasted](const FStateTreeWeakExecutionContext& WeakExecContext) { bListenerOnNode1Broadcasted = true; }; TStateTreeEditorNode& ListenerOnNode2TaskNode = Root.AddTask(FName(TEXT("ListenerOnNode2"))); ListenerOnNode2TaskNode.GetNode().CustomFunc = [&bListenerOnNode2Broadcasted](const FStateTreeWeakExecutionContext& WeakExecContext) { bListenerOnNode2Broadcasted = true; }; TStateTreeEditorNode& ListenerOnInstanceTaskNode = Root.AddTask(FName(TEXT("ListenerOnInstance"))); ListenerOnInstanceTaskNode.GetNode().CustomFunc = [&bListenerOnInstanceBroadcasted](const FStateTreeWeakExecutionContext& WeakExecContext) { bListenerOnInstanceBroadcasted = true; }; EditorData.AddPropertyBinding( FPropertyBindingPath(DispatcherTaskNode.GetNodeID(), GET_MEMBER_NAME_CHECKED(FTestTask_DispatcherOnNodeAndInstance, DispatcherOnNode)), FPropertyBindingPath(ListenerOnNode1TaskNode.GetNodeID(), GET_MEMBER_NAME_CHECKED(FTestTask_ListenerOnNode, ListenerOnNode))); EditorData.AddPropertyBinding( FPropertyBindingPath(DispatcherTaskNode.ID, GET_MEMBER_NAME_CHECKED(FTestTask_DispatcherOnNodeAndInstance_InstanceData, DispatcherOnInstance)), FPropertyBindingPath(ListenerOnNode2TaskNode.GetNodeID(), GET_MEMBER_NAME_CHECKED(FTestTask_ListenerOnNode, ListenerOnNode))); EditorData.AddPropertyBinding( FPropertyBindingPath(DispatcherTaskNode.GetNodeID(), GET_MEMBER_NAME_CHECKED(FTestTask_DispatcherOnNodeAndInstance, DispatcherOnNode)), FPropertyBindingPath(ListenerOnInstanceTaskNode.ID, GET_MEMBER_NAME_CHECKED(FTestTask_ListenerOnInstance_InstanceData, ListenerOnInstance))); } { FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); const FCompactStateTreeState& RootState = StateTree.GetStates()[0]; int32 TaskNodeIndex = RootState.TasksBegin; FConstStructView DispatcherView = StateTree.GetNode(TaskNodeIndex++); FConstStructView DispatcherInstanceDataView = StateTree.GetDefaultInstanceData().GetStruct(DispatcherView.Get().InstanceTemplateIndex.Get()); FConstStructView ListenerOnNode1View = StateTree.GetNode(TaskNodeIndex++); FConstStructView ListenerOnNode2View = StateTree.GetNode(TaskNodeIndex++); FConstStructView ListenerOnInstanceView = StateTree.GetNode(TaskNodeIndex++); FConstStructView ListenerOnInstanceInstanceDataView = StateTree.GetDefaultInstanceData().GetStruct(ListenerOnInstanceView.Get().InstanceTemplateIndex.Get()); const FStateTreeDelegateDispatcher DispatcherOnNode = DispatcherView.Get().DispatcherOnNode; const FStateTreeDelegateDispatcher DispatcherOnInstance = DispatcherInstanceDataView.Get().DispatcherOnInstance; const FStateTreeDelegateListener ListenerOnNode1 = ListenerOnNode1View.Get().ListenerOnNode; const FStateTreeDelegateListener ListenerOnNode2 = ListenerOnNode2View.Get().ListenerOnNode; const FStateTreeDelegateListener ListenerOnInstance = ListenerOnInstanceInstanceDataView.Get().ListenerOnInstance; AITEST_TRUE("Dispatcher on Node should init", DispatcherOnNode.IsValid()); AITEST_TRUE("Dispatcher on Instance should init", DispatcherOnInstance.IsValid()); AITEST_TRUE("Listener on Node should init", ListenerOnNode1.IsValid() && ListenerOnNode2.IsValid()); AITEST_TRUE("Listener on Instance should init", ListenerOnInstance.IsValid()); AITEST_EQUAL("ListenerOnNode1 should be bound to Dispatcher on Node", DispatcherOnNode, ListenerOnNode1.GetDispatcher()); AITEST_EQUAL("ListenerOnNode2 should be bound to Dispatcher on Instance", DispatcherOnInstance, ListenerOnNode2.GetDispatcher()); AITEST_EQUAL("ListenerOnInstance should be bound to Dispatcher on Node", DispatcherOnNode, ListenerOnInstance.GetDispatcher()); } { FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE("StateTree should init", bInitSucceeded); Exec.Start(); AITEST_TRUE("Expected Root to be active.", Exec.ExpectInActiveStates("Root")); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE("Expected Root to be active.", Exec.ExpectInActiveStates("Root")); AITEST_TRUE("ListenerOnNode1 should be broadcasted", bListenerOnNode1Broadcasted); AITEST_TRUE("ListenerOnNode2 should be broadcasted", bListenerOnNode2Broadcasted); AITEST_TRUE("ListenerOnInstance should be broadcasted", bListenerOnInstanceBroadcasted); Exec.Stop(); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_Delegate_ListenerDispatcherOnNode, "System.StateTree.Delegate.ListenerDispatcherOnNode"); struct FStateTreeTest_Delegate_DispatcherOnNodeListenerOnTransition : FStateTreeTestBase { virtual bool InstantTest() override { // Root(Dispatcher) // State1(Transition on Delegate -> DispatcherOnNode) -> State2 // State2(Transition on Delegate -> DispatcherOnInstance) -> Tree Succeeded UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); TStateTreeEditorNode DispatcherTaskNode = Root.AddTask(TEXT("Dispatcher")); UStateTreeState& State1 = Root.AddChildState(FName(TEXT("State1"))); FStateTreeTransition& DelegateOnNodeTransition = State1.AddTransition(EStateTreeTransitionTrigger::OnDelegate, EStateTreeTransitionType::NextState); UStateTreeState& State2 = Root.AddChildState(FName(TEXT("State2"))); FStateTreeTransition& DelegateOnInstanceTransition = State2.AddTransition(EStateTreeTransitionTrigger::OnDelegate, EStateTreeTransitionType::Succeeded); EditorData.AddPropertyBinding( FPropertyBindingPath(DispatcherTaskNode.GetNodeID(), GET_MEMBER_NAME_CHECKED(FTestTask_DispatcherOnNodeAndInstance, DispatcherOnNode)), FPropertyBindingPath(DelegateOnNodeTransition.ID, GET_MEMBER_NAME_CHECKED(FStateTreeTransition, DelegateListener))); EditorData.AddPropertyBinding( FPropertyBindingPath(DispatcherTaskNode.ID, GET_MEMBER_NAME_CHECKED(FTestTask_DispatcherOnNodeAndInstance_InstanceData, DispatcherOnInstance)), FPropertyBindingPath(DelegateOnInstanceTransition.ID, GET_MEMBER_NAME_CHECKED(FStateTreeTransition, DelegateListener))); { FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); const FCompactStateTreeState& RootState = StateTree.GetStates()[0]; const FCompactStateTreeState& State1State = StateTree.GetStates()[1]; const FCompactStateTreeState& State2State = StateTree.GetStates()[2]; const FTestTask_DispatcherOnNodeAndInstance& DispatcherTask = StateTree.GetNode(RootState.TasksBegin).Get(); const FStateTreeDelegateDispatcher DispatcherOnNode = DispatcherTask.DispatcherOnNode; const FStateTreeDelegateDispatcher DispatcherOnInstance = StateTree.GetDefaultInstanceData().GetStruct(DispatcherTask.InstanceTemplateIndex.Get()).Get().DispatcherOnInstance; AITEST_TRUE("Dispatcher on node should be valid", DispatcherOnNode.IsValid()); AITEST_TRUE("Dispatcher on instance should be valid", DispatcherOnInstance.IsValid()); const FCompactStateTransition* CompactDelegateOnNodeTransition = StateTree.GetTransitionFromIndex(FStateTreeIndex16(State1State.TransitionsBegin)); const FCompactStateTransition* CompactDelegateOnInstanceTransition = StateTree.GetTransitionFromIndex(FStateTreeIndex16(State2State.TransitionsBegin)); AITEST_EQUAL("State1 Transition Delegate Listener should be bound to Dispatcher on Node", CompactDelegateOnNodeTransition->RequiredDelegateDispatcher, DispatcherOnNode); AITEST_EQUAL("State2 Transition Delegate Listener should be bound to Dispatcher on Instance", CompactDelegateOnInstanceTransition->RequiredDelegateDispatcher, DispatcherOnInstance); } { FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE("StateTree should init", bInitSucceeded); Exec.Start(); AITEST_TRUE("Expected [Root, State1] to be active.", Exec.ExpectInActiveStates("Root", "State1")); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE("Expected [Root, State2] to be active.", Exec.ExpectInActiveStates("Root", "State2")); Exec.LogClear(); Exec.Tick(0.1f); AITEST_TRUE("Expected Tree to be succeeded.", Exec.GetStateTreeRunStatus() == EStateTreeRunStatus::Succeeded); Exec.LogClear(); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_Delegate_DispatcherOnNodeListenerOnTransition, "System.StateTree.Delegate.DispatcherOnNodeListenerOnTransition"); } // namespace UE::StateTree::Tests UE_ENABLE_OPTIMIZATION_SHIP #undef LOCTEXT_NAMESPACE