// 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" #define LOCTEXT_NAMESPACE "AITestSuite_StateTreeTestWeakContext" UE_DISABLE_OPTIMIZATION_SHIP namespace UE::StateTree::Tests { struct FStateTreeTest_WeakContext_FinishTask : FStateTreeTestBase { //Tree 1 : Global Task // Root : Task // State1 : Task -> Root // State2 : Task -> Root virtual bool InstantTest() override { struct FWeakContext { FStateTreeWeakExecutionContext ContextTree1GlobalTask; FStateTreeWeakExecutionContext ContextTree1RootTask; FStateTreeWeakExecutionContext ContextTree1State1Task; FStateTreeWeakExecutionContext ContextTree1State2Task; bool bGlobalFinishTaskSuccessOnTick = false; bool bState1lFinishTaskFailOnTick = false; }; FWeakContext WeakContext; // Building up the State Tree UStateTree& StateTree1 = NewStateTree(); { UStateTreeEditorData& EditorData1 = *Cast(StateTree1.EditorData); { // Global Task TStateTreeEditorNode& Tree1GlobalTask = EditorData1.AddGlobalTask(FName(TEXT("Tree1GlobalTask"))); { Tree1GlobalTask.GetNode().CustomEnterStateFunc = [&WeakContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { WeakContext.ContextTree1GlobalTask = Context.MakeWeakExecutionContext(); }; Tree1GlobalTask.GetNode().CustomTickFunc = [&WeakContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { if (WeakContext.bGlobalFinishTaskSuccessOnTick) { WeakContext.ContextTree1GlobalTask.FinishTask(EStateTreeFinishTaskType::Succeeded); } }; } // Root State UStateTreeState& Root = EditorData1.AddSubTree("Tree1StateRoot"); { TStateTreeEditorNode& Tree1RootTask = Root.AddTask(FName(TEXT("Tree1RootTask"))); Tree1RootTask.GetNode().CustomEnterStateFunc = [&WeakContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { WeakContext.ContextTree1RootTask = Context.MakeWeakExecutionContext(); }; } // State 1 UStateTreeState& State1 = Root.AddChildState("Tree1State1"); { TStateTreeEditorNode& Tree1State1Task = State1.AddTask(FName(TEXT("Tree1State1Task"))); Tree1State1Task.GetNode().CustomEnterStateFunc = [&WeakContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { WeakContext.ContextTree1State1Task = Context.MakeWeakExecutionContext(); }; Tree1State1Task.GetNode().CustomTickFunc = [&WeakContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { if (WeakContext.bState1lFinishTaskFailOnTick) { WeakContext.ContextTree1State1Task.FinishTask(EStateTreeFinishTaskType::Failed); } }; } // State 2 UStateTreeState& State2 = State1.AddChildState("Tree1State2"); { TStateTreeEditorNode& Tree1State2Task = State2.AddTask(FName(TEXT("Tree1State2Task"))); Tree1State2Task.GetNode().CustomEnterStateFunc = [&WeakContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { WeakContext.ContextTree1State2Task = Context.MakeWeakExecutionContext(); }; } }; } // Compile tree { FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree1); AITEST_TRUE(TEXT("StateTree1 should get compiled"), bResult); } // Create context FStateTreeInstanceData InstanceData; { { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); } { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); EStateTreeRunStatus Status = Exec.Start(); AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State1", "Tree1State2")); AITEST_TRUE(TEXT("Start should EnterState"), Exec.Expect("Tree1GlobalTask", TEXT("EnterState0")) .Then("Tree1RootTask", TEXT("EnterState0")) .Then("Tree1State1Task", TEXT("EnterState0")) .Then("Tree1State2Task", TEXT("EnterState0"))); Exec.LogClear(); } // Test that everything tick and there are no transitions. { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State1", "Tree1State2")); AITEST_TRUE(TEXT("Tick should Tick"), Exec.Expect("Tree1GlobalTask", TEXT("Tick0")) .Then("Tree1RootTask", TEXT("Tick0")) .Then("Tree1State1Task", TEXT("Tick0")) .Then("Tree1State2Task", TEXT("Tick0"))); Exec.LogClear(); } // Test Finish GlobalTask inside the tick { WeakContext.bGlobalFinishTaskSuccessOnTick = true; FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Succeeded"), Status, EStateTreeRunStatus::Succeeded); AITEST_TRUE(TEXT("Tick should Tick"), Exec.Expect("Tree1GlobalTask", TEXT("Tick0")) .Then("Tree1State2Task", TEXT("ExitState0")) .Then("Tree1State1Task", TEXT("ExitState0")) .Then("Tree1RootTask", TEXT("ExitState0"))); WeakContext.bGlobalFinishTaskSuccessOnTick = false; } // Finished global task stop the execution. Reset the execution. { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); Exec.Start(); Exec.LogClear(); } // Test Finish GlobalTask outside the tick { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); WeakContext.ContextTree1GlobalTask.FinishTask(EStateTreeFinishTaskType::Failed); EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Failed"), Status, EStateTreeRunStatus::Failed); AITEST_FALSE(TEXT("Tick should not Tick"), Exec.Expect("Tree1GlobalTask", TEXT("Tick0"))); AITEST_FALSE(TEXT("Tick should not Tick"), Exec.Expect("Tree1RootTask", TEXT("Tick0"))); AITEST_TRUE(TEXT("Tick should Tick"), Exec.Expect("Tree1State2Task", TEXT("ExitState0")) .Then("Tree1State1Task", TEXT("ExitState0")) .Then("Tree1RootTask", TEXT("ExitState0"))); } // Finished global task stop the execution. Reset the execution. { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); Exec.Start(); Exec.LogClear(); } // Test Finish StateTask inside the tick { WeakContext.bState1lFinishTaskFailOnTick = true; FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Tick should Tick"), Exec.Expect("Tree1GlobalTask", TEXT("Tick0")) .Then("Tree1RootTask", TEXT("Tick0")) .Then("Tree1State1Task", TEXT("Tick0")) .Then("Tree1State2Task", TEXT("ExitState0")) .Then("Tree1State1Task", TEXT("ExitState0")) .Then("Tree1RootTask", TEXT("ExitState0"))); WeakContext.bGlobalFinishTaskSuccessOnTick = false; } // Test Finish StateTask outside the tick { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); WeakContext.ContextTree1State1Task.FinishTask(EStateTreeFinishTaskType::Succeeded); EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Tick should Tick"), Exec.Expect("Tree1GlobalTask", TEXT("Tick0")) .Then("Tree1RootTask", TEXT("Tick0")) .Then("Tree1State2Task", TEXT("Tick0")) .Then("Tree1State2Task", TEXT("ExitState0")) .Then("Tree1State1Task", TEXT("ExitState0")) .Then("Tree1RootTask", TEXT("ExitState0"))); } // Stop the Exec { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); Exec.Stop(); } } return true; } }; IMPLEMENT_STATE_TREE_INSTANT_TEST(FStateTreeTest_WeakContext_FinishTask, "System.StateTree.WeakContext.FinishTask"); struct FStateTreeTest_WeakContext_InstanceData : FStateTreeTestBase { //Tree 1 : 2x Global Tasks, 2x global evaluators // Root // State1 : 2x tasks -> Next // State2 // State3Linked -> Tree 2 (fails) -> Next // State4Linked -> Tree 2 (complete) -> Next // State5 -> Next (dummy) //Tree 2 : 2x Global tasks, 2x global evaluators // Root // State1 : 2x tasks -> fail or wait //(1)Tree1GlobalEvaluator1 Context.Weak can access data //(2)Tree1GlobalEvaluator2 Context.Weak can access data and access data of Tree1GlobalEvaluator1 //(3)Tree1GlobalTask1 Context.Weak can access data and access data of Tree1GlobalEvaluator2 //(4)Tree1GlobalTask2 Context.Weak can access data and access data of Tree1GlobalTask1 //(1)Tree2GlobalEvaluator1 Context.Weak can access data and access data of Tree1GlobalTask2 //(2)Tree2GlobalEvaluator2 Context.Weak can access data and access data of Tree2GlobalEvaluator1 //(3)Tree2GlobalTask1 Context.Weak can access data and access data of Tree2GlobalEvaluator //(4)Tree2GlobalTask2 Context.Weak can access data and access data of Tree2GlobalTask1 //Tree2GlobalTask2 fails //(1)Tree2GlobalEvaluator1 Context.Weak can access data and access data of Tree1GlobalTask2 but not of previous Tree2GlobalEvaluator1 //(2)Tree2GlobalEvaluator2 Context.Weak can access data and access data of Tree2GlobalEvaluator1 //(3)Tree2GlobalTask1 Context.Weak can access data and access data of Tree2GlobalEvaluator //(4)Tree2GlobalTask2 Context.Weak can access data and access data of Tree2GlobalTask1 //Tree2GlobalTask2 success //can access all data //trigger another state selection of State1 to test the same during state selection virtual bool InstantTest() override { struct FTestContext { FStateTreeWeakExecutionContext ContextTree1GlobalEvaluator1; FStateTreeWeakExecutionContext ContextTree1GlobalEvaluator2; FStateTreeWeakExecutionContext ContextTree1GlobalTask1; FStateTreeWeakExecutionContext ContextTree1GlobalTask2; FStateTreeWeakExecutionContext ContextTree1State1Task1; FStateTreeWeakExecutionContext ContextTree1State1Task2; FStateTreeWeakExecutionContext ContextTree2GlobalEvaluator1_1; FStateTreeWeakExecutionContext ContextTree2GlobalEvaluator2_1; FStateTreeWeakExecutionContext ContextTree2GlobalTask1_1; FStateTreeWeakExecutionContext ContextTree2GlobalTask2_1; FStateTreeWeakExecutionContext ContextTree2State1Task1_1; FStateTreeWeakExecutionContext ContextTree2State1Task2_1; FStateTreeWeakExecutionContext ContextTree2GlobalEvaluator1_2; FStateTreeWeakExecutionContext ContextTree2GlobalEvaluator2_2; FStateTreeWeakExecutionContext ContextTree2GlobalTask1_2; FStateTreeWeakExecutionContext ContextTree2GlobalTask2_2; FStateTreeWeakExecutionContext ContextTree2State1Task1_2; FStateTreeWeakExecutionContext ContextTree2State1Task2_2; bool bSecondTree2Trigger = false; TArray ReadAllData() const { TArray Result; Result.Add(ReadEvalData(ContextTree1GlobalEvaluator1)); Result.Add(ReadEvalData(ContextTree1GlobalEvaluator2)); Result.Add(ReadTaskData(ContextTree1GlobalTask1)); Result.Add(ReadTaskData(ContextTree1GlobalTask2)); Result.Add(ReadTaskData(ContextTree1State1Task1)); Result.Add(ReadTaskData(ContextTree1State1Task2)); Result.Add(ReadEvalData(ContextTree2GlobalEvaluator1_1)); Result.Add(ReadEvalData(ContextTree2GlobalEvaluator2_1)); Result.Add(ReadTaskData(ContextTree2GlobalTask1_1)); Result.Add(ReadTaskData(ContextTree2GlobalTask2_1)); Result.Add(ReadTaskData(ContextTree2State1Task1_1)); Result.Add(ReadTaskData(ContextTree2State1Task2_1)); Result.Add(ReadEvalData(ContextTree2GlobalEvaluator1_2)); Result.Add(ReadEvalData(ContextTree2GlobalEvaluator2_2)); Result.Add(ReadTaskData(ContextTree2GlobalTask1_2)); Result.Add(ReadTaskData(ContextTree2GlobalTask2_2)); Result.Add(ReadTaskData(ContextTree2State1Task1_2)); Result.Add(ReadTaskData(ContextTree2State1Task2_2)); return Result; } FString ReadAllData_ToString() const { TStringBuilder<256> StringBuilder; StringBuilder << TEXT('{'); bool bFirst = true; for (int32 It : ReadAllData()) { if (!bFirst) { StringBuilder << TEXT(", "); } bFirst = false; StringBuilder << It; } StringBuilder << TEXT('}'); return StringBuilder.ToString(); } FString ExepectData(TArrayView AllElements, TArrayView ValidElements) { TStringBuilder<256> StringBuilder; StringBuilder << TEXT("AccessData:{"); bool bFirst = true; for (int32 Index = 0; Index < AllElements.Num(); ++Index) { if (!bFirst) { StringBuilder << TEXT(", "); } bFirst = false; if (ValidElements.Contains(Index)) { StringBuilder << AllElements[Index]; } else { constexpr int32 InvalidNumber = - 1; StringBuilder << InvalidNumber; } } StringBuilder << TEXT('}'); return StringBuilder.ToString(); } int32 ReadTaskData(const FStateTreeWeakExecutionContext& WeakContext) const { FStateTreeStrongReadOnlyExecutionContext StrongContext = WeakContext.MakeStrongReadOnlyExecutionContext(); const FTestTask_PrintValue::FInstanceDataType* Ptr = StrongContext.GetInstanceDataPtr(); return Ptr ? Ptr->Value : INDEX_NONE; } bool WriteTaskData(FStateTreeWeakExecutionContext& WeakContext, int32 NewValue) const { FStateTreeStrongExecutionContext StrongContext = WeakContext.MakeStrongExecutionContext(); FTestTask_PrintValue::FInstanceDataType* Ptr = StrongContext.GetInstanceDataPtr(); if (Ptr) { Ptr->Value = NewValue; return true; } return false; } int32 ReadEvalData(const FStateTreeWeakExecutionContext& WeakContext) const { FStateTreeStrongReadOnlyExecutionContext StrongContext = WeakContext.MakeStrongReadOnlyExecutionContext(); const FTestEval_Custom::FInstanceDataType* Ptr = StrongContext.GetInstanceDataPtr(); return Ptr ? Ptr->IntA : INDEX_NONE; } bool WriteEvalData(FStateTreeWeakExecutionContext& WeakContext, int32 NewValue) const { FStateTreeStrongExecutionContext StrongContext = WeakContext.MakeStrongExecutionContext(); FTestEval_Custom::FInstanceDataType* Ptr = StrongContext.GetInstanceDataPtr(); if (Ptr) { Ptr->IntA = NewValue; return true; } return false; } } TestContext; // Building up the State Tree UStateTree& StateTree2 = NewStateTree(); UStateTree& StateTree1 = NewStateTree(); #define UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC(T1) \ TestContext.T1 = Context.MakeWeakExecutionContext();\ FStateTreeTestLog& TestLog = Context.GetExternalData(Task->LogHandle);\ TArray AllDatas = TestContext.ReadAllData();\ TestLog.Log(Task->Name, FString::Printf(TEXT("AccessData:%s"), *TestContext.ReadAllData_ToString())); #define UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC_2(T1, T2) \ if (!TestContext.bSecondTree2Trigger)\ {\ TestContext.T1 = Context.MakeWeakExecutionContext();\ }\ else\ {\ TestContext.T2 = Context.MakeWeakExecutionContext();\ }\ FStateTreeTestLog& TestLog = Context.GetExternalData(Task->LogHandle);\ TArray AllDatas = TestContext.ReadAllData();\ TestLog.Log(Task->Name, FString::Printf(TEXT("AccessData:%s"), *TestContext.ReadAllData_ToString())); #define UE_STATETREE_TEST_CUSTOMTICKFUNC() \ FStateTreeTestLog& TestLog = Context.GetExternalData(Task->LogHandle);\ TArray AllDatas = TestContext.ReadAllData();\ TestLog.Log(Task->Name, FString::Printf(TEXT("AccessData:%s"), *TestContext.ReadAllData_ToString())); // Tree2 { UStateTreeEditorData& EditorData = *Cast(StateTree2.EditorData); { TStateTreeEditorNode& Tree2GlobalEval1 = EditorData.AddEvaluator(FName("Tree2GlobalEval1")); Tree2GlobalEval1.GetInstanceData().IntA = 201; Tree2GlobalEval1.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC_2(ContextTree2GlobalEvaluator1_1, ContextTree2GlobalEvaluator1_2); }; Tree2GlobalEval1.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree2GlobalEval1.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; TStateTreeEditorNode& Tree2GlobalEval2 = EditorData.AddEvaluator(FName("Tree2GlobalEval2")); Tree2GlobalEval2.GetInstanceData().IntA = 202; Tree2GlobalEval2.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC_2(ContextTree2GlobalEvaluator2_1, ContextTree2GlobalEvaluator2_2); }; Tree2GlobalEval2.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree2GlobalEval2.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; // Global Task TStateTreeEditorNode& Tree2GlobalTask1 = EditorData.AddGlobalTask(FName("Tree2GlobalTask1")); Tree2GlobalTask1.GetInstanceData().Value = 203; Tree2GlobalTask1.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC_2(ContextTree2GlobalTask1_1, ContextTree2GlobalTask1_2); }; Tree2GlobalTask1.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree2GlobalTask1.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; TStateTreeEditorNode& Tree2GlobalTask2 = EditorData.AddGlobalTask(FName("Tree2GlobalTask2")); Tree2GlobalTask2.GetInstanceData().Value = 204; Tree2GlobalTask2.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC_2(ContextTree2GlobalTask2_1, ContextTree2GlobalTask2_2); }; Tree2GlobalTask2.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree2GlobalTask2.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; } // Root State UStateTreeState& Root = EditorData.AddSubTree("Tree2StateRoot"); { TStateTreeEditorNode& Tree2RootTask1 = Root.AddTask(FName("Tree2RootTask1")); Tree2RootTask1.GetInstanceData().Value = 211; TStateTreeEditorNode& Tree2RootTask2 = Root.AddTask(FName("Tree2RootTask2")); Tree2RootTask2.GetInstanceData().Value = 212; } // State 1 UStateTreeState& State1 = Root.AddChildState("Tree2State1"); { TStateTreeEditorNode& Tree2State1Task1 = State1.AddTask(FName("Tree2State1Task1")); Tree2State1Task1.GetInstanceData().Value = 221; Tree2State1Task1.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC_2(ContextTree2State1Task1_1, ContextTree2State1Task1_2); if (!TestContext.bSecondTree2Trigger) { FTestTask_PrintValue::FInstanceDataType& InstanceData = Context.GetInstanceData(*Task); InstanceData.EnterStateRunStatus = EStateTreeRunStatus::Failed; TestContext.bSecondTree2Trigger = true; } }; Tree2State1Task1.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree2State1Task1.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; TStateTreeEditorNode& Tree2State1Task2 = State1.AddTask(FName("Tree2State1Task2")); Tree2State1Task2.GetInstanceData().Value = 222; Tree2State1Task2.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC_2(ContextTree2State1Task2_1, ContextTree2State1Task2_2); }; Tree2State1Task2.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree2State1Task2.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; } // Compile tree { FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree2); AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult); } } // Tree1 { UStateTreeEditorData& EditorData = *Cast(StateTree1.EditorData); { // Global Evaluator TStateTreeEditorNode& Tree1GlobalEval1 = EditorData.AddEvaluator(FName("Tree1GlobalEval1")); Tree1GlobalEval1.GetInstanceData().IntA = 101; Tree1GlobalEval1.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC(ContextTree1GlobalEvaluator1); }; Tree1GlobalEval1.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree1GlobalEval1.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; TStateTreeEditorNode& Tree1GlobalEval2 = EditorData.AddEvaluator(FName("Tree1GlobalEval2")); Tree1GlobalEval2.GetInstanceData().IntA = 102; Tree1GlobalEval2.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC(ContextTree1GlobalEvaluator2); }; Tree1GlobalEval2.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree1GlobalEval2.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestEval_Custom* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; // Global Task TStateTreeEditorNode& Tree1GlobalTask1 = EditorData.AddGlobalTask(FName("Tree1GlobalTask1")); Tree1GlobalTask1.GetInstanceData().Value = 103; Tree1GlobalTask1.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC(ContextTree1GlobalTask1); }; Tree1GlobalTask1.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree1GlobalTask1.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; TStateTreeEditorNode& Tree1GlobalTask2 = EditorData.AddGlobalTask(FName("Tree1GlobalTask2")); Tree1GlobalTask2.GetInstanceData().Value = 104; Tree1GlobalTask2.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC(ContextTree1GlobalTask2); }; Tree1GlobalTask2.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree1GlobalTask2.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; } // Root State UStateTreeState& Root = EditorData.AddSubTree("Tree1StateRoot"); { TStateTreeEditorNode& Tree1RootTask1 = Root.AddTask(FName("Tree1RootTask1")); Tree1RootTask1.GetInstanceData().Value = 111; TStateTreeEditorNode& Tree1RootTask2 = Root.AddTask(FName("Tree1RootTask2")); Tree1RootTask2.GetInstanceData().Value = 112; } // State 1 UStateTreeState& State1 = Root.AddChildState("Tree1State1"); State1.SelectionBehavior = EStateTreeStateSelectionBehavior::TrySelectChildrenInOrder; { TStateTreeEditorNode& Tree1State1Task1 = State1.AddTask(FName("Tree1State1Task1")); Tree1State1Task1.GetInstanceData().Value = 121; Tree1State1Task1.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC(ContextTree1State1Task1); }; Tree1State1Task1.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree1State1Task1.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; TStateTreeEditorNode& Tree1State1Task2 = State1.AddTask(FName("Tree1State1Task2")); Tree1State1Task2.GetInstanceData().Value = 122; Tree1State1Task2.GetNode().CustomEnterStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC(ContextTree1State1Task2); }; Tree1State1Task2.GetNode().CustomTickFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; Tree1State1Task2.GetNode().CustomExitStateFunc = [&TestContext](FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { UE_STATETREE_TEST_CUSTOMTICKFUNC(); }; } UStateTreeState& State2 = State1.AddChildState("Tree1State2"); State1.SelectionBehavior = EStateTreeStateSelectionBehavior::TrySelectChildrenInOrder; UStateTreeState& State3 = State2.AddChildState("Tree1State3", EStateTreeStateType::LinkedAsset); State3.SetLinkedStateAsset(&StateTree2); State3.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextState); UStateTreeState& State4 = State2.AddChildState("Tree1State4", EStateTreeStateType::LinkedAsset); State4.SetLinkedStateAsset(&StateTree2); UStateTreeState& State5 = State2.AddChildState("Tree1State5"); { TStateTreeEditorNode& Tree1State5Task1 = State4.AddTask(FName("Tree1State5Task1")); Tree1State5Task1.GetInstanceData().Value = 155; } // Compile tree { FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree1); AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult); } } #undef UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC #undef UE_STATETREE_TEST_CUSTOMENTERSTATEFUNC_2 #undef UE_STATETREE_TEST_CUSTOMTICKFUNC // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const int32 AllValidReadData[] = { 101, 102, 103, 104, 121, 122, 201, 202, 203, 204, 221, 222, 201, 202, 203, 204, 221, 222 }; // Create context FStateTreeInstanceData InstanceData; { { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); } { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); EStateTreeRunStatus Status = Exec.Start(); AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State1", "Tree1State2", "Tree1State3", "Tree2StateRoot", "Tree2State1")); FTestStateTreeExecutionContext::FLogOrder ExpectedLog = Exec.Expect("Tree1GlobalEval1", TestContext.ExepectData(AllValidReadData, { 0 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree1GlobalEval2", TestContext.ExepectData(AllValidReadData, { 0, 1 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree1GlobalTask1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree1GlobalTask2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree1State1Task1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 6, 7, 8, 9 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree1State1Task2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = Exec.Expect("Tree2GlobalEval1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 6 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalEval2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 6, 7 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalTask1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 6, 7, 8 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalTask2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 6, 7, 8, 9 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2State1Task1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2State1Task2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 })); // 11 is not valid AITEST_FALSE(TEXT("Start should EnterState"), ExpectedLog); } { TArray AllDatas = TestContext.ReadAllData(); AITEST_TRUE(TEXT("After enter. Same num"), AllDatas.Num() == UE_ARRAY_COUNT(AllValidReadData)); AITEST_TRUE(TEXT("After enter. Same Value"), AllDatas == MakeArrayView({ 101, 102, 103, 104, 121, 122, 201, 202, 203, 204, 221, -1, -1, -1, -1, -1, -1, -1 })); } { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1State1", "Tree1State2", "Tree1State4", "Tree2StateRoot", "Tree2State1")); // Tick FTestStateTreeExecutionContext::FLogOrder ExpectedLog = Exec.Expect("Tree2GlobalTask2", TEXT("Tick204")); AITEST_TRUE(TEXT("Tick"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalTask2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 })); AITEST_TRUE(TEXT("Tick"), ExpectedLog); // Enter second Tree 2 { ExpectedLog = ExpectedLog.Then("Tree2GlobalEval1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12 })); AITEST_TRUE(TEXT("Tick Enter new tree"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalEval2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalTask1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalTask2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15 })); AITEST_TRUE(TEXT("Start should EnterState"), ExpectedLog); } // Exit first Tree 2 { ExpectedLog = ExpectedLog.Then("Tree2State1Task1", TEXT("ExitState221")); AITEST_TRUE(TEXT("Exit first tree2"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2State1Task1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15 })); AITEST_TRUE(TEXT("Exit first tree2"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2RootTask1", TEXT("ExitState211")); AITEST_TRUE(TEXT("Exit first tree2"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalTask2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15 })); // -10 AITEST_TRUE(TEXT("Exit first tree2"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalTask1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 13, 14, 15 })); //-9 AITEST_TRUE(TEXT("Exit first tree2"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalEval2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15 })); //-8 AITEST_TRUE(TEXT("Exit first tree2"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2GlobalEval1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 6, 12, 13, 14, 15 })); //-7 AITEST_TRUE(TEXT("Exit first tree2"), ExpectedLog); } // Enter second Tree 2 { ExpectedLog = ExpectedLog.Then("Tree2RootTask1", TEXT("EnterState211")); AITEST_TRUE(TEXT("Exit first tree2"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2State1Task1", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 16 })); //-6+16 AITEST_TRUE(TEXT("Exit first tree2"), ExpectedLog); ExpectedLog = ExpectedLog.Then("Tree2State1Task2", TestContext.ExepectData(AllValidReadData, { 0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 16, 17 })); //+17 AITEST_TRUE(TEXT("Exit first tree2"), ExpectedLog); } } { TArray AllDatas = TestContext.ReadAllData(); AITEST_TRUE(TEXT("After enter. Same num"), AllDatas.Num() == UE_ARRAY_COUNT(AllValidReadData)); AITEST_TRUE(TEXT("After enter. Same Value"), AllDatas == MakeArrayView({ 101, 102, 103, 104, 121, 122, -1, -1, -1, -1, -1, -1, 201, 202, 203, 204, 221, 222 })); } // Stop the Exec { FTestStateTreeExecutionContext Exec(StateTree1, StateTree1, InstanceData); Exec.Stop(); } } return true; } }; IMPLEMENT_STATE_TREE_INSTANT_TEST(FStateTreeTest_WeakContext_InstanceData, "System.StateTree.WeakContext.InstanceData"); } // namespace UE::StateTree::Tests UE_ENABLE_OPTIMIZATION_SHIP #undef LOCTEXT_NAMESPACE