2021-09-28 13:33:17 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "StateTreeTest.h"
# include "AITestsCommon.h"
2023-01-25 02:42:36 -05:00
# include "StateTreeCompilerLog.h"
2021-09-28 13:33:17 -04:00
# include "StateTreeEditorData.h"
2022-04-05 09:44:28 -04:00
# include "StateTreeCompiler.h"
2022-08-23 13:04:52 -04:00
# include "Conditions/StateTreeCommonConditions.h"
2021-09-28 13:33:17 -04:00
# include "StateTreeTestTypes.h"
2021-11-17 18:06:10 -05:00
# include "Engine/World.h"
2022-09-07 17:12:18 -04:00
# include "Async/ParallelFor.h"
2023-03-14 10:40:47 -04:00
# include "GameplayTagsManager.h"
2021-09-28 13:33:17 -04:00
2022-09-28 01:06:15 -04:00
# include UE_INLINE_GENERATED_CPP_BY_NAME(StateTreeTest)
2021-09-28 13:33:17 -04:00
# define LOCTEXT_NAMESPACE "AITestSuite_StateTreeTest"
2022-11-22 08:13:54 -05:00
UE_DISABLE_OPTIMIZATION_SHIP
2022-09-07 17:12:18 -04:00
std : : atomic < int32 > FStateTreeTestConditionInstanceData : : GlobalCounter = 0 ;
2021-09-28 13:33:17 -04:00
namespace UE : : StateTree : : Tests
{
2021-11-17 18:06:10 -05:00
UStateTree & NewStateTree ( UObject * Outer = GetTransientPackage ( ) )
2021-09-28 13:33:17 -04:00
{
2021-11-17 18:06:10 -05:00
UStateTree * StateTree = NewObject < UStateTree > ( Outer ) ;
2021-09-28 13:33:17 -04:00
check ( StateTree ) ;
UStateTreeEditorData * EditorData = NewObject < UStateTreeEditorData > ( StateTree ) ;
check ( EditorData ) ;
StateTree - > EditorData = EditorData ;
2022-04-12 15:55:39 -04:00
EditorData - > Schema = NewObject < UStateTreeTestSchema > ( ) ;
2021-09-28 13:33:17 -04:00
return * StateTree ;
}
2022-04-05 03:20:57 -04:00
2024-01-11 04:24:45 -05:00
FStateTreePropertyPathBinding MakeBinding ( const FGuid & SourceID , const FString & Source , const FGuid & TargetID , const FString & Target )
{
FStateTreePropertyPath SourcePath ;
SourcePath . FromString ( Source ) ;
SourcePath . SetStructID ( SourceID ) ;
FStateTreePropertyPath TargetPath ;
TargetPath . FromString ( Target ) ;
TargetPath . SetStructID ( TargetID ) ;
return FStateTreePropertyPathBinding ( SourcePath , TargetPath ) ;
}
2023-03-14 10:40:47 -04:00
// Helper struct to define some test tags
struct FNativeGameplayTags : public FGameplayTagNativeAdder
{
virtual ~ FNativeGameplayTags ( ) { }
FGameplayTag TestTag ;
virtual void AddTags ( ) override
{
UGameplayTagsManager & Manager = UGameplayTagsManager : : Get ( ) ;
TestTag = Manager . AddNativeGameplayTag ( TEXT ( " Test.StateTree.Tag " ) ) ;
}
FORCEINLINE static const FNativeGameplayTags & Get ( )
{
return StaticInstance ;
}
static FNativeGameplayTags StaticInstance ;
} ;
FNativeGameplayTags FNativeGameplayTags : : StaticInstance ;
2021-09-28 13:33:17 -04:00
}
struct FStateTreeTest_MakeAndBakeStateTree : FAITestBase
{
virtual bool InstantTest ( ) override
{
2021-11-17 18:06:10 -05:00
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
2021-09-28 13:33:17 -04:00
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & StateA = Root . AddChildState ( FName ( TEXT ( " A " ) ) ) ;
UStateTreeState & StateB = Root . AddChildState ( FName ( TEXT ( " B " ) ) ) ;
// Root
2022-05-16 05:13:27 -04:00
auto & EvalA = EditorData . AddEvaluator < FTestEval_A > ( ) ;
2021-10-21 04:08:20 -04:00
2021-09-28 13:33:17 -04:00
// State A
2021-11-12 05:48:11 -05:00
auto & TaskB1 = StateA . AddTask < FTestTask_B > ( ) ;
2023-02-10 07:22:48 -05:00
EditorData . AddPropertyBinding ( EvalA , TEXT ( " IntA " ) , TaskB1 , TEXT ( " IntB " ) ) ;
2021-09-28 13:33:17 -04:00
2022-08-23 13:04:52 -04:00
auto & IntCond = StateA . AddEnterCondition < FStateTreeCompareIntCondition > ( EGenericAICheck : : Less ) ;
2022-09-07 17:12:18 -04:00
IntCond . GetInstanceData ( ) . Right = 2 ;
2021-11-12 05:48:11 -05:00
2023-02-10 07:22:48 -05:00
EditorData . AddPropertyBinding ( EvalA , TEXT ( " IntA " ) , IntCond , TEXT ( " Left " ) ) ;
2021-09-28 13:33:17 -04:00
2022-09-01 09:06:53 -04:00
StateA . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : GotoState , & StateB ) ;
2021-09-28 13:33:17 -04:00
// State B
2021-11-12 05:48:11 -05:00
auto & TaskB2 = StateB . AddTask < FTestTask_B > ( ) ;
2023-02-10 07:22:48 -05:00
EditorData . AddPropertyBinding ( EvalA , TEXT ( " bBoolA " ) , TaskB2 , TEXT ( " bBoolB " ) ) ;
2021-09-28 13:33:17 -04:00
2022-08-23 13:04:52 -04:00
FStateTreeTransition & Trans = StateB . AddTransition ( { } , EStateTreeTransitionType : : GotoState , & Root ) ;
auto & TransFloatCond = Trans . AddCondition < FStateTreeCompareFloatCondition > ( EGenericAICheck : : Less ) ;
2022-09-07 17:12:18 -04:00
TransFloatCond . GetInstanceData ( ) . Right = 13.0f ;
2023-02-10 07:22:48 -05:00
EditorData . AddPropertyBinding ( EvalA , TEXT ( " FloatA " ) , TransFloatCond , TEXT ( " Left " ) ) ;
2021-09-28 13:33:17 -04:00
2022-09-01 09:06:53 -04:00
StateB . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : Succeeded ) ;
2021-09-28 13:33:17 -04:00
2021-10-29 04:33:21 -04:00
FStateTreeCompilerLog Log ;
2022-04-05 09:44:28 -04:00
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
2021-09-28 13:33:17 -04:00
2022-04-05 09:44:28 -04:00
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
2023-11-22 04:08:33 -05:00
AITEST_TRUE ( " StateTree should be ready to run " , StateTree . IsReadyToRun ( ) ) ;
2021-09-28 13:33:17 -04:00
return true ;
}
} ;
2021-12-09 05:26:00 -05:00
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_MakeAndBakeStateTree , " System.StateTree.MakeAndBakeStateTree " ) ;
2021-09-28 13:33:17 -04:00
2023-11-22 04:08:33 -05:00
struct FStateTreeTest_EmptyStateTree : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
Root . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : Succeeded ) ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree should be completed " , Status = = EStateTreeRunStatus : : Succeeded ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_EmptyStateTree , " System.StateTree.Empty " ) ;
2021-09-28 13:33:17 -04:00
struct FStateTreeTest_Sequence : FAITestBase
{
virtual bool InstantTest ( ) override
{
2021-11-17 18:06:10 -05:00
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
2021-09-28 13:33:17 -04:00
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & State1 = Root . AddChildState ( FName ( TEXT ( " State1 " ) ) ) ;
UStateTreeState & State2 = Root . AddChildState ( FName ( TEXT ( " State2 " ) ) ) ;
2021-11-12 05:48:11 -05:00
auto & Task1 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
2022-09-01 09:06:53 -04:00
State1 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : NextState ) ;
2021-09-28 13:33:17 -04:00
2021-11-12 05:48:11 -05:00
auto & Task2 = State2 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task2 " ) ) ) ;
2022-09-01 09:06:53 -04:00
State2 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : Succeeded ) ;
2021-09-28 13:33:17 -04:00
2021-10-29 04:33:21 -04:00
FStateTreeCompilerLog Log ;
2022-04-05 09:44:28 -04:00
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
2021-09-28 13:33:17 -04:00
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
2022-09-23 20:02:42 -04:00
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
2021-09-28 13:33:17 -04:00
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
2021-11-22 12:11:19 -05:00
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task1 should enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_FALSE ( " StateTree Task1 should not tick " , Exec . Expect ( Task1 . GetName ( ) , TickStr ) ) ;
Exec . LogClear ( ) ;
2021-09-28 13:33:17 -04:00
Status = Exec . Tick ( 0.1f ) ;
2021-11-22 12:11:19 -05:00
AITEST_TRUE ( " StateTree Task1 should tick, and exit state " , Exec . Expect ( Task1 . GetName ( ) , TickStr ) . Then ( Task1 . GetName ( ) , ExitStateStr ) ) ;
AITEST_TRUE ( " StateTree Task2 should enter state " , Exec . Expect ( Task2 . GetName ( ) , EnterStateStr ) ) ;
2021-11-12 05:48:11 -05:00
AITEST_FALSE ( " StateTree Task2 should not tick " , Exec . Expect ( Task2 . GetName ( ) , TickStr ) ) ;
2021-09-28 13:33:17 -04:00
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
Status = Exec . Tick ( 0.1f ) ;
2021-11-22 12:11:19 -05:00
AITEST_TRUE ( " StateTree Task2 should tick, and exit state " , Exec . Expect ( Task2 . GetName ( ) , TickStr ) . Then ( Task2 . GetName ( ) , ExitStateStr ) ) ;
2021-11-12 05:48:11 -05:00
AITEST_FALSE ( " StateTree Task1 should not tick " , Exec . Expect ( Task1 . GetName ( ) , TickStr ) ) ;
2021-09-28 13:33:17 -04:00
AITEST_TRUE ( " StateTree should be completed " , Status = = EStateTreeRunStatus : : Succeeded ) ;
2021-11-22 12:11:19 -05:00
Exec . LogClear ( ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_FALSE ( " StateTree Task1 should not tick " , Exec . Expect ( Task1 . GetName ( ) , TickStr ) ) ;
AITEST_FALSE ( " StateTree Task2 should not tick " , Exec . Expect ( Task2 . GetName ( ) , TickStr ) ) ;
Exec . LogClear ( ) ;
2021-09-28 13:33:17 -04:00
return true ;
}
} ;
2021-12-09 05:26:00 -05:00
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Sequence , " System.StateTree.Sequence " ) ;
2021-09-28 13:33:17 -04:00
struct FStateTreeTest_Select : FAITestBase
{
virtual bool InstantTest ( ) override
{
2021-11-17 18:06:10 -05:00
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
2021-09-28 13:33:17 -04:00
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & State1 = Root . AddChildState ( FName ( TEXT ( " State1 " ) ) ) ;
UStateTreeState & State1A = State1 . AddChildState ( FName ( TEXT ( " State1A " ) ) ) ;
2021-11-12 05:48:11 -05:00
auto & TaskRoot = Root . AddTask < FTestTask_Stand > ( FName ( TEXT ( " TaskRoot " ) ) ) ;
2022-11-22 08:13:54 -05:00
TaskRoot . GetNode ( ) . TicksToCompletion = 3 ; // let Task1A to complete first
2021-09-28 13:33:17 -04:00
2021-11-12 05:48:11 -05:00
auto & Task1 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
2022-11-22 08:13:54 -05:00
Task1 . GetNode ( ) . TicksToCompletion = 3 ; // let Task1A to complete first
2021-09-28 13:33:17 -04:00
2021-11-12 05:48:11 -05:00
auto & Task1A = State1A . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1A " ) ) ) ;
2022-09-07 17:12:18 -04:00
Task1A . GetNode ( ) . TicksToCompletion = 2 ;
2022-09-01 09:06:53 -04:00
State1A . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : GotoState , & State1 ) ;
2021-09-28 13:33:17 -04:00
2021-10-29 04:33:21 -04:00
FStateTreeCompilerLog Log ;
2022-04-05 09:44:28 -04:00
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
2021-09-28 13:33:17 -04:00
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
2022-09-23 20:02:42 -04:00
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
2021-09-28 13:33:17 -04:00
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
2021-11-22 12:11:19 -05:00
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree TaskRoot should enter state " , Exec . Expect ( TaskRoot . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree Task1 should enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree Task1A should enter state " , Exec . Expect ( Task1A . GetName ( ) , EnterStateStr ) ) ;
AITEST_FALSE ( " StateTree TaskRoot should not tick " , Exec . Expect ( TaskRoot . GetName ( ) , TickStr ) ) ;
AITEST_FALSE ( " StateTree Task1 should not tick " , Exec . Expect ( Task1 . GetName ( ) , TickStr ) ) ;
AITEST_FALSE ( " StateTree Task1A should not tick " , Exec . Expect ( Task1A . GetName ( ) , TickStr ) ) ;
2021-09-28 13:33:17 -04:00
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
2023-11-22 04:08:33 -05:00
// Regular tick, no state selection at all.
2021-09-28 13:33:17 -04:00
Status = Exec . Tick ( 0.1f ) ;
2021-11-12 05:48:11 -05:00
AITEST_TRUE ( " StateTree tasks should update in order " , Exec . Expect ( TaskRoot . GetName ( ) , TickStr ) . Then ( Task1 . GetName ( ) , TickStr ) . Then ( Task1A . GetName ( ) , TickStr ) ) ;
AITEST_FALSE ( " StateTree TaskRoot should not EnterState " , Exec . Expect ( TaskRoot . GetName ( ) , EnterStateStr ) ) ;
AITEST_FALSE ( " StateTree Task1 should not EnterState " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_FALSE ( " StateTree Task1A should not EnterState " , Exec . Expect ( Task1A . GetName ( ) , EnterStateStr ) ) ;
AITEST_FALSE ( " StateTree TaskRoot should not ExitState " , Exec . Expect ( TaskRoot . GetName ( ) , ExitStateStr ) ) ;
AITEST_FALSE ( " StateTree Task1 should not ExitState " , Exec . Expect ( Task1 . GetName ( ) , ExitStateStr ) ) ;
AITEST_FALSE ( " StateTree Task1A should not ExitState " , Exec . Expect ( Task1A . GetName ( ) , ExitStateStr ) ) ;
2021-09-28 13:33:17 -04:00
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
// Partial reselect, Root should not get EnterState
Status = Exec . Tick ( 0.1f ) ;
2021-11-12 05:48:11 -05:00
AITEST_FALSE ( " StateTree TaskRoot should not enter state " , Exec . Expect ( TaskRoot . GetName ( ) , EnterStateStr ) ) ;
2021-11-22 12:11:19 -05:00
AITEST_TRUE ( " StateTree Task1 should tick, exit state, and enter state " , Exec . Expect ( Task1 . GetName ( ) , TickStr ) . Then ( Task1 . GetName ( ) , ExitStateStr ) . Then ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree Task1A should tick, exit state, and enter state " , Exec . Expect ( Task1A . GetName ( ) , TickStr ) . Then ( Task1A . GetName ( ) , ExitStateStr ) . Then ( Task1A . GetName ( ) , EnterStateStr ) ) ;
2021-09-28 13:33:17 -04:00
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
2021-12-09 05:26:00 -05:00
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Select , " System.StateTree.Select " ) ;
struct FStateTreeTest_FailEnterState : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & State1 = Root . AddChildState ( FName ( TEXT ( " State1 " ) ) ) ;
UStateTreeState & State1A = State1 . AddChildState ( FName ( TEXT ( " State1A " ) ) ) ;
auto & TaskRoot = Root . AddTask < FTestTask_Stand > ( FName ( TEXT ( " TaskRoot " ) ) ) ;
auto & Task1 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
auto & Task2 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task2 " ) ) ) ;
2022-09-07 17:12:18 -04:00
Task2 . GetNode ( ) . EnterStateResult = EStateTreeRunStatus : : Failed ;
2021-12-09 05:26:00 -05:00
auto & Task3 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task3 " ) ) ) ;
auto & Task1A = State1A . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1A " ) ) ) ;
2022-09-01 09:06:53 -04:00
State1A . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : GotoState , & State1 ) ;
2021-12-09 05:26:00 -05:00
FStateTreeCompilerLog Log ;
2022-04-05 09:44:28 -04:00
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
2021-12-09 05:26:00 -05:00
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
2022-09-23 20:02:42 -04:00
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
2021-12-09 05:26:00 -05:00
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree TaskRoot should enter state " , Exec . Expect ( TaskRoot . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree Task1 should enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree Task2 should enter state " , Exec . Expect ( Task2 . GetName ( ) , EnterStateStr ) ) ;
AITEST_FALSE ( " StateTree Task3 should not enter state " , Exec . Expect ( Task3 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree Should execute StateCompleted in reverse order " , Exec . Expect ( Task2 . GetName ( ) , StateCompletedStr ) . Then ( Task1 . GetName ( ) , StateCompletedStr ) . Then ( TaskRoot . GetName ( ) , StateCompletedStr ) ) ;
AITEST_FALSE ( " StateTree Task3 should not state complete " , Exec . Expect ( Task3 . GetName ( ) , StateCompletedStr ) ) ;
AITEST_TRUE ( " StateTree exec status should be failed " , Exec . GetLastTickStatus ( ) = = EStateTreeRunStatus : : Failed ) ;
Exec . LogClear ( ) ;
// Stop and exit state
Status = Exec . Stop ( ) ;
AITEST_TRUE ( " StateTree TaskRoot should exit state " , Exec . Expect ( TaskRoot . GetName ( ) , ExitStateStr ) ) ;
AITEST_TRUE ( " StateTree Task1 should exit state " , Exec . Expect ( Task1 . GetName ( ) , ExitStateStr ) ) ;
AITEST_TRUE ( " StateTree Task2 should exit state " , Exec . Expect ( Task2 . GetName ( ) , ExitStateStr ) ) ;
AITEST_FALSE ( " StateTree Task3 should not exit state " , Exec . Expect ( Task3 . GetName ( ) , ExitStateStr ) ) ;
2023-05-10 09:30:13 -04:00
AITEST_TRUE ( " StateTree status should be stopped " , Status = = EStateTreeRunStatus : : Stopped ) ;
2021-12-09 05:26:00 -05:00
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_FailEnterState , " System.StateTree.FailEnterState " ) ;
2021-09-28 13:33:17 -04:00
2022-11-23 09:22:14 -05:00
struct FStateTreeTest_Restart : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & State1 = Root . AddChildState ( FName ( TEXT ( " State1 " ) ) ) ;
auto & Task1 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
Task1 . GetNode ( ) . TicksToCompletion = 2 ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task1 should enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree exec status should be running " , Exec . GetLastTickStatus ( ) = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
// Tick
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree exec status should be running " , Exec . GetLastTickStatus ( ) = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
// Call Start again, should stop and start the tree.
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task1 should exit state " , Exec . Expect ( Task1 . GetName ( ) , ExitStateStr ) ) ;
AITEST_TRUE ( " StateTree Task1 should enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree exec status should be running " , Exec . GetLastTickStatus ( ) = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Restart , " System.StateTree.Restart " ) ;
2022-04-05 03:20:57 -04:00
struct FStateTreeTest_SubTree : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & State1 = Root . AddChildState ( FName ( TEXT ( " State1 " ) ) , EStateTreeStateType : : Linked ) ;
UStateTreeState & State2 = Root . AddChildState ( FName ( TEXT ( " State2 " ) ) ) ;
2022-07-21 08:19:56 -04:00
UStateTreeState & State3 = Root . AddChildState ( FName ( TEXT ( " State3 " ) ) , EStateTreeStateType : : Subtree ) ;
2022-04-05 03:20:57 -04:00
UStateTreeState & State3A = State3 . AddChildState ( FName ( TEXT ( " State3A " ) ) ) ;
UStateTreeState & State3B = State3 . AddChildState ( FName ( TEXT ( " State3B " ) ) ) ;
2023-01-23 12:48:04 -05:00
State1 . LinkedSubtree = State3 . GetLinkToState ( ) ;
2022-04-05 03:20:57 -04:00
2022-11-23 09:22:14 -05:00
State1 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : GotoState , & State2 ) ;
2022-04-05 03:20:57 -04:00
auto & Task2 = State2 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task2 " ) ) ) ;
2022-11-23 09:22:14 -05:00
State2 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : Succeeded ) ;
2022-04-05 03:20:57 -04:00
auto & Task3A = State3A . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task3A " ) ) ) ;
2022-09-01 09:06:53 -04:00
State3A . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : GotoState , & State3B ) ;
2022-04-05 03:20:57 -04:00
auto & Task3B = State3B . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task3B " ) ) ) ;
2022-11-23 09:22:14 -05:00
State3B . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : Succeeded ) ;
2022-04-05 03:20:57 -04:00
FStateTreeCompilerLog Log ;
2022-04-05 09:44:28 -04:00
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
2022-04-05 03:20:57 -04:00
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
2022-09-23 20:02:42 -04:00
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
2022-04-05 03:20:57 -04:00
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Active States should be in Root/State1/State3/State3A " , Exec . ExpectInActiveStates ( Root . Name , State1 . Name , State3 . Name , State3A . Name ) ) ;
2023-06-12 07:34:11 -04:00
AITEST_FALSE ( " StateTree Task2 should not enter state " , Exec . Expect ( Task2 . GetName ( ) , EnterStateStr ) ) ;
2022-04-05 03:20:57 -04:00
AITEST_TRUE ( " StateTree Task3A should enter state " , Exec . Expect ( Task3A . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
2022-11-23 09:22:14 -05:00
// Transition within subtree
2022-04-05 03:20:57 -04:00
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Active States should be in Root/State1/State3/State3B " , Exec . ExpectInActiveStates ( Root . Name , State1 . Name , State3 . Name , State3B . Name ) ) ;
AITEST_TRUE ( " StateTree Task3B should enter state " , Exec . Expect ( Task3B . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
2022-11-23 09:22:14 -05:00
Exec . LogClear ( ) ;
// Complete subtree
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Active States should be in Root/State2 " , Exec . ExpectInActiveStates ( Root . Name , State2 . Name ) ) ;
AITEST_TRUE ( " StateTree Task2 should enter state " , Exec . Expect ( Task2 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
// Complete the whole tree
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree should complete in succeeded " , Status = = EStateTreeRunStatus : : Succeeded ) ;
Exec . LogClear ( ) ;
2022-04-05 03:20:57 -04:00
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_SubTree , " System.StateTree.SubTree " ) ;
2023-11-22 04:08:33 -05:00
struct FStateTreeTest_SubTreeCondition : FAITestBase
{
virtual bool InstantTest ( ) override
{
/*
- Root
- Linked : Subtree - > Root
- SubTree : Task1
- ? State1 : Task2 - > Succeeded // condition linked to Task1
- State2 : Task3
*/
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & Linked = Root . AddChildState ( FName ( TEXT ( " Linked " ) ) , EStateTreeStateType : : Linked ) ;
UStateTreeState & SubTree = Root . AddChildState ( FName ( TEXT ( " SubTree " ) ) , EStateTreeStateType : : Subtree ) ;
UStateTreeState & State1 = SubTree . AddChildState ( FName ( TEXT ( " State1 " ) ) ) ;
UStateTreeState & State2 = SubTree . AddChildState ( FName ( TEXT ( " State2 " ) ) ) ;
Linked . LinkedSubtree = SubTree . GetLinkToState ( ) ;
Linked . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : GotoState , & Root ) ;
// SubTask should not complete during the test.
TStateTreeEditorNode < FTestTask_Stand > & SubTask = SubTree . AddTask < FTestTask_Stand > ( FName ( TEXT ( " SubTask " ) ) ) ;
SubTask . GetNode ( ) . TicksToCompletion = 100 ;
TStateTreeEditorNode < FTestTask_Stand > & Task1 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
Task1 . GetNode ( ) . TicksToCompletion = 1 ;
TStateTreeEditorNode < FTestTask_Stand > & Task2 = State2 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task2 " ) ) ) ;
Task2 . GetNode ( ) . TicksToCompletion = 1 ;
// Allow to enter State1 if Task1 instance data TicksToCompletion > 0.
TStateTreeEditorNode < FStateTreeCompareIntCondition > & IntCond1 = State1 . AddEnterCondition < FStateTreeCompareIntCondition > ( EGenericAICheck : : Greater ) ;
EditorData . AddPropertyBinding ( SubTask , TEXT ( " CurrentTick " ) , IntCond1 , TEXT ( " Left " ) ) ;
IntCond1 . GetInstanceData ( ) . Right = 0 ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Active States should be in Root/Linked/SubTree/State2 " , Exec . ExpectInActiveStates ( Root . Name , Linked . Name , SubTree . Name , State2 . Name ) ) ;
AITEST_FALSE ( " StateTree State1 should not be active " , Exec . ExpectInActiveStates ( State1 . Name ) ) ; // Enter condition should prevent to enter State1
AITEST_TRUE ( " StateTree SubTask should enter state " , Exec . Expect ( SubTask . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree Task2 should enter state " , Exec . Expect ( Task2 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
// Task1 completes, and we should enter State1 since the enter condition now passes.
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Active States should be in Root/Linked/SubTree/State1 " , Exec . ExpectInActiveStates ( Root . Name , Linked . Name , SubTree . Name , State1 . Name ) ) ;
AITEST_FALSE ( " StateTree State2 should not be active " , Exec . ExpectInActiveStates ( State2 . Name ) ) ;
AITEST_TRUE ( " StateTree Task1 should enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_SubTreeCondition , " System.StateTree.SubTreeCondition " ) ;
2023-06-12 07:34:11 -04:00
struct FStateTreeTest_SubTree_CascadedSucceeded : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
// - Root [TaskA]
// - LinkedState>SubTreeState -> (F)Failed
// - SubTreeState [TaskB]
// - SubLinkedState>SubSubTreeState -> (S)Failed
// - SubSubTreeState
// - SubSubLeaf [TaskC] -> (S)Succeeded
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & LinkedState = Root . AddChildState ( FName ( TEXT ( " Linked " ) ) , EStateTreeStateType : : Linked ) ;
UStateTreeState & SubTreeState = Root . AddChildState ( FName ( TEXT ( " SubTreeState " ) ) , EStateTreeStateType : : Subtree ) ;
UStateTreeState & SubLinkedState = SubTreeState . AddChildState ( FName ( TEXT ( " SubLinkedState " ) ) , EStateTreeStateType : : Linked ) ;
UStateTreeState & SubSubTreeState = Root . AddChildState ( FName ( TEXT ( " SubSubTreeState " ) ) , EStateTreeStateType : : Subtree ) ;
UStateTreeState & SubSubLeaf = SubSubTreeState . AddChildState ( FName ( TEXT ( " SubSubLeaf " ) ) ) ;
LinkedState . LinkedSubtree = SubTreeState . GetLinkToState ( ) ;
SubLinkedState . LinkedSubtree = SubSubTreeState . GetLinkToState ( ) ;
LinkedState . AddTransition ( EStateTreeTransitionTrigger : : OnStateFailed , EStateTreeTransitionType : : Failed ) ;
SubLinkedState . AddTransition ( EStateTreeTransitionTrigger : : OnStateSucceeded , EStateTreeTransitionType : : Failed ) ;
SubSubLeaf . AddTransition ( EStateTreeTransitionTrigger : : OnStateSucceeded , EStateTreeTransitionType : : Succeeded ) ;
TStateTreeEditorNode < FTestTask_Stand > & TaskA = Root . AddTask < FTestTask_Stand > ( FName ( TEXT ( " TaskA " ) ) ) ;
TStateTreeEditorNode < FTestTask_Stand > & TaskB = SubTreeState . AddTask < FTestTask_Stand > ( FName ( TEXT ( " TaskB " ) ) ) ;
TStateTreeEditorNode < FTestTask_Stand > & TaskC = SubSubLeaf . AddTask < FTestTask_Stand > ( FName ( TEXT ( " TaskC " ) ) ) ;
TaskA . GetNode ( ) . TicksToCompletion = 2 ;
TaskB . GetNode ( ) . TicksToCompletion = 2 ;
TaskC . GetNode ( ) . TicksToCompletion = 1 ; // The deepest task completes first.
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Active States should be in Root/Linked/SubTreeState " , Exec . ExpectInActiveStates ( Root . Name , LinkedState . Name , SubTreeState . Name , SubLinkedState . Name , SubSubTreeState . Name , SubSubLeaf . Name ) ) ;
AITEST_TRUE ( " TaskA,B,C should enter state " , Exec . Expect ( TaskA . GetName ( ) , EnterStateStr ) . Then ( TaskB . GetName ( ) , EnterStateStr ) . Then ( TaskC . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree should be running " , Status = = EStateTreeRunStatus : : Running ) ;
Exec . LogClear ( ) ;
// Subtrees completes, and it completes the whole tree too.
// There's no good way to observe this externally. We switch the return along the way to make sure the transition does not happen directly from the leaf to failed.
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree should be Failed " , Status = = EStateTreeRunStatus : : Failed ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_SubTree_CascadedSucceeded , " System.StateTree.SubTree.CascadedSucceeded " ) ;
2022-09-07 17:12:18 -04:00
struct FStateTreeTest_SharedInstanceData : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
auto & IntCond = Root . AddEnterCondition < FStateTreeTestCondition > ( ) ;
IntCond . GetInstanceData ( ) . Count = 1 ;
auto & Task = Root . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task " ) ) ) ;
Task . GetNode ( ) . TicksToCompletion = 2 ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
// Init, nothing should access the shared data.
constexpr int32 NumConcurrent = 100 ;
FStateTreeTestConditionInstanceData : : GlobalCounter = 0 ;
bool bInitSucceeded = true ;
2022-09-23 20:02:42 -04:00
TArray < FStateTreeInstanceData > InstanceDatas ;
InstanceDatas . SetNum ( NumConcurrent ) ;
for ( int32 Index = 0 ; Index < NumConcurrent ; Index + + )
2022-09-07 17:12:18 -04:00
{
2022-09-23 20:02:42 -04:00
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceDatas [ Index ] ) ;
bInitSucceeded & = Exec . IsValid ( ) ;
2022-09-07 17:12:18 -04:00
}
AITEST_TRUE ( " All StateTree contexts should init " , bInitSucceeded ) ;
AITEST_EQUAL ( " Test condition global counter should be 0 " , ( int32 ) FStateTreeTestConditionInstanceData : : GlobalCounter , 0 ) ;
// Start in parallel
// This should create shared data per thread.
// We expect that ParallelForWithTaskContext() creates a context per thread.
TArray < FStateTreeTestRunContext > RunContexts ;
ParallelForWithTaskContext (
RunContexts ,
2022-09-23 20:02:42 -04:00
InstanceDatas . Num ( ) ,
[ & InstanceDatas , & StateTree ] ( FStateTreeTestRunContext & RunContext , int32 Index )
2022-09-07 17:12:18 -04:00
{
2022-09-23 20:02:42 -04:00
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceDatas [ Index ] ) ;
const EStateTreeRunStatus Status = Exec . Start ( ) ;
2022-09-07 17:12:18 -04:00
if ( Status = = EStateTreeRunStatus : : Running )
{
RunContext . Count + + ;
}
}
) ;
int32 StartTotalRunning = 0 ;
for ( FStateTreeTestRunContext RunContext : RunContexts )
{
StartTotalRunning + = RunContext . Count ;
}
AITEST_EQUAL ( " All StateTree contexts should be running after Start " , StartTotalRunning , NumConcurrent ) ;
2022-09-23 20:02:42 -04:00
AITEST_EQUAL ( " Test condition global counter should equal context count after Start " , ( int32 ) FStateTreeTestConditionInstanceData : : GlobalCounter , InstanceDatas . Num ( ) ) ;
2022-09-07 17:12:18 -04:00
// Tick in parallel
// This should not recreate the data, so FStateTreeTestConditionInstanceData::GlobalCounter should stay as is.
for ( FStateTreeTestRunContext RunContext : RunContexts )
{
RunContext . Count = 0 ;
}
ParallelForWithTaskContext (
RunContexts ,
2022-09-23 20:02:42 -04:00
InstanceDatas . Num ( ) ,
[ & InstanceDatas , & StateTree ] ( FStateTreeTestRunContext & RunContext , int32 Index )
2022-09-07 17:12:18 -04:00
{
2022-09-23 20:02:42 -04:00
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceDatas [ Index ] ) ;
const EStateTreeRunStatus Status = Exec . Tick ( 0.1f ) ;
2022-09-07 17:12:18 -04:00
if ( Status = = EStateTreeRunStatus : : Running )
{
RunContext . Count + + ;
}
}
) ;
int32 TickTotalRunning = 0 ;
for ( FStateTreeTestRunContext RunContext : RunContexts )
{
TickTotalRunning + = RunContext . Count ;
}
AITEST_EQUAL ( " All StateTree contexts should be running after Tick " , TickTotalRunning , NumConcurrent ) ;
2022-09-23 20:02:42 -04:00
AITEST_EQUAL ( " Test condition global counter should equal context count after Tick " , ( int32 ) FStateTreeTestConditionInstanceData : : GlobalCounter , InstanceDatas . Num ( ) ) ;
2022-09-07 17:12:18 -04:00
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_SharedInstanceData , " System.StateTree.SharedInstanceData " ) ;
2022-11-22 08:13:54 -05:00
struct FStateTreeTest_TransitionPriority : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
2023-11-22 04:08:33 -05:00
/*
- Root
- State1 : Task1 - > Succeeded
- State1A : Task1A - > Next
- State1B : Task1B - > Next
- State1C : Task1C
Task1A completed first , transitioning to State1B .
Task1 , Task1B , and Task1C complete at the same time , we should take the transition on the first completed state ( State1 ) .
*/
2022-11-22 08:13:54 -05:00
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & State1 = Root . AddChildState ( FName ( TEXT ( " State1 " ) ) ) ;
UStateTreeState & State1A = State1 . AddChildState ( FName ( TEXT ( " State1A " ) ) ) ;
UStateTreeState & State1B = State1 . AddChildState ( FName ( TEXT ( " State1B " ) ) ) ;
UStateTreeState & State1C = State1 . AddChildState ( FName ( TEXT ( " State1C " ) ) ) ;
auto & Task1 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
Task1 . GetNode ( ) . TicksToCompletion = 2 ;
State1 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : Succeeded ) ;
auto & Task1A = State1A . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1A " ) ) ) ;
Task1A . GetNode ( ) . TicksToCompletion = 1 ;
State1A . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : NextState ) ;
auto & Task1B = State1B . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1B " ) ) ) ;
Task1B . GetNode ( ) . TicksToCompletion = 2 ;
State1B . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : NextState ) ;
auto & Task1C = State1C . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1C " ) ) ) ;
Task1C . GetNode ( ) . TicksToCompletion = 2 ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task1 should enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree Task1A should enter state " , Exec . Expect ( Task1A . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
// Transition from Task1A to Task1B
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task1A should complete " , Exec . Expect ( Task1A . GetName ( ) , StateCompletedStr ) ) ;
AITEST_TRUE ( " StateTree Task1B should enter state " , Exec . Expect ( Task1B . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
// Task1 completes, and we should take State1 transition.
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task1 should complete " , Exec . Expect ( Task1 . GetName ( ) , StateCompletedStr ) ) ;
AITEST_EQUAL ( " Tree execution should stop on success " , Status , EStateTreeRunStatus : : Succeeded ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
2023-02-09 04:20:59 -05:00
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_TransitionPriority , " System.StateTree.Transition.Priority " ) ;
2022-11-22 08:13:54 -05:00
struct FStateTreeTest_TransitionPriorityEnterState : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
2023-01-23 12:48:04 -05:00
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
2022-11-22 08:13:54 -05:00
UStateTreeState & State0 = Root . AddChildState ( FName ( TEXT ( " State0 " ) ) ) ;
UStateTreeState & State1 = Root . AddChildState ( FName ( TEXT ( " State1 " ) ) ) ;
UStateTreeState & State1A = State1 . AddChildState ( FName ( TEXT ( " State1A " ) ) ) ;
UStateTreeState & State2 = Root . AddChildState ( FName ( TEXT ( " State2 " ) ) ) ;
UStateTreeState & State3 = Root . AddChildState ( FName ( TEXT ( " State3 " ) ) ) ;
auto & Task0 = State0 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task0 " ) ) ) ;
State0 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : GotoState , & State1 ) ;
auto & Task1 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
Task1 . GetNode ( ) . EnterStateResult = EStateTreeRunStatus : : Failed ;
State1 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : GotoState , & State2 ) ;
auto & Task1A = State1A . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1A " ) ) ) ;
State1A . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : GotoState , & State3 ) ;
auto & Task2 = State2 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task2 " ) ) ) ;
State2 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : Succeeded ) ;
auto & Task3 = State3 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task3 " ) ) ) ;
State3 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : Succeeded ) ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task0 should enter state " , Exec . Expect ( Task0 . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
// Transition from State0 to State1, it should fail (Task1), and the transition on State1->State2 (and not State1A->State3)
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task0 should complete " , Exec . Expect ( Task0 . GetName ( ) , StateCompletedStr ) ) ;
AITEST_TRUE ( " StateTree Task2 should enter state " , Exec . Expect ( Task2 . GetName ( ) , EnterStateStr ) ) ;
AITEST_FALSE ( " StateTree Task3 should not enter state " , Exec . Expect ( Task3 . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
2023-02-09 04:20:59 -05:00
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_TransitionPriorityEnterState , " System.StateTree.Transition.PriorityEnterState " ) ;
2023-10-17 16:41:19 -04:00
struct FStateTreeTest_TransitionNextSelectableState : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & State0 = Root . AddChildState ( FName ( TEXT ( " State0 " ) ) ) ;
UStateTreeState & State1 = Root . AddChildState ( FName ( TEXT ( " State1 " ) ) ) ;
UStateTreeState & State2 = Root . AddChildState ( FName ( TEXT ( " State2 " ) ) ) ;
auto & EvalA = EditorData . AddEvaluator < FTestEval_A > ( ) ;
EvalA . GetInstanceData ( ) . bBoolA = true ;
auto & Task0 = State0 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task0 " ) ) ) ;
State0 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : NextSelectableState ) ;
// Add Task 1 with Condition that will always fail
auto & Task1 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
auto & BoolCond1 = State1 . AddEnterCondition < FStateTreeCompareBoolCondition > ( ) ;
EditorData . AddPropertyBinding ( EvalA , TEXT ( " bBoolA " ) , BoolCond1 , TEXT ( " bLeft " ) ) ;
BoolCond1 . GetInstanceData ( ) . bRight = ! EvalA . GetInstanceData ( ) . bBoolA ;
// Add Task 2 with Condition that will always succeed
auto & Task2 = State2 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task2 " ) ) ) ;
auto & BoolCond2 = State2 . AddEnterCondition < FStateTreeCompareBoolCondition > ( ) ;
State2 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : Succeeded ) ;
EditorData . AddPropertyBinding ( EvalA , TEXT ( " bBoolA " ) , BoolCond2 , TEXT ( " bLeft " ) ) ;
BoolCond2 . GetInstanceData ( ) . bRight = EvalA . GetInstanceData ( ) . bBoolA ;
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 ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task0 should enter state " , Exec . Expect ( Task0 . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
// Transition from State0 and tries to select State1. It should fail (Task1) and because transition is set to "Next Selectable", it should now select Task 2 and Enter State
Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task0 should complete " , Exec . Expect ( Task0 . GetName ( ) , StateCompletedStr ) ) ;
AITEST_FALSE ( " StateTree Task1 should not enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree Task2 should enter state " , Exec . Expect ( Task2 . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
// Complete Task2
Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task2 should complete " , Exec . Expect ( Task2 . GetName ( ) , StateCompletedStr ) ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_TransitionNextSelectableState , " System.StateTree.Transition.NextSelectableState " ) ;
2024-03-27 11:10:54 -04:00
struct FStateTreeTest_TransitionNextWithParentData : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & State0 = Root . AddChildState ( FName ( TEXT ( " State0 " ) ) ) ;
UStateTreeState & State1 = Root . AddChildState ( FName ( TEXT ( " State1 " ) ) ) ;
UStateTreeState & State1A = State1 . AddChildState ( FName ( TEXT ( " State1A " ) ) ) ;
auto & RootTask = Root . AddTask < FTestTask_B > ( FName ( TEXT ( " RootTask " ) ) ) ;
RootTask . GetInstanceData ( ) . bBoolB = true ;
auto & Task0 = State0 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task0 " ) ) ) ;
State0 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : NextState ) ;
auto & Task1A = State1A . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1A " ) ) ) ;
auto & BoolCond1 = State1A . AddEnterCondition < FStateTreeCompareBoolCondition > ( ) ;
EditorData . AddPropertyBinding ( RootTask , TEXT ( " bBoolB " ) , BoolCond1 , TEXT ( " bLeft " ) ) ;
BoolCond1 . GetInstanceData ( ) . bRight = true ;
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 ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task0 should enter state " , Exec . Expect ( Task0 . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
// Transition from State0 and tries to select State1.
// This tests that data from current shared active states (Root) is available during state selection.
Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task0 should complete " , Exec . Expect ( Task0 . GetName ( ) , StateCompletedStr ) ) ;
AITEST_TRUE ( " StateTree Task1A should enter state " , Exec . Expect ( Task1A . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_TransitionNextWithParentData , " System.StateTree.Transition.NextWithParentData " ) ;
2023-04-04 08:25:50 -04:00
struct FStateTreeTest_LastConditionWithIndent : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & State1 = Root . AddChildState ( FName ( TEXT ( " State1 " ) ) ) ;
auto & Task1 = State1 . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
State1 . AddEnterCondition < FStateTreeTestCondition > ( ) ;
auto & LastCondition = State1 . AddEnterCondition < FStateTreeTestCondition > ( ) ;
// Last condition has Indent
LastCondition . ConditionIndent = 1 ;
State1 . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : Succeeded ) ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task1 should enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
AITEST_FALSE ( " StateTree Task1 should not tick " , Exec . Expect ( Task1 . GetName ( ) , TickStr ) ) ;
Exec . LogClear ( ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task1 should tick, and exit state " , Exec . Expect ( Task1 . GetName ( ) , TickStr ) . Then ( Task1 . GetName ( ) , ExitStateStr ) ) ;
AITEST_TRUE ( " StateTree should be completed " , Status = = EStateTreeRunStatus : : Succeeded ) ;
Exec . LogClear ( ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_FALSE ( " StateTree Task1 should not tick " , Exec . Expect ( Task1 . GetName ( ) , TickStr ) ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_LastConditionWithIndent , " System.StateTree.LastConditionWithIndent " ) ;
2023-02-09 04:20:59 -05:00
struct FStateTreeTest_TransitionGlobalDataView : FAITestBase
{
// Tests that the global eval and task dataviews are kept up to date when transitioning from
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & StateA = Root . AddChildState ( FName ( TEXT ( " A " ) ) ) ;
UStateTreeState & StateB = Root . AddChildState ( FName ( TEXT ( " B " ) ) ) ;
auto & EvalA = EditorData . AddEvaluator < FTestEval_A > ( FName ( TEXT ( " Eval " ) ) ) ;
EvalA . GetInstanceData ( ) . IntA = 42 ;
auto & GlobalTask = EditorData . AddGlobalTask < FTestTask_PrintValue > ( FName ( TEXT ( " Global " ) ) ) ;
GlobalTask . GetInstanceData ( ) . Value = 123 ;
// State A
auto & Task0 = StateA . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task0 " ) ) ) ;
StateA . AddTransition ( EStateTreeTransitionTrigger : : OnStateCompleted , EStateTreeTransitionType : : GotoState , & StateB ) ;
// State B
auto & Task1 = StateB . AddTask < FTestTask_PrintValue > ( FName ( TEXT ( " Task1 " ) ) ) ;
2023-02-10 07:22:48 -05:00
EditorData . AddPropertyBinding ( EvalA , TEXT ( " IntA " ) , Task1 , TEXT ( " Value " ) ) ;
2023-02-09 04:20:59 -05:00
auto & Task2 = StateB . AddTask < FTestTask_PrintValue > ( FName ( TEXT ( " Task2 " ) ) ) ;
2023-02-10 07:22:48 -05:00
EditorData . AddPropertyBinding ( GlobalTask , TEXT ( " Value " ) , Task2 , TEXT ( " Value " ) ) ;
2023-02-09 04:20:59 -05:00
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString EnterState42Str ( TEXT ( " EnterState42 " ) ) ;
const FString EnterState123Str ( TEXT ( " EnterState123 " ) ) ;
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task0 should enter state " , Exec . Expect ( Task0 . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
// Transition from StateA to StateB, Task0 should enter state with evaluator value copied.
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task0 should enter state with value 42 " , Exec . Expect ( Task1 . GetName ( ) , EnterState42Str ) ) ;
AITEST_TRUE ( " StateTree Task1 should enter state with value 123 " , Exec . Expect ( Task2 . GetName ( ) , EnterState123Str ) ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_TransitionGlobalDataView , " System.StateTree.Transition.GlobalDataView " ) ;
2022-11-22 08:13:54 -05:00
2023-03-14 10:40:47 -04:00
struct FStateTreeTest_TransitionDelay : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
const FGameplayTag Tag = UE : : StateTree : : Tests : : FNativeGameplayTags : : Get ( ) . TestTag ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & StateA = Root . AddChildState ( FName ( TEXT ( " A " ) ) ) ;
UStateTreeState & StateB = Root . AddChildState ( FName ( TEXT ( " B " ) ) ) ;
// State A
auto & Task0 = StateA . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task0 " ) ) ) ;
Task0 . GetNode ( ) . TicksToCompletion = 100 ;
FStateTreeTransition & Transition = StateA . AddTransition ( EStateTreeTransitionTrigger : : OnEvent , EStateTreeTransitionType : : GotoState , & StateB ) ;
Transition . bDelayTransition = true ;
Transition . DelayDuration = 0.15f ;
Transition . DelayRandomVariance = 0.0f ;
Transition . EventTag = Tag ;
// State B
auto & Task1 = StateB . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
Task1 . GetNode ( ) . TicksToCompletion = 100 ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task0 should enter state " , Exec . Expect ( Task0 . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
// This should cause delayed transition.
Exec . SendEvent ( Tag ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task0 should tick " , Exec . Expect ( Task0 . GetName ( ) , TickStr ) ) ;
Exec . LogClear ( ) ;
2023-11-22 04:08:33 -05:00
// Should have execution frames
AITEST_TRUE ( " Should have active frames " , InstanceData . GetExecutionState ( ) - > ActiveFrames . Num ( ) > 0 ) ;
2023-03-14 10:40:47 -04:00
// Should have delayed transitions
2023-11-22 04:08:33 -05:00
const int32 NumDelayedTransitions0 = InstanceData . GetExecutionState ( ) - > DelayedTransitions . Num ( ) ;
2023-03-14 10:40:47 -04:00
AITEST_EQUAL ( " Should have a delayed transition " , NumDelayedTransitions0 , 1 ) ;
// Tick and expect a delayed transition.
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task0 should tick " , Exec . Expect ( Task0 . GetName ( ) , TickStr ) ) ;
Exec . LogClear ( ) ;
2023-11-22 04:08:33 -05:00
const int32 NumDelayedTransitions1 = InstanceData . GetExecutionState ( ) - > DelayedTransitions . Num ( ) ;
2023-03-14 10:40:47 -04:00
AITEST_EQUAL ( " Should have a delayed transition " , NumDelayedTransitions1 , 1 ) ;
// Should complete delayed transition.
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task0 should exit state " , Exec . Expect ( Task0 . GetName ( ) , ExitStateStr ) ) ;
AITEST_TRUE ( " StateTree Task1 should enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_TransitionDelay , " System.StateTree.TransitionDelay " ) ;
struct FStateTreeTest_TransitionDelayZero : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
const FGameplayTag Tag = UE : : StateTree : : Tests : : FNativeGameplayTags : : Get ( ) . TestTag ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & StateA = Root . AddChildState ( FName ( TEXT ( " A " ) ) ) ;
UStateTreeState & StateB = Root . AddChildState ( FName ( TEXT ( " B " ) ) ) ;
// State A
auto & Task0 = StateA . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task0 " ) ) ) ;
Task0 . GetNode ( ) . TicksToCompletion = 100 ;
FStateTreeTransition & Transition = StateA . AddTransition ( EStateTreeTransitionTrigger : : OnEvent , EStateTreeTransitionType : : GotoState , & StateB ) ;
Transition . bDelayTransition = true ;
Transition . DelayDuration = 0.0f ;
Transition . DelayRandomVariance = 0.0f ;
Transition . EventTag = Tag ;
// State B
auto & Task1 = StateB . AddTask < FTestTask_Stand > ( FName ( TEXT ( " Task1 " ) ) ) ;
Task1 . GetNode ( ) . TicksToCompletion = 100 ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
const FString StateCompletedStr ( TEXT ( " StateCompleted " ) ) ;
// Start and enter state
Status = Exec . Start ( ) ;
AITEST_TRUE ( " StateTree Task0 should enter state " , Exec . Expect ( Task0 . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
// This should cause delayed transition. Because the time is 0, it should happen immediately.
Exec . SendEvent ( Tag ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_TRUE ( " StateTree Task0 should exit state " , Exec . Expect ( Task0 . GetName ( ) , ExitStateStr ) ) ;
AITEST_TRUE ( " StateTree Task1 should enter state " , Exec . Expect ( Task1 . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_TransitionDelayZero , " System.StateTree.TransitionDelayZero " ) ;
2023-02-10 07:22:48 -05:00
struct FStateTreeTest_PropertyPathOffset : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " StructB.B " ) ) ;
AITEST_TRUE ( " Parsing path should succeeed " , bParseResult ) ;
AITEST_EQUAL ( " Should have 2 path segments " , Path . NumSegments ( ) , 2 ) ;
FString ResolveErrors ;
TArray < FStateTreePropertyPathIndirection > Indirections ;
const bool bResolveResult = Path . ResolveIndirections ( FStateTreeTest_PropertyStruct : : StaticStruct ( ) , Indirections , & ResolveErrors ) ;
AITEST_TRUE ( " Resolve path should succeeed " , bResolveResult ) ;
AITEST_EQUAL ( " Should have no resolve errors " , ResolveErrors . Len ( ) , 0 ) ;
AITEST_EQUAL ( " Should have 2 indirections " , Indirections . Num ( ) , 2 ) ;
AITEST_EQUAL ( " Indirection 0 should be Offset type " , Indirections [ 0 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : Offset ) ;
AITEST_EQUAL ( " Indirection 1 should be Offset type " , Indirections [ 1 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : Offset ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_PropertyPathOffset , " System.StateTree.PropertyPath.Offset " ) ;
struct FStateTreeTest_PropertyPathParseFail : FAITestBase
{
virtual bool InstantTest ( ) override
{
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " " ) ) ; // empty is valid.
AITEST_TRUE ( " Parsing path should succeed " , bParseResult ) ;
}
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " StructB.[0]B " ) ) ;
AITEST_FALSE ( " Parsing path should fail " , bParseResult ) ;
}
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " StructB..NoThere " ) ) ;
AITEST_FALSE ( " Parsing path should fail " , bParseResult ) ;
}
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " . " ) ) ;
AITEST_FALSE ( " Parsing path should fail " , bParseResult ) ;
}
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " StructB..B " ) ) ;
AITEST_FALSE ( " Parsing path should fail " , bParseResult ) ;
}
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_PropertyPathParseFail , " System.StateTree.PropertyPath.ParseFail " ) ;
struct FStateTreeTest_PropertyPathOffsetFail : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " StructB.Q " ) ) ;
AITEST_TRUE ( " Parsing path should succeeed " , bParseResult ) ;
AITEST_EQUAL ( " Should have 2 path segments " , Path . NumSegments ( ) , 2 ) ;
FString ResolveErrors ;
TArray < FStateTreePropertyPathIndirection > Indirections ;
const bool bResolveResult = Path . ResolveIndirections ( FStateTreeTest_PropertyStruct : : StaticStruct ( ) , Indirections , & ResolveErrors ) ;
AITEST_FALSE ( " Resolve path should not succeeed " , bResolveResult ) ;
AITEST_NOT_EQUAL ( " Should have errors " , ResolveErrors . Len ( ) , 0 ) ;
AITEST_EQUAL ( " Should have 0 indirections " , Indirections . Num ( ) , 0 ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_PropertyPathOffsetFail , " System.StateTree.PropertyPath.OffsetFail " ) ;
struct FStateTreeTest_PropertyPathObject : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " InstancedObject.A " ) ) ;
AITEST_TRUE ( " Parsing path should succeeed " , bParseResult ) ;
AITEST_EQUAL ( " Should have 2 path segments " , Path . NumSegments ( ) , 2 ) ;
UStateTreeTest_PropertyObject * Object = NewObject < UStateTreeTest_PropertyObject > ( ) ;
Object - > InstancedObject = NewObject < UStateTreeTest_PropertyObjectInstanced > ( ) ;
2023-06-21 10:25:47 -04:00
const bool bUpdateResult = Path . UpdateSegmentsFromValue ( FStateTreeDataView ( Object ) ) ;
2023-02-10 07:22:48 -05:00
AITEST_TRUE ( " Update instance types should succeeed " , bUpdateResult ) ;
AITEST_TRUE ( " Path segment 0 instance type should be UStateTreeTest_PropertyObjectInstanced " , Path . GetSegment ( 0 ) . GetInstanceStruct ( ) = = UStateTreeTest_PropertyObjectInstanced : : StaticClass ( ) ) ;
AITEST_TRUE ( " Path segment 1 instance type should be nullptr " , Path . GetSegment ( 1 ) . GetInstanceStruct ( ) = = nullptr ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_PropertyPathObject , " System.StateTree.PropertyPath.Object " ) ;
struct FStateTreeTest_PropertyPathWrongObject : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " InstancedObject.B " ) ) ;
AITEST_TRUE ( " Parsing path should succeeed " , bParseResult ) ;
AITEST_EQUAL ( " Should have 2 path segments " , Path . NumSegments ( ) , 2 ) ;
UStateTreeTest_PropertyObject * Object = NewObject < UStateTreeTest_PropertyObject > ( ) ;
Object - > InstancedObject = NewObject < UStateTreeTest_PropertyObjectInstancedWithB > ( ) ;
{
FString ResolveErrors ;
TArray < FStateTreePropertyPathIndirection > Indirections ;
const bool bResolveResult = Path . ResolveIndirectionsWithValue ( FStateTreeDataView ( Object ) , Indirections , & ResolveErrors ) ;
AITEST_TRUE ( " Resolve path should succeeed " , bResolveResult ) ;
AITEST_EQUAL ( " Should have 2 indirections " , Indirections . Num ( ) , 2 ) ;
AITEST_TRUE ( " Object " , Indirections [ 0 ] . GetAccessType ( ) = = EStateTreePropertyAccessType : : ObjectInstance ) ;
AITEST_TRUE ( " Object " , Indirections [ 0 ] . GetContainerStruct ( ) = = Object - > GetClass ( ) ) ;
AITEST_TRUE ( " Object " , Indirections [ 0 ] . GetInstanceStruct ( ) = = UStateTreeTest_PropertyObjectInstancedWithB : : StaticClass ( ) ) ;
AITEST_EQUAL ( " Should not have error " , ResolveErrors . Len ( ) , 0 ) ;
}
Object - > InstancedObject = NewObject < UStateTreeTest_PropertyObjectInstanced > ( ) ;
{
FString ResolveErrors ;
TArray < FStateTreePropertyPathIndirection > Indirections ;
const bool bResolveResult = Path . ResolveIndirectionsWithValue ( FStateTreeDataView ( Object ) , Indirections , & ResolveErrors ) ;
AITEST_FALSE ( " Resolve path should fail " , bResolveResult ) ;
AITEST_EQUAL ( " Should have 0 indirections " , Indirections . Num ( ) , 0 ) ;
AITEST_NOT_EQUAL ( " Should have error " , ResolveErrors . Len ( ) , 0 ) ;
}
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_PropertyPathWrongObject , " System.StateTree.PropertyPath.WrongObject " ) ;
struct FStateTreeTest_PropertyPathArray : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " ArrayOfInts[1] " ) ) ;
AITEST_TRUE ( " Parsing path should succeeed " , bParseResult ) ;
AITEST_EQUAL ( " Should have 1 path segments " , Path . NumSegments ( ) , 1 ) ;
UStateTreeTest_PropertyObject * Object = NewObject < UStateTreeTest_PropertyObject > ( ) ;
Object - > ArrayOfInts . Add ( 42 ) ;
Object - > ArrayOfInts . Add ( 123 ) ;
FString ResolveErrors ;
TArray < FStateTreePropertyPathIndirection > Indirections ;
const bool bResolveResult = Path . ResolveIndirectionsWithValue ( FStateTreeDataView ( Object ) , Indirections , & ResolveErrors ) ;
AITEST_TRUE ( " Resolve path should succeeed " , bResolveResult ) ;
AITEST_EQUAL ( " Should have no resolve errors " , ResolveErrors . Len ( ) , 0 ) ;
AITEST_EQUAL ( " Should have 2 indirections " , Indirections . Num ( ) , 2 ) ;
AITEST_EQUAL ( " Indirection 0 should be IndexArray type " , Indirections [ 0 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : IndexArray ) ;
AITEST_EQUAL ( " Indirection 1 should be Offset type " , Indirections [ 1 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : Offset ) ;
const int32 Value = * reinterpret_cast < const int32 * > ( Indirections [ 1 ] . GetPropertyAddress ( ) ) ;
AITEST_EQUAL ( " Value should be 123 " , Value , 123 ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_PropertyPathArray , " System.StateTree.PropertyPath.Array " ) ;
struct FStateTreeTest_PropertyPathArrayInvalidIndex : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreePropertyPath Path ;
const bool bParseResult = Path . FromString ( TEXT ( " ArrayOfInts[123] " ) ) ;
AITEST_TRUE ( " Parsing path should succeeed " , bParseResult ) ;
AITEST_EQUAL ( " Should have 1 path segments " , Path . NumSegments ( ) , 1 ) ;
UStateTreeTest_PropertyObject * Object = NewObject < UStateTreeTest_PropertyObject > ( ) ;
Object - > ArrayOfInts . Add ( 42 ) ;
Object - > ArrayOfInts . Add ( 123 ) ;
FString ResolveErrors ;
TArray < FStateTreePropertyPathIndirection > Indirections ;
const bool bResolveResult = Path . ResolveIndirectionsWithValue ( FStateTreeDataView ( Object ) , Indirections , & ResolveErrors ) ;
AITEST_FALSE ( " Resolve path should fail " , bResolveResult ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_PropertyPathArrayInvalidIndex , " System.StateTree.PropertyPath.ArrayInvalidIndex " ) ;
struct FStateTreeTest_PropertyPathArrayOfStructs : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreePropertyPath Path1 ;
Path1 . FromString ( TEXT ( " ArrayOfStruct[0].B " ) ) ;
FStateTreePropertyPath Path2 ;
Path2 . FromString ( TEXT ( " ArrayOfStruct[2].StructB.B " ) ) ;
UStateTreeTest_PropertyObject * Object = NewObject < UStateTreeTest_PropertyObject > ( ) ;
Object - > ArrayOfStruct . AddDefaulted_GetRef ( ) . B = 3 ;
Object - > ArrayOfStruct . AddDefaulted ( ) ;
Object - > ArrayOfStruct . AddDefaulted_GetRef ( ) . StructB . B = 42 ;
{
FString ResolveErrors ;
TArray < FStateTreePropertyPathIndirection > Indirections ;
const bool bResolveResult = Path1 . ResolveIndirectionsWithValue ( FStateTreeDataView ( Object ) , Indirections , & ResolveErrors ) ;
AITEST_TRUE ( " Resolve path1 should succeeed " , bResolveResult ) ;
AITEST_EQUAL ( " Should have no resolve errors " , ResolveErrors . Len ( ) , 0 ) ;
AITEST_EQUAL ( " Should have 3 indirections " , Indirections . Num ( ) , 3 ) ;
AITEST_EQUAL ( " Indirection 0 should be ArrayIndex type " , Indirections [ 0 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : IndexArray ) ;
AITEST_EQUAL ( " Indirection 1 should be Offset type " , Indirections [ 1 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : Offset ) ;
AITEST_EQUAL ( " Indirection 2 should be Offset type " , Indirections [ 2 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : Offset ) ;
const int32 Value = * reinterpret_cast < const int32 * > ( Indirections [ 2 ] . GetPropertyAddress ( ) ) ;
AITEST_EQUAL ( " Value should be 3 " , Value , 3 ) ;
}
{
FString ResolveErrors ;
TArray < FStateTreePropertyPathIndirection > Indirections ;
const bool bResolveResult = Path2 . ResolveIndirectionsWithValue ( FStateTreeDataView ( Object ) , Indirections , & ResolveErrors ) ;
AITEST_TRUE ( " Resolve path2 should succeeed " , bResolveResult ) ;
AITEST_EQUAL ( " Should have no resolve errors " , ResolveErrors . Len ( ) , 0 ) ;
AITEST_EQUAL ( " Should have 4 indirections " , Indirections . Num ( ) , 4 ) ;
AITEST_EQUAL ( " Indirection 0 should be ArrayIndex type " , Indirections [ 0 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : IndexArray ) ;
AITEST_EQUAL ( " Indirection 1 should be Offset type " , Indirections [ 1 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : Offset ) ;
AITEST_EQUAL ( " Indirection 2 should be Offset type " , Indirections [ 2 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : Offset ) ;
AITEST_EQUAL ( " Indirection 3 should be Offset type " , Indirections [ 3 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : Offset ) ;
const int32 Value = * reinterpret_cast < const int32 * > ( Indirections [ 3 ] . GetPropertyAddress ( ) ) ;
AITEST_EQUAL ( " Value should be 42 " , Value , 42 ) ;
}
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_PropertyPathArrayOfStructs , " System.StateTree.PropertyPath.ArrayOfStructs " ) ;
struct FStateTreeTest_PropertyPathArrayOfInstancedObjects : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreePropertyPath Path ;
Path . FromString ( TEXT ( " ArrayOfInstancedStructs[0].B " ) ) ;
FStateTreeTest_PropertyStruct Struct ;
Struct . B = 123 ;
UStateTreeTest_PropertyObject * Object = NewObject < UStateTreeTest_PropertyObject > ( ) ;
Object - > ArrayOfInstancedStructs . Emplace ( FConstStructView : : Make ( Struct ) ) ;
2023-06-21 10:25:47 -04:00
const bool bUpdateResult = Path . UpdateSegmentsFromValue ( FStateTreeDataView ( Object ) ) ;
2023-02-10 07:22:48 -05:00
AITEST_TRUE ( " Update instance types should succeeed " , bUpdateResult ) ;
AITEST_EQUAL ( " Should have 2 path segments " , Path . NumSegments ( ) , 2 ) ;
AITEST_TRUE ( " Path segment 0 instance type should be FStateTreeTest_PropertyStruct " , Path . GetSegment ( 0 ) . GetInstanceStruct ( ) = = FStateTreeTest_PropertyStruct : : StaticStruct ( ) ) ;
AITEST_TRUE ( " Path segment 1 instance type should be nullptr " , Path . GetSegment ( 1 ) . GetInstanceStruct ( ) = = nullptr ) ;
{
FString ResolveErrors ;
TArray < FStateTreePropertyPathIndirection > Indirections ;
const bool bResolveResult = Path . ResolveIndirections ( UStateTreeTest_PropertyObject : : StaticClass ( ) , Indirections , & ResolveErrors ) ;
AITEST_TRUE ( " Resolve path should succeeed " , bResolveResult ) ;
AITEST_EQUAL ( " Should have no resolve errors " , ResolveErrors . Len ( ) , 0 ) ;
AITEST_EQUAL ( " Should have 3 indirections " , Indirections . Num ( ) , 3 ) ;
AITEST_EQUAL ( " Indirection 0 should be ArrayIndex type " , Indirections [ 0 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : IndexArray ) ;
AITEST_EQUAL ( " Indirection 1 should be StructInstance type " , Indirections [ 1 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : StructInstance ) ;
AITEST_EQUAL ( " Indirection 2 should be Offset type " , Indirections [ 2 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : Offset ) ;
}
{
FString ResolveErrors ;
TArray < FStateTreePropertyPathIndirection > Indirections ;
const bool bResolveResult = Path . ResolveIndirectionsWithValue ( FStateTreeDataView ( Object ) , Indirections , & ResolveErrors ) ;
AITEST_TRUE ( " Resolve path should succeeed " , bResolveResult ) ;
AITEST_EQUAL ( " Should have no resolve errors " , ResolveErrors . Len ( ) , 0 ) ;
AITEST_EQUAL ( " Should have 3 indirections " , Indirections . Num ( ) , 3 ) ;
AITEST_EQUAL ( " Indirection 0 should be ArrayIndex type " , Indirections [ 0 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : IndexArray ) ;
AITEST_EQUAL ( " Indirection 1 should be StructInstance type " , Indirections [ 1 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : StructInstance ) ;
AITEST_EQUAL ( " Indirection 2 should be Offset type " , Indirections [ 2 ] . GetAccessType ( ) , EStateTreePropertyAccessType : : Offset ) ;
const int32 Value = * reinterpret_cast < const int32 * > ( Indirections [ 2 ] . GetPropertyAddress ( ) ) ;
AITEST_EQUAL ( " Value should be 123 " , Value , 123 ) ;
}
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_PropertyPathArrayOfInstancedObjects , " System.StateTree.PropertyPath.ArrayOfInstancedObjects " ) ;
2023-02-17 04:00:32 -05:00
struct FStateTreeTest_BindingsCompiler : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreeCompilerLog Log ;
FStateTreePropertyBindings Bindings ;
FStateTreePropertyBindingCompiler BindingCompiler ;
const bool bInitResult = BindingCompiler . Init ( Bindings , Log ) ;
AITEST_TRUE ( " Expect init to succeed " , bInitResult ) ;
FStateTreeBindableStructDesc SourceADesc ;
SourceADesc . Name = FName ( TEXT ( " SourceA " ) ) ;
SourceADesc . Struct = TBaseStructure < FStateTreeTest_PropertyCopy > : : Get ( ) ;
SourceADesc . DataSource = EStateTreeBindableStructSource : : Parameter ;
2023-11-22 04:08:33 -05:00
SourceADesc . DataHandle = FStateTreeDataHandle ( EStateTreeDataSourceType : : ContextData , 0 ) ; // Used as index to SourceViews below.
2023-02-17 04:00:32 -05:00
SourceADesc . ID = FGuid : : NewGuid ( ) ;
FStateTreeBindableStructDesc SourceBDesc ;
SourceBDesc . Name = FName ( TEXT ( " SourceB " ) ) ;
SourceBDesc . Struct = TBaseStructure < FStateTreeTest_PropertyCopy > : : Get ( ) ;
SourceBDesc . DataSource = EStateTreeBindableStructSource : : Parameter ;
2023-11-22 04:08:33 -05:00
SourceBDesc . DataHandle = FStateTreeDataHandle ( EStateTreeDataSourceType : : ContextData , 1 ) ; // Used as index to SourceViews below.
2023-02-17 04:00:32 -05:00
SourceBDesc . ID = FGuid : : NewGuid ( ) ;
FStateTreeBindableStructDesc TargetDesc ;
TargetDesc . Name = FName ( TEXT ( " Target " ) ) ;
TargetDesc . Struct = TBaseStructure < FStateTreeTest_PropertyCopy > : : Get ( ) ;
TargetDesc . DataSource = EStateTreeBindableStructSource : : Parameter ;
TargetDesc . ID = FGuid : : NewGuid ( ) ;
const int32 SourceAIndex = BindingCompiler . AddSourceStruct ( SourceADesc ) ;
const int32 SourceBIndex = BindingCompiler . AddSourceStruct ( SourceBDesc ) ;
TArray < FStateTreePropertyPathBinding > PropertyBindings ;
2024-01-11 04:24:45 -05:00
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceBDesc . ID , TEXT ( " Item " ) , TargetDesc . ID , TEXT ( " Array[1] " ) ) ) ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceADesc . ID , TEXT ( " Item.B " ) , TargetDesc . ID , TEXT ( " Array[1].B " ) ) ) ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceADesc . ID , TEXT ( " Array " ) , TargetDesc . ID , TEXT ( " Array " ) ) ) ;
2023-02-17 04:00:32 -05:00
int32 CopyBatchIndex = INDEX_NONE ;
const bool bCompileBatchResult = BindingCompiler . CompileBatch ( TargetDesc , PropertyBindings , CopyBatchIndex ) ;
AITEST_TRUE ( " CompileBatch should succeed " , bCompileBatchResult ) ;
AITEST_NOT_EQUAL ( " CopyBatchIndex should not be INDEX_NONE " , CopyBatchIndex , ( int32 ) INDEX_NONE ) ;
BindingCompiler . Finalize ( ) ;
const bool bResolveResult = Bindings . ResolvePaths ( ) ;
AITEST_TRUE ( " ResolvePaths should succeed " , bResolveResult ) ;
FStateTreeTest_PropertyCopy SourceA ;
SourceA . Item . B = 123 ;
SourceA . Array . AddDefaulted_GetRef ( ) . A = 1 ;
SourceA . Array . AddDefaulted_GetRef ( ) . B = 2 ;
FStateTreeTest_PropertyCopy SourceB ;
SourceB . Item . A = 41 ;
SourceB . Item . B = 42 ;
FStateTreeTest_PropertyCopy Target ;
AITEST_TRUE ( " SourceAIndex should be less than max number of source structs. " , SourceAIndex < Bindings . GetSourceStructNum ( ) ) ;
AITEST_TRUE ( " SourceBIndex should be less than max number of source structs. " , SourceBIndex < Bindings . GetSourceStructNum ( ) ) ;
TArray < FStateTreeDataView > SourceViews ;
SourceViews . SetNum ( Bindings . GetSourceStructNum ( ) ) ;
2023-08-31 08:03:46 -04:00
SourceViews [ SourceAIndex ] = FStateTreeDataView ( FStructView : : Make ( SourceA ) ) ;
SourceViews [ SourceBIndex ] = FStateTreeDataView ( FStructView : : Make ( SourceB ) ) ;
FStateTreeDataView TargetView ( FStructView : : Make ( Target ) ) ;
2023-11-22 04:08:33 -05:00
bool bCopyResult = true ;
for ( const FStateTreePropertyCopy & Copy : Bindings . GetBatchCopies ( FStateTreeIndex16 ( CopyBatchIndex ) ) )
{
bCopyResult & = Bindings . CopyProperty ( Copy , SourceViews [ Copy . SourceDataHandle . GetIndex ( ) ] , TargetView ) ;
}
2023-02-17 04:00:32 -05:00
AITEST_TRUE ( " CopyTo should succeed " , bCopyResult ) ;
// Due to binding sorting, we expect them to executed in this order (sorted based on target access, earliest to latest)
// SourceA.Array -> Target.Array
// SourceB.Item -> Target.Array[1]
// SourceA.Item.B -> Target.Array[1].B
AITEST_EQUAL ( " Expect TargetArray to be copied from SourceA " , Target . Array . Num ( ) , SourceA . Array . Num ( ) ) ;
AITEST_EQUAL ( " Expect Target.Array[0].A copied from SourceA.Array[0].A " , Target . Array [ 0 ] . A , SourceA . Array [ 0 ] . A ) ;
AITEST_EQUAL ( " Expect Target.Array[0].B copied from SourceA.Array[0].B " , Target . Array [ 0 ] . B , SourceA . Array [ 0 ] . B ) ;
AITEST_EQUAL ( " Expect Target.Array[1].A copied from SourceB.Item.A " , Target . Array [ 1 ] . A , SourceB . Item . A ) ;
AITEST_EQUAL ( " Expect Target.Array[1].B copied from SourceA.Item.B " , Target . Array [ 1 ] . B , SourceA . Item . B ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_BindingsCompiler , " System.StateTree.BindingsCompiler " ) ;
2023-08-31 08:03:46 -04:00
struct FStateTreeTest_CopyObjects : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreeCompilerLog Log ;
FStateTreePropertyBindings Bindings ;
FStateTreePropertyBindingCompiler BindingCompiler ;
const bool bInitResult = BindingCompiler . Init ( Bindings , Log ) ;
AITEST_TRUE ( " Expect init to succeed " , bInitResult ) ;
FStateTreeBindableStructDesc SourceDesc ;
SourceDesc . Name = FName ( TEXT ( " Source " ) ) ;
SourceDesc . Struct = TBaseStructure < FStateTreeTest_PropertyCopyObjects > : : Get ( ) ;
SourceDesc . DataSource = EStateTreeBindableStructSource : : Parameter ;
2023-11-22 04:08:33 -05:00
SourceDesc . DataHandle = FStateTreeDataHandle ( EStateTreeDataSourceType : : ContextData , 0 ) ; // Used as index to SourceViews below.
2023-08-31 08:03:46 -04:00
SourceDesc . ID = FGuid : : NewGuid ( ) ;
FStateTreeBindableStructDesc TargetADesc ;
TargetADesc . Name = FName ( TEXT ( " TargetA " ) ) ;
TargetADesc . Struct = TBaseStructure < FStateTreeTest_PropertyCopyObjects > : : Get ( ) ;
TargetADesc . DataSource = EStateTreeBindableStructSource : : Parameter ;
TargetADesc . ID = FGuid : : NewGuid ( ) ;
FStateTreeBindableStructDesc TargetBDesc ;
TargetBDesc . Name = FName ( TEXT ( " TargetB " ) ) ;
TargetBDesc . Struct = TBaseStructure < FStateTreeTest_PropertyCopyObjects > : : Get ( ) ;
TargetBDesc . DataSource = EStateTreeBindableStructSource : : Parameter ;
TargetBDesc . ID = FGuid : : NewGuid ( ) ;
const int32 SourceIndex = BindingCompiler . AddSourceStruct ( SourceDesc ) ;
TArray < FStateTreePropertyPathBinding > PropertyBindings ;
// One-to-one copy from source to target A
2024-01-11 04:24:45 -05:00
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " Object " ) , TargetADesc . ID , TEXT ( " Object " ) ) ) ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " SoftObject " ) , TargetADesc . ID , TEXT ( " SoftObject " ) ) ) ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " Class " ) , TargetADesc . ID , TEXT ( " Class " ) ) ) ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " SoftClass " ) , TargetADesc . ID , TEXT ( " SoftClass " ) ) ) ;
2023-08-31 08:03:46 -04:00
// Cross copy from source to target B
2024-01-11 04:24:45 -05:00
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " SoftObject " ) , TargetBDesc . ID , TEXT ( " Object " ) ) ) ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " Object " ) , TargetBDesc . ID , TEXT ( " SoftObject " ) ) ) ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " SoftClass " ) , TargetBDesc . ID , TEXT ( " Class " ) ) ) ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " Class " ) , TargetBDesc . ID , TEXT ( " SoftClass " ) ) ) ;
2023-08-31 08:03:46 -04:00
int32 TargetACopyBatchIndex = INDEX_NONE ;
const bool bCompileBatchResultA = BindingCompiler . CompileBatch ( TargetADesc , PropertyBindings , TargetACopyBatchIndex ) ;
AITEST_TRUE ( " CompileBatchResultA should succeed " , bCompileBatchResultA ) ;
AITEST_NOT_EQUAL ( " TargetACopyBatchIndex should not be INDEX_NONE " , TargetACopyBatchIndex , ( int32 ) INDEX_NONE ) ;
int32 TargetBCopyBatchIndex = INDEX_NONE ;
const bool bCompileBatchResultB = BindingCompiler . CompileBatch ( TargetBDesc , PropertyBindings , TargetBCopyBatchIndex ) ;
AITEST_TRUE ( " CompileBatchResultB should succeed " , bCompileBatchResultB ) ;
AITEST_NOT_EQUAL ( " TargetBCopyBatchIndex should not be INDEX_NONE " , TargetBCopyBatchIndex , ( int32 ) INDEX_NONE ) ;
BindingCompiler . Finalize ( ) ;
const bool bResolveResult = Bindings . ResolvePaths ( ) ;
AITEST_TRUE ( " ResolvePaths should succeed " , bResolveResult ) ;
UStateTreeTest_PropertyObject * ObjectA = NewObject < UStateTreeTest_PropertyObject > ( ) ;
UStateTreeTest_PropertyObject2 * ObjectB = NewObject < UStateTreeTest_PropertyObject2 > ( ) ;
FStateTreeTest_PropertyCopyObjects Source ;
Source . Object = ObjectA ;
Source . SoftObject = ObjectB ;
Source . Class = UStateTreeTest_PropertyObject : : StaticClass ( ) ;
Source . SoftClass = UStateTreeTest_PropertyObject : : StaticClass ( ) ;
AITEST_TRUE ( " SourceIndex should be less than max number of source structs. " , SourceIndex < Bindings . GetSourceStructNum ( ) ) ;
TArray < FStateTreeDataView > SourceViews ;
SourceViews . SetNum ( Bindings . GetSourceStructNum ( ) ) ;
SourceViews [ SourceIndex ] = FStateTreeDataView ( FStructView : : Make ( Source ) ) ;
2023-11-22 04:08:33 -05:00
2023-08-31 08:03:46 -04:00
FStateTreeTest_PropertyCopyObjects TargetA ;
2023-11-22 04:08:33 -05:00
bool bCopyResultA = true ;
for ( const FStateTreePropertyCopy & Copy : Bindings . GetBatchCopies ( FStateTreeIndex16 ( TargetACopyBatchIndex ) ) )
{
bCopyResultA & = Bindings . CopyProperty ( Copy , SourceViews [ Copy . SourceDataHandle . GetIndex ( ) ] , FStructView : : Make ( TargetA ) ) ;
}
2023-08-31 08:03:46 -04:00
AITEST_TRUE ( " CopyTo should succeed " , bCopyResultA ) ;
AITEST_TRUE ( " Expect TargetA.Object == Source.Object " , TargetA . Object = = Source . Object ) ;
AITEST_TRUE ( " Expect TargetA.SoftObject == Source.SoftObject " , TargetA . SoftObject = = Source . SoftObject ) ;
AITEST_TRUE ( " Expect TargetA.Class == Source.Class " , TargetA . Class = = Source . Class ) ;
AITEST_TRUE ( " Expect TargetA.SoftClass == Source.SoftClass " , TargetA . SoftClass = = Source . SoftClass ) ;
// Copying to TargetB should not affect TargetA
TargetA . Object = nullptr ;
FStateTreeTest_PropertyCopyObjects TargetB ;
2023-11-22 04:08:33 -05:00
bool bCopyResultB = true ;
for ( const FStateTreePropertyCopy & Copy : Bindings . GetBatchCopies ( FStateTreeIndex16 ( TargetBCopyBatchIndex ) ) )
{
bCopyResultB & = Bindings . CopyProperty ( Copy , SourceViews [ Copy . SourceDataHandle . GetIndex ( ) ] , FStructView : : Make ( TargetB ) ) ;
}
2023-08-31 08:03:46 -04:00
AITEST_TRUE ( " CopyTo should succeed " , bCopyResultB ) ;
AITEST_TRUE ( " Expect TargetB.Object == Source.SoftObject " , TSoftObjectPtr < UObject > ( TargetB . Object ) = = Source . SoftObject ) ;
AITEST_TRUE ( " Expect TargetB.SoftObject == Source.Object " , TargetB . SoftObject = = TSoftObjectPtr < UObject > ( Source . Object ) ) ;
AITEST_TRUE ( " Expect TargetB.Class == Source.SoftClass " , TSoftClassPtr < UObject > ( TargetB . Class ) = = Source . SoftClass ) ;
AITEST_TRUE ( " Expect TargetB.SoftClass == Source.Class " , TargetB . SoftClass = = TSoftClassPtr < UObject > ( Source . Class ) ) ;
AITEST_TRUE ( " Expect TargetA.Object == nullptr after copy of TargetB " , TargetA . Object = = nullptr ) ;
// Collect ObjectA and ObjectB, soft object paths should still copy ok.
ObjectA = nullptr ;
ObjectB = nullptr ;
Source . Object = nullptr ;
CollectGarbage ( GARBAGE_COLLECTION_KEEPFLAGS ) ;
FStateTreeTest_PropertyCopyObjects TargetC ;
2023-11-22 04:08:33 -05:00
bool bCopyResultC = true ;
for ( const FStateTreePropertyCopy & Copy : Bindings . GetBatchCopies ( FStateTreeIndex16 ( TargetACopyBatchIndex ) ) )
{
bCopyResultB & = Bindings . CopyProperty ( Copy , SourceViews [ Copy . SourceDataHandle . GetIndex ( ) ] , FStructView : : Make ( TargetC ) ) ;
}
2023-08-31 08:03:46 -04:00
AITEST_TRUE ( " CopyTo should succeed " , bCopyResultC ) ;
AITEST_TRUE ( " Expect TargetC.SoftObject == Source.SoftObject after GC " , TargetC . SoftObject = = Source . SoftObject ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_CopyObjects , " System.StateTree.CopyObjects " ) ;
2024-01-11 04:24:45 -05:00
struct FStateTreeTest_References : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreeCompilerLog Log ;
FStateTreePropertyBindings Bindings ;
FStateTreePropertyBindingCompiler BindingCompiler ;
const bool bInitResult = BindingCompiler . Init ( Bindings , Log ) ;
AITEST_TRUE ( " Expect init to succeed " , bInitResult ) ;
FStateTreeBindableStructDesc SourceDesc ;
SourceDesc . Name = FName ( TEXT ( " Source " ) ) ;
SourceDesc . Struct = TBaseStructure < FStateTreeTest_PropertyRefSourceStruct > : : Get ( ) ;
SourceDesc . DataSource = EStateTreeBindableStructSource : : Parameter ;
SourceDesc . DataHandle = FStateTreeDataHandle ( EStateTreeDataSourceType : : ContextData , 0 ) ;
SourceDesc . ID = FGuid : : NewGuid ( ) ;
BindingCompiler . AddSourceStruct ( SourceDesc ) ;
FStateTreeBindableStructDesc TargetDesc ;
TargetDesc . Name = FName ( TEXT ( " Target " ) ) ;
TargetDesc . Struct = TBaseStructure < FStateTreeTest_PropertyRefTargetStruct > : : Get ( ) ;
TargetDesc . DataSource = EStateTreeBindableStructSource : : Parameter ;
TargetDesc . ID = FGuid : : NewGuid ( ) ;
TArray < FStateTreePropertyPathBinding > PropertyBindings ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " Item " ) , TargetDesc . ID , TEXT ( " RefToStruct " ) ) ) ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " Item.A " ) , TargetDesc . ID , TEXT ( " RefToInt " ) ) ) ;
PropertyBindings . Add ( UE : : StateTree : : Tests : : MakeBinding ( SourceDesc . ID , TEXT ( " Array " ) , TargetDesc . ID , TEXT ( " RefToStructArray " ) ) ) ;
FStateTreeTest_PropertyRefTargetStruct Target ;
FStateTreeDataView TargetView ( FStructView : : Make ( Target ) ) ;
const bool bCompileReferencesResult = BindingCompiler . CompileReferences ( TargetDesc , PropertyBindings , TargetView ) ;
AITEST_TRUE ( " CompileReferences should succeed " , bCompileReferencesResult ) ;
BindingCompiler . Finalize ( ) ;
const bool bResolveResult = Bindings . ResolvePaths ( ) ;
AITEST_TRUE ( " ResolvePaths should succeed " , bResolveResult ) ;
FStateTreeTest_PropertyRefSourceStruct Source ;
FStateTreeDataView SourceView = FStateTreeDataView ( FStructView : : Make ( Source ) ) ;
{
const FStateTreePropertyAccess * PropertyAccess = Bindings . GetPropertyAccess ( Target . RefToStruct ) ;
AITEST_NOT_NULL ( " GetPropertyAccess should succeed " , PropertyAccess ) ;
FStateTreeTest_PropertyStruct * Reference = Bindings . GetMutablePropertyPtr < FStateTreeTest_PropertyStruct > ( SourceView , * PropertyAccess ) ;
AITEST_EQUAL ( " Expect RefToStruct to point to SourceA.Item " , Reference , & Source . Item ) ;
}
{
const FStateTreePropertyAccess * PropertyAccess = Bindings . GetPropertyAccess ( Target . RefToInt ) ;
AITEST_NOT_NULL ( " GetPropertyAccess should succeed " , PropertyAccess ) ;
int32 * Reference = Bindings . GetMutablePropertyPtr < int32 > ( SourceView , * PropertyAccess ) ;
AITEST_EQUAL ( " Expect RefToInt to point to SourceA.Item.A " , Reference , & Source . Item ) ;
}
{
const FStateTreePropertyAccess * PropertyAccess = Bindings . GetPropertyAccess ( Target . RefToStructArray ) ;
AITEST_NOT_NULL ( " GetPropertyAccess should succeed " , PropertyAccess ) ;
TArray < FStateTreeTest_PropertyStruct > * Reference = Bindings . GetMutablePropertyPtr < TArray < FStateTreeTest_PropertyStruct > > ( SourceView , * PropertyAccess ) ;
AITEST_EQUAL ( " Expect RefToStructArray to point to SourceA.Array " , Reference , & Source . Array ) ;
}
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_References , " System.StateTree.References " ) ;
struct FStateTreeTest_ReferencesConstness : FAITestBase
{
virtual bool InstantTest ( ) override
{
FStateTreeCompilerLog Log ;
FStateTreePropertyBindings Bindings ;
FStateTreePropertyBindingCompiler BindingCompiler ;
const bool bInitResult = BindingCompiler . Init ( Bindings , Log ) ;
AITEST_TRUE ( " Expect init to succeed " , bInitResult ) ;
FStateTreeBindableStructDesc SourceAsTaskDesc ;
SourceAsTaskDesc . Name = FName ( TEXT ( " SourceTask " ) ) ;
SourceAsTaskDesc . Struct = TBaseStructure < FStateTreeTest_PropertyRefSourceStruct > : : Get ( ) ;
SourceAsTaskDesc . DataSource = EStateTreeBindableStructSource : : Task ;
SourceAsTaskDesc . DataHandle = FStateTreeDataHandle ( EStateTreeDataSourceType : : ContextData , 0 ) ;
SourceAsTaskDesc . ID = FGuid : : NewGuid ( ) ;
BindingCompiler . AddSourceStruct ( SourceAsTaskDesc ) ;
FStateTreeBindableStructDesc SourceAsContextDesc ;
SourceAsContextDesc . Name = FName ( TEXT ( " SourceContext " ) ) ;
SourceAsContextDesc . Struct = TBaseStructure < FStateTreeTest_PropertyRefSourceStruct > : : Get ( ) ;
SourceAsContextDesc . DataSource = EStateTreeBindableStructSource : : Context ;
SourceAsContextDesc . DataHandle = FStateTreeDataHandle ( EStateTreeDataSourceType : : ContextData , 0 ) ;
SourceAsContextDesc . ID = FGuid : : NewGuid ( ) ;
BindingCompiler . AddSourceStruct ( SourceAsContextDesc ) ;
FStateTreeBindableStructDesc TargetDesc ;
TargetDesc . Name = FName ( TEXT ( " Target " ) ) ;
TargetDesc . Struct = TBaseStructure < FStateTreeTest_PropertyRefTargetStruct > : : Get ( ) ;
TargetDesc . DataSource = EStateTreeBindableStructSource : : Parameter ;
TargetDesc . ID = FGuid : : NewGuid ( ) ;
FStateTreePropertyPathBinding TaskPropertyBinding = UE : : StateTree : : Tests : : MakeBinding ( SourceAsTaskDesc . ID , TEXT ( " Item " ) , TargetDesc . ID , TEXT ( " RefToStruct " ) ) ;
FStateTreePropertyPathBinding TaskOutputPropertyBinding = UE : : StateTree : : Tests : : MakeBinding ( SourceAsTaskDesc . ID , TEXT ( " OutputItem " ) , TargetDesc . ID , TEXT ( " RefToStruct " ) ) ;
FStateTreePropertyPathBinding ContextPropertyBinding = UE : : StateTree : : Tests : : MakeBinding ( SourceAsTaskDesc . ID , TEXT ( " Item " ) , TargetDesc . ID , TEXT ( " RefToStruct " ) ) ;
FStateTreePropertyPathBinding ContextOutputPropertyBinding = UE : : StateTree : : Tests : : MakeBinding ( SourceAsTaskDesc . ID , TEXT ( " Item " ) , TargetDesc . ID , TEXT ( " RefToStruct " ) ) ;
FStateTreeTest_PropertyRefTargetStruct Target ;
FStateTreeDataView TargetView ( FStructView : : Make ( Target ) ) ;
{
const bool bCompileReferenceResult = BindingCompiler . CompileReferences ( TargetDesc , { TaskPropertyBinding } , TargetView ) ;
AITEST_FALSE ( " CompileReferences should fail " , bCompileReferenceResult ) ;
}
{
const bool bCompileReferenceResult = BindingCompiler . CompileReferences ( TargetDesc , { TaskOutputPropertyBinding } , TargetView ) ;
AITEST_TRUE ( " CompileReferences should succeed " , bCompileReferenceResult ) ;
}
{
const bool bCompileReferenceResult = BindingCompiler . CompileReferences ( TargetDesc , { ContextPropertyBinding } , TargetView ) ;
AITEST_FALSE ( " CompileReferences should fail " , bCompileReferenceResult ) ;
}
{
const bool bCompileReferenceResult = BindingCompiler . CompileReferences ( TargetDesc , { ContextOutputPropertyBinding } , TargetView ) ;
AITEST_FALSE ( " CompileReferences should fail " , bCompileReferenceResult ) ;
}
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_ReferencesConstness , " System.StateTree.ReferencesConstness " ) ;
2023-04-12 07:59:16 -04:00
struct FStateTreeTest_FollowTransitions : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
EditorData . RootParameters . Parameters . AddProperty ( FName ( TEXT ( " Int " ) ) , EPropertyBagPropertyType : : Int32 ) ;
EditorData . RootParameters . Parameters . SetValueInt32 ( FName ( TEXT ( " Int " ) ) , 1 ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & StateTrans = Root . AddChildState ( FName ( TEXT ( " Trans " ) ) ) ;
UStateTreeState & StateA = Root . AddChildState ( FName ( TEXT ( " A " ) ) ) ;
UStateTreeState & StateB = Root . AddChildState ( FName ( TEXT ( " B " ) ) ) ;
UStateTreeState & StateC = Root . AddChildState ( FName ( TEXT ( " C " ) ) ) ;
// Root
// Trans
{
StateTrans . SelectionBehavior = EStateTreeStateSelectionBehavior : : TryFollowTransitions ;
{
// This transition should be skipped due to the condition
FStateTreeTransition & TransA = StateTrans . AddTransition ( EStateTreeTransitionTrigger : : OnTick , EStateTreeTransitionType : : GotoState , & StateA ) ;
TStateTreeEditorNode < FStateTreeCompareIntCondition > & TransIntCond = TransA . AddCondition < FStateTreeCompareIntCondition > ( EGenericAICheck : : Equal ) ;
TransIntCond . GetInstanceData ( ) . Right = 0 ;
EditorData . AddPropertyBinding (
FStateTreePropertyPath ( EditorData . RootParameters . ID , TEXT ( " Int " ) ) ,
FStateTreePropertyPath ( TransIntCond . ID , TEXT ( " Left " ) ) ) ;
}
{
// This transition leads to selection, but will be overridden.
FStateTreeTransition & TransB = StateTrans . AddTransition ( EStateTreeTransitionTrigger : : OnTick , EStateTreeTransitionType : : GotoState , & StateB ) ;
TransB . Priority = EStateTreeTransitionPriority : : Normal ;
TStateTreeEditorNode < FStateTreeCompareIntCondition > & TransIntCond = TransB . AddCondition < FStateTreeCompareIntCondition > ( EGenericAICheck : : Equal ) ;
TransIntCond . GetInstanceData ( ) . Right = 1 ;
EditorData . AddPropertyBinding (
FStateTreePropertyPath ( EditorData . RootParameters . ID , TEXT ( " Int " ) ) ,
FStateTreePropertyPath ( TransIntCond . ID , TEXT ( " Left " ) ) ) ;
}
{
// This transition is selected, should override previous one due to priority.
FStateTreeTransition & TransC = StateTrans . AddTransition ( EStateTreeTransitionTrigger : : OnTick , EStateTreeTransitionType : : GotoState , & StateC ) ;
TransC . Priority = EStateTreeTransitionPriority : : High ;
TStateTreeEditorNode < FStateTreeCompareIntCondition > & TransIntCond = TransC . AddCondition < FStateTreeCompareIntCondition > ( EGenericAICheck : : Equal ) ;
TransIntCond . GetInstanceData ( ) . Right = 1 ;
EditorData . AddPropertyBinding (
FStateTreePropertyPath ( EditorData . RootParameters . ID , TEXT ( " Int " ) ) ,
FStateTreePropertyPath ( TransIntCond . ID , TEXT ( " Left " ) ) ) ;
}
}
auto & TaskA = StateA . AddTask < FTestTask_Stand > ( FName ( TEXT ( " TaskA " ) ) ) ;
auto & TaskB = StateB . AddTask < FTestTask_Stand > ( FName ( TEXT ( " TaskB " ) ) ) ;
auto & TaskC = StateC . AddTask < FTestTask_Stand > ( FName ( TEXT ( " TaskC " ) ) ) ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
Status = Exec . Start ( ) ;
AITEST_FALSE ( " StateTree TaskA should not enter state " , Exec . Expect ( TaskA . GetName ( ) , EnterStateStr ) ) ;
AITEST_FALSE ( " StateTree TaskB should not enter state " , Exec . Expect ( TaskB . GetName ( ) , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree TaskC should enter state " , Exec . Expect ( TaskC . GetName ( ) , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_FollowTransitions , " System.StateTree.FollowTransitions " ) ;
struct FStateTreeTest_InfiniteLoop : FAITestBase
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
EditorData . RootParameters . Parameters . AddProperty ( FName ( TEXT ( " Int " ) ) , EPropertyBagPropertyType : : Int32 ) ;
EditorData . RootParameters . Parameters . SetValueInt32 ( FName ( TEXT ( " Int " ) ) , 1 ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & StateA = Root . AddChildState ( FName ( TEXT ( " A " ) ) ) ;
UStateTreeState & StateB = StateA . AddChildState ( FName ( TEXT ( " B " ) ) ) ;
// Root
// State A
{
StateA . SelectionBehavior = EStateTreeStateSelectionBehavior : : TryFollowTransitions ;
{
// A -> B
FStateTreeTransition & Trans = StateA . AddTransition ( EStateTreeTransitionTrigger : : OnTick , EStateTreeTransitionType : : GotoState , & StateB ) ;
TStateTreeEditorNode < FStateTreeCompareIntCondition > & TransIntCond = Trans . AddCondition < FStateTreeCompareIntCondition > ( EGenericAICheck : : Equal ) ;
TransIntCond . GetInstanceData ( ) . Right = 1 ;
EditorData . AddPropertyBinding (
FStateTreePropertyPath ( EditorData . RootParameters . ID , TEXT ( " Int " ) ) ,
FStateTreePropertyPath ( TransIntCond . ID , TEXT ( " Left " ) ) ) ;
}
}
// State B
{
StateB . SelectionBehavior = EStateTreeStateSelectionBehavior : : TryFollowTransitions ;
{
// B -> A
FStateTreeTransition & Trans = StateB . AddTransition ( EStateTreeTransitionTrigger : : OnTick , EStateTreeTransitionType : : GotoState , & StateA ) ;
TStateTreeEditorNode < FStateTreeCompareIntCondition > & TransIntCond = Trans . AddCondition < FStateTreeCompareIntCondition > ( EGenericAICheck : : Equal ) ;
TransIntCond . GetInstanceData ( ) . Right = 1 ;
EditorData . AddPropertyBinding (
FStateTreePropertyPath ( EditorData . RootParameters . ID , TEXT ( " Int " ) ) ,
FStateTreePropertyPath ( TransIntCond . ID , TEXT ( " Left " ) ) ) ;
}
}
auto & TaskA = StateA . AddTask < FTestTask_Stand > ( FName ( TEXT ( " TaskA " ) ) ) ;
auto & TaskB = StateB . AddTask < FTestTask_Stand > ( FName ( TEXT ( " TaskB " ) ) ) ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
GetTestRunner ( ) . AddExpectedError ( TEXT ( " Loop detected when trying to select state " ) , EAutomationExpectedErrorFlags : : Contains , 1 ) ;
GetTestRunner ( ) . AddExpectedError ( TEXT ( " Failed to select initial state " ) , EAutomationExpectedErrorFlags : : Contains , 1 ) ;
2023-11-22 04:08:33 -05:00
2023-04-12 07:59:16 -04:00
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Start should fail " , Status , EStateTreeRunStatus : : Failed ) ;
Exec . LogClear ( ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_InfiniteLoop , " System.StateTree.InfiniteLoop " ) ;
2023-02-17 04:00:32 -05:00
2023-06-05 06:33:07 -04:00
//
// The stop tests test how the combinations of execution path to stop the tree are reported on ExitState() transition.
//
struct FStateTreeTest_Stop : FAITestBase
{
UStateTree & SetupTree ( )
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & StateA = Root . AddChildState ( FName ( TEXT ( " A " ) ) ) ;
TStateTreeEditorNode < FTestTask_Stand > & TaskA = StateA . AddTask < FTestTask_Stand > ( TaskAName ) ;
TStateTreeEditorNode < FTestTask_Stand > & GlobalTask = EditorData . AddGlobalTask < FTestTask_Stand > ( GlobalTaskName ) ;
// Transition success
StateA . AddTransition ( EStateTreeTransitionTrigger : : OnStateSucceeded , EStateTreeTransitionType : : Succeeded ) ;
StateA . AddTransition ( EStateTreeTransitionTrigger : : OnStateFailed , EStateTreeTransitionType : : Failed ) ;
GlobalTask . GetNode ( ) . TicksToCompletion = GlobalTaskTicks ;
GlobalTask . GetNode ( ) . TickCompletionResult = GlobalTaskStatus ;
GlobalTask . GetNode ( ) . EnterStateResult = GlobalTaskEnterStatus ;
TaskA . GetNode ( ) . TicksToCompletion = NormalTaskTicks ;
TaskA . GetNode ( ) . TickCompletionResult = NormalTaskStatus ;
TaskA . GetNode ( ) . EnterStateResult = NormalTaskEnterStatus ;
return StateTree ;
}
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = SetupTree ( ) ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Start should be running " , Status , EStateTreeRunStatus : : Running ) ;
AITEST_TRUE ( " StateTree GlobalTask should enter state " , Exec . Expect ( GlobalTaskName , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree TaskA should enter state " , Exec . Expect ( TaskAName , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_EQUAL ( " Tree should end expectedly " , Status , ExpectedStatusAfterTick ) ;
AITEST_TRUE ( " StateTree GlobalTask should get exit state expectedly " , Exec . Expect ( GlobalTaskName , ExpectedExitStatusStr ) ) ;
AITEST_TRUE ( " StateTree TaskA should get exit state expectedly " , Exec . Expect ( TaskAName , ExpectedExitStatusStr ) ) ;
Exec . LogClear ( ) ;
return true ;
}
protected :
const FName GlobalTaskName = FName ( TEXT ( " GlobalTask " ) ) ;
const FName TaskAName = FName ( TEXT ( " TaskA " ) ) ;
EStateTreeRunStatus NormalTaskStatus = EStateTreeRunStatus : : Succeeded ;
EStateTreeRunStatus NormalTaskEnterStatus = EStateTreeRunStatus : : Running ;
int32 NormalTaskTicks = 1 ;
EStateTreeRunStatus GlobalTaskStatus = EStateTreeRunStatus : : Succeeded ;
EStateTreeRunStatus GlobalTaskEnterStatus = EStateTreeRunStatus : : Running ;
int32 GlobalTaskTicks = 1 ;
EStateTreeRunStatus ExpectedStatusAfterTick = EStateTreeRunStatus : : Succeeded ;
FString ExpectedExitStatusStr = TEXT ( " ExitSucceeded " ) ;
} ;
struct FStateTreeTest_Stop_NormalSucceeded : FStateTreeTest_Stop
{
virtual bool SetUp ( ) override
{
// Normal task completes as succeeded.
NormalTaskStatus = EStateTreeRunStatus : : Succeeded ;
NormalTaskTicks = 1 ;
// Global task completes later
GlobalTaskTicks = 2 ;
// Tree should complete as succeeded.
ExpectedStatusAfterTick = EStateTreeRunStatus : : Succeeded ;
// Tasks should have Transition.CurrentRunStatus as succeeded
ExpectedExitStatusStr = TEXT ( " ExitSucceeded " ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Stop_NormalSucceeded , " System.StateTree.Stop.NormalSucceeded " ) ;
struct FStateTreeTest_Stop_NormalFailed : FStateTreeTest_Stop
{
virtual bool SetUp ( ) override
{
// Normal task completes as failed.
NormalTaskStatus = EStateTreeRunStatus : : Failed ;
NormalTaskTicks = 1 ;
// Global task completes later.
GlobalTaskTicks = 2 ;
// Tree should complete as failed.
ExpectedStatusAfterTick = EStateTreeRunStatus : : Failed ;
// Tasks should have Transition.CurrentRunStatus as failed.
ExpectedExitStatusStr = TEXT ( " ExitFailed " ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Stop_NormalFailed , " System.StateTree.Stop.NormalFailed " ) ;
struct FStateTreeTest_Stop_GlobalSucceeded : FStateTreeTest_Stop
{
virtual bool SetUp ( ) override
{
// Normal task completes later.
NormalTaskTicks = 2 ;
// Global task completes as succeeded.
GlobalTaskStatus = EStateTreeRunStatus : : Succeeded ;
GlobalTaskTicks = 1 ;
// Tree should complete as succeeded.
ExpectedStatusAfterTick = EStateTreeRunStatus : : Succeeded ;
// Tasks should have Transition.CurrentRunStatus as succeeded.
ExpectedExitStatusStr = TEXT ( " ExitSucceeded " ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Stop_GlobalSucceeded , " System.StateTree.Stop.GlobalSucceeded " ) ;
struct FStateTreeTest_Stop_GlobalFailed : FStateTreeTest_Stop
{
virtual bool SetUp ( ) override
{
// Normal task completes later
NormalTaskTicks = 2 ;
// Global task completes as failed.
GlobalTaskStatus = EStateTreeRunStatus : : Failed ;
GlobalTaskTicks = 1 ;
// Tree should complete as failed.
ExpectedStatusAfterTick = EStateTreeRunStatus : : Failed ;
// Tasks should have Transition.CurrentRunStatus as failed.
ExpectedExitStatusStr = TEXT ( " ExitFailed " ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Stop_GlobalFailed , " System.StateTree.Stop.GlobalFailed " ) ;
//
// Tests combinations of completing the tree on EnterState.
//
struct FStateTreeTest_StopEnterNormal : FStateTreeTest_Stop
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = SetupTree ( ) ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
// If a normal task fails at start, the last tick status will be failed, but transition handling (and final execution status) will take place next tick.
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Tree should be running after start " , Status , EStateTreeRunStatus : : Running ) ;
AITEST_EQUAL ( " Last execution status should be expected value " , Exec . GetLastTickStatus ( ) , ExpectedStatusAfterStart ) ;
// Handles any transitions from failed transition
Status = Exec . Tick ( 0.1f ) ;
AITEST_EQUAL ( " Start should be expected value " , Status , ExpectedStatusAfterStart ) ;
AITEST_TRUE ( " StateTree GlobalTask should get exit state expectedly " , Exec . Expect ( GlobalTaskName , ExpectedExitStatusStr ) ) ;
AITEST_TRUE ( " StateTree TaskA should enter state " , Exec . Expect ( TaskAName , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree TaskA should report exit status " , Exec . Expect ( TaskAName , ExpectedExitStatusStr ) ) ;
Exec . LogClear ( ) ;
return true ;
}
EStateTreeRunStatus ExpectedStatusAfterStart = EStateTreeRunStatus : : Succeeded ;
FString ExpectedExitStatusStr = TEXT ( " ExitSucceeded " ) ;
bool bExpectNormalTaskToRun = true ;
} ;
struct FStateTreeTest_Stop_NormalEnterSucceeded : FStateTreeTest_StopEnterNormal
{
virtual bool SetUp ( ) override
{
// Tasks should complete later.
NormalTaskTicks = 2 ;
GlobalTaskTicks = 2 ;
// Normal task EnterState as succeeded, completion is handled using completion transitions.
NormalTaskEnterStatus = EStateTreeRunStatus : : Succeeded ;
// Tree should complete as succeeded.
ExpectedStatusAfterStart = EStateTreeRunStatus : : Succeeded ;
// Tasks should have Transition.CurrentRunStatus as succeeded.
ExpectedExitStatusStr = TEXT ( " ExitSucceeded " ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Stop_NormalEnterSucceeded , " System.StateTree.Stop.NormalEnterSucceeded " ) ;
struct FStateTreeTest_Stop_NormalEnterFailed : FStateTreeTest_StopEnterNormal
{
virtual bool SetUp ( ) override
{
// Tasks should complete later.
NormalTaskTicks = 2 ;
GlobalTaskTicks = 2 ;
// Normal task EnterState as failed, completion is handled using completion transitions.
NormalTaskEnterStatus = EStateTreeRunStatus : : Failed ;
// Tree should complete as failed.
ExpectedStatusAfterStart = EStateTreeRunStatus : : Failed ;
// Tasks should have Transition.CurrentRunStatus as failed.
ExpectedExitStatusStr = TEXT ( " ExitFailed " ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Stop_NormalEnterFailed , " System.StateTree.Stop.NormalEnterFailed " ) ;
struct FStateTreeTest_StopEnterGlobal : FStateTreeTest_Stop
{
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = SetupTree ( ) ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Start should be expected value " , Status , ExpectedStatusAfterStart ) ;
AITEST_TRUE ( " StateTree GlobalTask should get exit state expectedly " , Exec . Expect ( GlobalTaskName , ExpectedExitStatusStr ) ) ;
// Normal tasks should not run
AITEST_FALSE ( " StateTree TaskA should not enter state " , Exec . Expect ( TaskAName , EnterStateStr ) ) ;
AITEST_FALSE ( " StateTree TaskA should not report exit status " , Exec . Expect ( TaskAName , ExpectedExitStatusStr ) ) ;
Exec . LogClear ( ) ;
return true ;
}
EStateTreeRunStatus ExpectedStatusAfterStart = EStateTreeRunStatus : : Succeeded ;
FString ExpectedExitStatusStr = TEXT ( " ExitSucceeded " ) ;
} ;
struct FStateTreeTest_Stop_GlobalEnterSucceeded : FStateTreeTest_StopEnterGlobal
{
virtual bool SetUp ( ) override
{
// Tasks should complete later.
NormalTaskTicks = 2 ;
GlobalTaskTicks = 2 ;
// Global task EnterState as succeeded, completion is handled directly based on the global task status.
GlobalTaskEnterStatus = EStateTreeRunStatus : : Succeeded ;
// Tree should complete as succeeded.
ExpectedStatusAfterStart = EStateTreeRunStatus : : Succeeded ;
// Tasks should have Transition.CurrentRunStatus as Succeeded.
ExpectedExitStatusStr = TEXT ( " ExitSucceeded " ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Stop_GlobalEnterSucceeded , " System.StateTree.Stop.GlobalEnterSucceeded " ) ;
struct FStateTreeTest_Stop_GlobalEnterFailed : FStateTreeTest_StopEnterGlobal
{
virtual bool SetUp ( ) override
{
// Tasks should complete later.
NormalTaskTicks = 2 ;
GlobalTaskTicks = 2 ;
// Global task EnterState as failed, completion is handled directly based on the global task status.
GlobalTaskEnterStatus = EStateTreeRunStatus : : Failed ;
// Tree should complete as failed.
ExpectedStatusAfterStart = EStateTreeRunStatus : : Failed ;
// Tasks should have Transition.CurrentRunStatus as failed.
ExpectedExitStatusStr = TEXT ( " ExitFailed " ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Stop_GlobalEnterFailed , " System.StateTree.Stop.GlobalEnterFailed " ) ;
struct FStateTreeTest_Stop_ExternalStop : FStateTreeTest_Stop
{
virtual bool SetUp ( ) override
{
// Tasks should complete later.
NormalTaskTicks = 2 ;
GlobalTaskTicks = 2 ;
// Tree should tick and keep on running.
ExpectedStatusAfterTick = EStateTreeRunStatus : : Running ;
// Tree should stop as stopped.
ExpectedStatusAfterStop = EStateTreeRunStatus : : Stopped ;
// Tasks should have Transition.CurrentRunStatus as stopped.
ExpectedExitStatusStr = TEXT ( " ExitStopped " ) ;
return true ;
}
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = SetupTree ( ) ;
FStateTreeCompilerLog Log ;
FStateTreeCompiler Compiler ( Log ) ;
const bool bResult = Compiler . Compile ( StateTree ) ;
AITEST_TRUE ( " StateTree should get compiled " , bResult ) ;
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
FStateTreeInstanceData InstanceData ;
FTestStateTreeExecutionContext Exec ( StateTree , StateTree , InstanceData ) ;
const bool bInitSucceeded = Exec . IsValid ( ) ;
AITEST_TRUE ( " StateTree should init " , bInitSucceeded ) ;
const FString TickStr ( TEXT ( " Tick " ) ) ;
const FString EnterStateStr ( TEXT ( " EnterState " ) ) ;
const FString ExitStateStr ( TEXT ( " ExitState " ) ) ;
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Start should be running " , Status , EStateTreeRunStatus : : Running ) ;
AITEST_TRUE ( " StateTree GlobalTask should enter state " , Exec . Expect ( GlobalTaskName , EnterStateStr ) ) ;
AITEST_TRUE ( " StateTree TaskA should enter state " , Exec . Expect ( TaskAName , EnterStateStr ) ) ;
Exec . LogClear ( ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_EQUAL ( " Tree should end expectedly " , Status , ExpectedStatusAfterTick ) ;
Exec . LogClear ( ) ;
Status = Exec . Stop ( EStateTreeRunStatus : : Stopped ) ;
AITEST_EQUAL ( " Start should be running " , Status , ExpectedStatusAfterStop ) ;
if ( ! ExpectedExitStatusStr . IsEmpty ( ) )
{
AITEST_TRUE ( " StateTree GlobalTask should get exit state expectedly " , Exec . Expect ( GlobalTaskName , ExpectedExitStatusStr ) ) ;
AITEST_TRUE ( " StateTree TaskA should get exit state expectedly " , Exec . Expect ( TaskAName , ExpectedExitStatusStr ) ) ;
}
return true ;
}
EStateTreeRunStatus ExpectedStatusAfterStop = EStateTreeRunStatus : : Stopped ;
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Stop_ExternalStop , " System.StateTree.Stop.ExternalStop " ) ;
struct FStateTreeTest_Stop_AlreadyStopped : FStateTreeTest_Stop_ExternalStop
{
virtual bool SetUp ( ) override
{
// Normal task completes before stop.
NormalTaskTicks = 1 ;
NormalTaskStatus = EStateTreeRunStatus : : Succeeded ;
// Global task completes later
GlobalTaskTicks = 2 ;
// Tree should tick stop as succeeded.
ExpectedStatusAfterTick = EStateTreeRunStatus : : Succeeded ;
// Tree is already stopped, should keep the status (not Stopped).
ExpectedStatusAfterStop = EStateTreeRunStatus : : Succeeded ;
// Skip exit status check.
ExpectedExitStatusStr = TEXT ( " " ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_Stop_AlreadyStopped , " System.StateTree.Stop.AlreadyStopped " ) ;
2023-09-22 09:53:34 -04:00
//
// The deferred stop tests validates that the tree can be properly stopped if requested in the main entry points (Start, Tick, Stop).
//
struct FStateTreeTest_DeferredStop : FAITestBase
{
UStateTree & SetupTree ( ) const
{
UStateTree & StateTree = UE : : StateTree : : Tests : : NewStateTree ( & GetWorld ( ) ) ;
UStateTreeEditorData & EditorData = * Cast < UStateTreeEditorData > ( StateTree . EditorData ) ;
UStateTreeState & Root = EditorData . AddSubTree ( FName ( TEXT ( " Root " ) ) ) ;
UStateTreeState & StateA = Root . AddChildState ( FName ( TEXT ( " A " ) ) ) ;
TStateTreeEditorNode < FTestTask_StopTree > & TaskA = StateA . AddTask < FTestTask_StopTree > ( TEXT ( " Task " ) ) ;
TStateTreeEditorNode < FTestTask_StopTree > & GlobalTask = EditorData . AddGlobalTask < FTestTask_StopTree > ( TEXT ( " GlobalTask " ) ) ;
StateA . AddTransition ( EStateTreeTransitionTrigger : : OnStateSucceeded , EStateTreeTransitionType : : Succeeded ) ;
StateA . AddTransition ( EStateTreeTransitionTrigger : : OnStateFailed , EStateTreeTransitionType : : Failed ) ;
GlobalTask . GetNode ( ) . Phase = GlobalTaskPhase ;
TaskA . GetNode ( ) . Phase = TaskPhase ;
return StateTree ;
}
virtual bool RunDerivedTest ( FTestStateTreeExecutionContext & Exec ) = 0 ;
virtual bool InstantTest ( ) override
{
UStateTree & StateTree = SetupTree ( ) ;
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 ) ;
return RunDerivedTest ( Exec ) ;
}
protected :
EStateTreeUpdatePhase GlobalTaskPhase = EStateTreeUpdatePhase : : Unset ;
EStateTreeUpdatePhase TaskPhase = EStateTreeUpdatePhase : : Unset ;
} ;
struct FStateTreeTest_DeferredStop_EnterGlobalTask : FStateTreeTest_DeferredStop
{
FStateTreeTest_DeferredStop_EnterGlobalTask ( ) { GlobalTaskPhase = EStateTreeUpdatePhase : : EnterStates ; }
virtual bool RunDerivedTest ( FTestStateTreeExecutionContext & Exec ) override
{
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Tree should be stopped " , Status , EStateTreeRunStatus : : Stopped ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_DeferredStop_EnterGlobalTask , " System.StateTree.DeferredStop.EnterGlobalTask " ) ;
struct FStateTreeTest_DeferredStop_TickGlobalTask : FStateTreeTest_DeferredStop
{
FStateTreeTest_DeferredStop_TickGlobalTask ( ) { GlobalTaskPhase = EStateTreeUpdatePhase : : TickStateTree ; }
virtual bool RunDerivedTest ( FTestStateTreeExecutionContext & Exec ) override
{
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Tree should be running " , Status , EStateTreeRunStatus : : Running ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_EQUAL ( " Tree should be stopped " , Status , EStateTreeRunStatus : : Stopped ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_DeferredStop_TickGlobalTask , " System.StateTree.DeferredStop.TickGlobalTask " ) ;
struct FStateTreeTest_DeferredStop_ExitGlobalTask : FStateTreeTest_DeferredStop
{
FStateTreeTest_DeferredStop_ExitGlobalTask ( ) { GlobalTaskPhase = EStateTreeUpdatePhase : : ExitStates ; }
virtual bool RunDerivedTest ( FTestStateTreeExecutionContext & Exec ) override
{
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Tree should be running " , Status , EStateTreeRunStatus : : Running ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_EQUAL ( " Tree should be running " , Status , EStateTreeRunStatus : : Running ) ;
Status = Exec . Stop ( ) ;
AITEST_EQUAL ( " Tree should be stopped " , Status , EStateTreeRunStatus : : Stopped ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_DeferredStop_ExitGlobalTask , " System.StateTree.DeferredStop.ExitGlobalTask " ) ;
struct FStateTreeTest_DeferredStop_EnterTask : FStateTreeTest_DeferredStop
{
FStateTreeTest_DeferredStop_EnterTask ( ) { TaskPhase = EStateTreeUpdatePhase : : EnterStates ; }
virtual bool RunDerivedTest ( FTestStateTreeExecutionContext & Exec ) override
{
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Tree should be running " , Status , EStateTreeRunStatus : : Stopped ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_DeferredStop_EnterTask , " System.StateTree.DeferredStop.EnterTask " ) ;
struct FStateTreeTest_DeferredStop_TickTask : FStateTreeTest_DeferredStop
{
FStateTreeTest_DeferredStop_TickTask ( ) { TaskPhase = EStateTreeUpdatePhase : : TickStateTree ; }
virtual bool RunDerivedTest ( FTestStateTreeExecutionContext & Exec ) override
{
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Tree should be running " , Status , EStateTreeRunStatus : : Running ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_EQUAL ( " Tree should be stopped " , Status , EStateTreeRunStatus : : Stopped ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_DeferredStop_TickTask , " System.StateTree.DeferredStop.TickTask " ) ;
struct FStateTreeTest_DeferredStop_ExitTask : FStateTreeTest_DeferredStop
{
FStateTreeTest_DeferredStop_ExitTask ( ) { TaskPhase = EStateTreeUpdatePhase : : ExitStates ; }
virtual bool RunDerivedTest ( FTestStateTreeExecutionContext & Exec ) override
{
EStateTreeRunStatus Status = EStateTreeRunStatus : : Unset ;
Status = Exec . Start ( ) ;
AITEST_EQUAL ( " Tree should be running " , Status , EStateTreeRunStatus : : Running ) ;
Status = Exec . Tick ( 0.1f ) ;
AITEST_EQUAL ( " Tree should be running " , Status , EStateTreeRunStatus : : Running ) ;
Status = Exec . Stop ( ) ;
AITEST_EQUAL ( " Tree should be stopped " , Status , EStateTreeRunStatus : : Stopped ) ;
return true ;
}
} ;
IMPLEMENT_AI_INSTANT_TEST ( FStateTreeTest_DeferredStop_ExitTask , " System.StateTree.DeferredStop.ExitTask " ) ;
2023-06-05 06:33:07 -04:00
2022-11-22 08:13:54 -05:00
UE_ENABLE_OPTIMIZATION_SHIP
2021-09-28 13:33:17 -04:00
# undef LOCTEXT_NAMESPACE
2022-09-28 01:06:15 -04:00