孟宇 2 mēneši atpakaļ
vecāks
revīzija
a2c1184060

+ 42 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbilityContext.cpp

@@ -322,9 +322,34 @@ EAbilityRunStatus FEzAbilityContext::Stop(const EAbilityRunStatus CompletionStat
 
 EAbilityRunStatus FEzAbilityContext::GetAbilityRunStatus() const
 {
+	if (!IsValid())
+	{
+		EZ_ABILITY_LOG(Warning, TEXT("%hs: EzAbiltiy context is not initialized properly ('%s' using StateTree '%s')"),
+			__FUNCTION__, *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
+		return EAbilityRunStatus::Failed;
+	}
+
+	if (const FEzAbilityExecutionState* Exec = InstanceData.GetExecutionState())
+	{
+		return Exec->TreeRunStatus;
+	}
+	
 	return EAbilityRunStatus::Failed;
 }
 
+EAbilityRunStatus FEzAbilityContext::GetLastTickStatus() const
+{
+	if (!IsValid())
+	{
+		EZ_ABILITY_LOG(Warning, TEXT("%hs: EzAbiltiy context is not initialized properly ('%s' using StateTree '%s')"),
+			__FUNCTION__, *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
+		return EAbilityRunStatus::Failed;
+	}
+
+	const FEzAbilityExecutionState& Exec = GetExecState();
+	return Exec.LastTickStatus;
+}
+
 EAbilityRunStatus FEzAbilityContext::Tick(float DeltaTime)
 {
 	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_Tick);
@@ -570,6 +595,23 @@ TArray<FName> FEzAbilityContext::GetActiveStateNames() const
 	return Result;
 }
 
+void FEzAbilityContext::SendEvent(const FGameplayTag Tag, const FConstStructView Payload, const FName Origin) const
+{
+	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_SendEvent);
+
+	if (!IsValid())
+	{
+		EZ_ABILITY_LOG_AND_TRACE(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
+			__FUNCTION__, *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
+		return;
+	}
+
+	EZ_ABILITY_LOG_AND_TRACE(Verbose, TEXT("Send Event '%s'"), *Tag.ToString());
+
+	FEzAbilityEventQueue& LocalEventQueue = InstanceData.GetMutableEventQueue();
+	LocalEventQueue.SendEvent(Owner, Tag, Payload, Origin);
+}
+
 bool FEzAbilityContext::CanExecute(EExecutePolicy Policy) const
 {
 	switch (Policy) {

+ 7 - 1
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityContext.h

@@ -75,7 +75,10 @@ public:
 	EAbilityRunStatus Start(UEzAbility* InAbility, const struct FEzAbilityParameter& Parameter, FText& OutText);
 	EAbilityRunStatus Stop(const EAbilityRunStatus CompletionStatus = EAbilityRunStatus::Stopped);
 	EAbilityRunStatus GetAbilityRunStatus() const;
-
+	
+	/** @return the status of the last tick function */
+	EAbilityRunStatus GetLastTickStatus() const;
+	
 	EAbilityRunStatus Tick(float DeltaTime);
 
 	const FEzAbilityExecutionFrame* GetCurrentlyProcessedFrame()		const { return CurrentlyProcessedFrame; }
@@ -125,6 +128,9 @@ public:
 		check(CurrentlyProcessedFrame);
 		return TStateTreeInstanceDataStructRef<typename T::FInstanceDataType>(InstanceData, *CurrentlyProcessedFrame, Node.InstanceDataHandle);
 	}
+
+	/** Sends event for the EzAbility. */
+	void SendEvent(const FGameplayTag Tag, const FConstStructView Payload = FConstStructView(), const FName Origin = FName()) const;
 	
 protected:
 

+ 3 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityTypes.h

@@ -128,6 +128,9 @@ UENUM(BlueprintType)
 enum class EEzAbilityTransitionPriority : uint8
 {
 	None UMETA(Hidden),
+
+	/** Low priority. */
+	Low,
 	
 	/** Normal priority. */
 	Normal,

+ 3344 - 2
Ability/Plugins/EzAbility/Source/EzAbilityTest/Private/EzAbilityTest.cpp

@@ -12,6 +12,7 @@
 #include "EzAblityTestTypes.h"
 #include "GameplayTagsManager.h"
 #include "Condition/EzAbilityCommonConditions.h"
+#include "EzAbilityTypes.h"
 
 #include UE_INLINE_GENERATED_CPP_BY_NAME(EzAbilityTest)
 
@@ -19,6 +20,8 @@
 
 UE_DISABLE_OPTIMIZATION_SHIP
 
+std::atomic<int32> FEzAbilityTestConditionInstanceData::GlobalCounter = 0;
+
 namespace UE::EzAbility::Tests
 {
 	UEzAbility& NewAbility(UObject* Outer = GetTransientPackage())
@@ -56,8 +59,8 @@ namespace UE::EzAbility::Tests
 		virtual void AddTags() override
 		{
 			UGameplayTagsManager& Manager = UGameplayTagsManager::Get();
-			TestTag = Manager.AddNativeGameplayTag(TEXT("Test.StateTree.Tag"));
-			TestTag2 = Manager.AddNativeGameplayTag(TEXT("Test.StateTree.Tag2"));
+			TestTag = Manager.AddNativeGameplayTag(TEXT("Test.EzAbility.Tag"));
+			TestTag2 = Manager.AddNativeGameplayTag(TEXT("Test.EzAbility.Tag2"));
 		}
 
 		FORCEINLINE static const FNativeGameplayTags& Get()
@@ -159,5 +162,3344 @@ struct FEzAbilityTest_EmptyAbility : FAITestBase
 
 IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_EmptyAbility, "System.EzAbility.EmptyAbility");
 
+struct FEzAbilityTest_Sequence : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& State1 = Root.AddChildState(FName(TEXT("State1")));
+		UEzAbilityState& State2 = Root.AddChildState(FName(TEXT("State2")));
+
+		auto& Task1 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+		State1.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::NextState);
+
+		auto& Task2 = State2.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task2")));
+		State2.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::Succeeded);
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString TickStr(TEXT("Tick"));
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString ExitStateStr(TEXT("ExitState"));
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);
+		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+		AITEST_FALSE("EzAbility Task1 should not tick", Exec.Expect(Task1.GetName(), TickStr));
+		Exec.LogClear();
+
+		Status = Exec.Tick(0.1f);
+		AITEST_TRUE("EzAbility Task1 should tick, and exit state", Exec.Expect(Task1.GetName(), TickStr).Then(Task1.GetName(), ExitStateStr));
+		AITEST_TRUE("EzAbility Task2 should enter state", Exec.Expect(Task2.GetName(), EnterStateStr));
+		AITEST_FALSE("EzAbility Task2 should not tick", Exec.Expect(Task2.GetName(), TickStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+		Exec.LogClear();
+		
+		Status = Exec.Tick(0.1f);
+        AITEST_TRUE("EzAbility Task2 should tick, and exit state", Exec.Expect(Task2.GetName(), TickStr).Then(Task2.GetName(), ExitStateStr));
+		AITEST_FALSE("EzAbility Task1 should not tick", Exec.Expect(Task1.GetName(), TickStr));
+        AITEST_TRUE("EzAbility should be completed", Status == EAbilityRunStatus::Succeeded);
+		Exec.LogClear();
+
+		Status = Exec.Tick(0.1f);
+		AITEST_FALSE("EzAbility Task1 should not tick", Exec.Expect(Task1.GetName(), TickStr));
+		AITEST_FALSE("EzAbility Task2 should not tick", Exec.Expect(Task2.GetName(), TickStr));
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Sequence, "System.EzAbility.Sequence");
+
+struct FEzAbilityTest_Select : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& State1 = Root.AddChildState(FName(TEXT("State1")));
+		UEzAbilityState& State1A = State1.AddChildState(FName(TEXT("State1A")));
+
+		auto& TaskRoot = Root.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskRoot")));
+		TaskRoot.GetNode().TicksToCompletion = 3;  // let Task1A to complete first
+
+		auto& Task1 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+		Task1.GetNode().TicksToCompletion = 3; // let Task1A to complete first
+
+		auto& Task1A = State1A.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1A")));
+		Task1A.GetNode().TicksToCompletion = 2;
+		State1A.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &State1);
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString TickStr(TEXT("Tick"));
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString ExitStateStr(TEXT("ExitState"));
+
+		// Start and enter state
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_TRUE("EzAbility TaskRoot should enter state", Exec.Expect(TaskRoot.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task1A should enter state", Exec.Expect(Task1A.GetName(), EnterStateStr));
+		AITEST_FALSE("EzAbility TaskRoot should not tick", Exec.Expect(TaskRoot.GetName(), TickStr));
+		AITEST_FALSE("EzAbility Task1 should not tick", Exec.Expect(Task1.GetName(), TickStr));
+		AITEST_FALSE("EzAbility Task1A should not tick", Exec.Expect(Task1A.GetName(), TickStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		// Regular tick, no state selection at all.
+		Status = Exec.Tick(0.1f);
+		AITEST_TRUE("EzAbility tasks should update in order", Exec.Expect(TaskRoot.GetName(), TickStr).Then(Task1.GetName(), TickStr).Then(Task1A.GetName(), TickStr));
+		AITEST_FALSE("EzAbility TaskRoot should not EnterState", Exec.Expect(TaskRoot.GetName(), EnterStateStr));
+		AITEST_FALSE("EzAbility Task1 should not EnterState", Exec.Expect(Task1.GetName(), EnterStateStr));
+		AITEST_FALSE("EzAbility Task1A should not EnterState", Exec.Expect(Task1A.GetName(), EnterStateStr));
+		AITEST_FALSE("EzAbility TaskRoot should not ExitState", Exec.Expect(TaskRoot.GetName(), ExitStateStr));
+		AITEST_FALSE("EzAbility Task1 should not ExitState", Exec.Expect(Task1.GetName(), ExitStateStr));
+		AITEST_FALSE("EzAbility Task1A should not ExitState", Exec.Expect(Task1A.GetName(), ExitStateStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		// Partial reselect, Root should not get EnterState
+		Status = Exec.Tick(0.1f);
+		AITEST_FALSE("EzAbility TaskRoot should not enter state", Exec.Expect(TaskRoot.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task1 should tick, exit state, and enter state", Exec.Expect(Task1.GetName(), TickStr).Then(Task1.GetName(), ExitStateStr).Then(Task1.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task1A should tick, exit state, and enter state", Exec.Expect(Task1A.GetName(), TickStr).Then(Task1A.GetName(), ExitStateStr).Then(Task1A.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+        Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Select, "System.EzAbility.Select");
+
+
+struct FEzAbilityTest_FailEnterState : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& State1 = Root.AddChildState(FName(TEXT("State1")));
+		UEzAbilityState& State1A = State1.AddChildState(FName(TEXT("State1A")));
+
+		auto& TaskRoot = Root.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskRoot")));
+
+		auto& Task1 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+		auto& Task2 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task2")));
+		Task2.GetNode().EnterStateResult = EAbilityRunStatus::Failed;
+		auto& Task3 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task3")));
+
+		auto& Task1A = State1A.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1A")));
+		State1A.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &State1);
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility 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
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_TRUE("EzAbility TaskRoot should enter state", Exec.Expect(TaskRoot.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task2 should enter state", Exec.Expect(Task2.GetName(), EnterStateStr));
+		AITEST_FALSE("EzAbility Task3 should not enter state", Exec.Expect(Task3.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Should execute StateCompleted in reverse order", Exec.Expect(Task2.GetName(), StateCompletedStr).Then(Task1.GetName(), StateCompletedStr).Then(TaskRoot.GetName(), StateCompletedStr));
+		AITEST_FALSE("EzAbility Task3 should not state complete", Exec.Expect(Task3.GetName(), StateCompletedStr));
+		AITEST_TRUE("EzAbility exec status should be failed", Exec.GetLastTickStatus() == EAbilityRunStatus::Failed);
+		Exec.LogClear();
+
+		// Stop and exit state
+		Status = Exec.Stop();
+		AITEST_TRUE("EzAbility TaskRoot should exit state", Exec.Expect(TaskRoot.GetName(), ExitStateStr));
+		AITEST_TRUE("EzAbility Task1 should exit state", Exec.Expect(Task1.GetName(), ExitStateStr));
+		AITEST_TRUE("EzAbility Task2 should exit state", Exec.Expect(Task2.GetName(), ExitStateStr));
+		AITEST_FALSE("EzAbility Task3 should not exit state", Exec.Expect(Task3.GetName(), ExitStateStr));
+		AITEST_TRUE("EzAbility status should be stopped", Status == EAbilityRunStatus::Stopped);
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_FailEnterState, "System.EzAbility.FailEnterState");
+
+
+struct FEzAbilityTest_Restart : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& State1 = Root.AddChildState(FName(TEXT("State1")));
+
+		auto& Task1 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+		Task1.GetNode().TicksToCompletion = 2;
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility 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
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility exec status should be running", Exec.GetLastTickStatus() == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		// Tick
+		Status = Exec.Tick(0.1f);
+		AITEST_TRUE("EzAbility exec status should be running", Exec.GetLastTickStatus() == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		// Call Start again, should stop and start the tree.
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_TRUE("EzAbility Task1 should exit state", Exec.Expect(Task1.GetName(), ExitStateStr));
+		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility exec status should be running", Exec.GetLastTickStatus() == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Restart, "System.EzAbility.Restart");
+
+struct FEzAbilityTest_SubTree : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& State1 = Root.AddChildState(FName(TEXT("State1")), EEzAbilityStateType::Linked);
+		UEzAbilityState& State2 = Root.AddChildState(FName(TEXT("State2")));
+		UEzAbilityState& State3 = Root.AddChildState(FName(TEXT("State3")), EEzAbilityStateType::Subtree);
+		UEzAbilityState& State3A = State3.AddChildState(FName(TEXT("State3A")));
+		UEzAbilityState& State3B = State3.AddChildState(FName(TEXT("State3B")));
+
+		State1.LinkedSubtree = State3.GetLinkToState();
+
+		State1.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &State2);
+
+		auto& Task2 = State2.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task2")));
+		State2.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::Succeeded);
+
+		auto& Task3A = State3A.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task3A")));
+		State3A.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &State3B);
+
+		auto& Task3B = State3B.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task3B")));
+		State3B.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::Succeeded);
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility 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
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+
+		AITEST_TRUE("EzAbility Active States should be in Root/State1/State3/State3A", Exec.ExpectInActiveStates(Root.Name, State1.Name, State3.Name, State3A.Name));
+		AITEST_FALSE("EzAbility Task2 should not enter state", Exec.Expect(Task2.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task3A should enter state", Exec.Expect(Task3A.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		// Transition within subtree
+		Status = Exec.Tick(0.1f);
+		AITEST_TRUE("EzAbility Active States should be in Root/State1/State3/State3B", Exec.ExpectInActiveStates(Root.Name, State1.Name, State3.Name, State3B.Name));
+		AITEST_TRUE("EzAbility Task3B should enter state", Exec.Expect(Task3B.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		// Complete subtree
+		Status = Exec.Tick(0.1f);
+		AITEST_TRUE("EzAbility Active States should be in Root/State2", Exec.ExpectInActiveStates(Root.Name, State2.Name));
+		AITEST_TRUE("EzAbility Task2 should enter state", Exec.Expect(Task2.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		// Complete the whole tree
+		Status = Exec.Tick(0.1f);
+		AITEST_TRUE("EzAbility should complete in succeeded", Status == EAbilityRunStatus::Succeeded);
+		Exec.LogClear();
+		
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_SubTree, "System.EzAbility.SubTree");
+
+struct FEzAbilityTest_SubTreeCondition : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		/*
+		- Root
+			- Linked : Subtree -> Root
+		- SubTree : Task1
+			- ? State1 : Task2 -> Succeeded // condition linked to Task1
+			- State2 : Task3
+		*/
+		
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& Linked = Root.AddChildState(FName(TEXT("Linked")), EEzAbilityStateType::Linked);
+		
+		UEzAbilityState& SubTree = Root.AddChildState(FName(TEXT("SubTree")), EEzAbilityStateType::Subtree);
+		UEzAbilityState& State1 = SubTree.AddChildState(FName(TEXT("State1")));
+		UEzAbilityState& State2 = SubTree.AddChildState(FName(TEXT("State2")));
+
+		Linked.LinkedSubtree = SubTree.GetLinkToState();
+
+		Linked.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &Root);
+
+		// SubTask should not complete during the test.
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& SubTask = SubTree.AddTask<FAbilityTestTask_Stand>(FName(TEXT("SubTask")));
+		SubTask.GetNode().TicksToCompletion = 100;
+
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task1 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+		Task1.GetNode().TicksToCompletion = 1;
+
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task2 = State2.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task2")));
+		Task2.GetNode().TicksToCompletion = 1;
+		
+		// Allow to enter State1 if Task1 instance data TicksToCompletion > 0.
+		TEzAbilityEditorNode<FAbilityCompareIntCondition>& IntCond1 = State1.AddEnterCondition<FAbilityCompareIntCondition>(EGenericCheck::Greater);
+		EditorData.AddPropertyBinding(SubTask, TEXT("CurrentTick"), IntCond1, TEXT("Left"));
+		IntCond1.GetInstanceData().Right = 0;
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility 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
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+
+		AITEST_TRUE("EzAbility Active States should be in Root/Linked/SubTree/State2", Exec.ExpectInActiveStates(Root.Name, Linked.Name, SubTree.Name, State2.Name));
+		AITEST_FALSE("EzAbility State1 should not be active", Exec.ExpectInActiveStates(State1.Name)); // Enter condition should prevent to enter State1
+		AITEST_TRUE("EzAbility SubTask should enter state", Exec.Expect(SubTask.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task2 should enter state", Exec.Expect(Task2.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		// Task1 completes, and we should enter State1 since the enter condition now passes.
+		Status = Exec.Tick(0.1f);
+		AITEST_TRUE("EzAbility Active States should be in Root/Linked/SubTree/State1", Exec.ExpectInActiveStates(Root.Name, Linked.Name, SubTree.Name, State1.Name));
+		AITEST_FALSE("EzAbility State2 should not be active", Exec.ExpectInActiveStates(State2.Name));
+		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_SubTreeCondition, "System.EzAbility.SubTreeCondition");
+
+struct FEzAbilityTest_SubTree_CascadedSucceeded : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+
+		//	- Root [TaskA]
+		//		- LinkedState>SubTreeState -> (F)Failed
+		//		- SubTreeState [TaskB]
+		//			- SubLinkedState>SubSubTreeState -> (S)Failed
+		//		- SubSubTreeState
+		//			- SubSubLeaf [TaskC] -> (S)Succeeded
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& LinkedState = Root.AddChildState(FName(TEXT("Linked")), EEzAbilityStateType::Linked);
+		
+		UEzAbilityState& SubTreeState = Root.AddChildState(FName(TEXT("SubTreeState")), EEzAbilityStateType::Subtree);
+		UEzAbilityState& SubLinkedState = SubTreeState.AddChildState(FName(TEXT("SubLinkedState")), EEzAbilityStateType::Linked);
+		
+		UEzAbilityState& SubSubTreeState = Root.AddChildState(FName(TEXT("SubSubTreeState")), EEzAbilityStateType::Subtree);
+		UEzAbilityState& SubSubLeaf = SubSubTreeState.AddChildState(FName(TEXT("SubSubLeaf")));
+
+		LinkedState.LinkedSubtree = SubTreeState.GetLinkToState();
+		SubLinkedState.LinkedSubtree = SubSubTreeState.GetLinkToState();
+
+		LinkedState.AddTransition(EEzAbilityTransitionTrigger::OnStateFailed, EEzAbilityTransitionType::Failed);
+		SubLinkedState.AddTransition(EEzAbilityTransitionTrigger::OnStateSucceeded, EEzAbilityTransitionType::Failed);
+		SubSubLeaf.AddTransition(EEzAbilityTransitionTrigger::OnStateSucceeded, EEzAbilityTransitionType::Succeeded);
+
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& TaskA = Root.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskA")));
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& TaskB = SubTreeState.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskB")));
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& TaskC = SubSubLeaf.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskC")));
+
+		TaskA.GetNode().TicksToCompletion = 2;
+		TaskB.GetNode().TicksToCompletion = 2;
+		TaskC.GetNode().TicksToCompletion = 1; // The deepest task completes first.
+		
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility 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
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_TRUE("EzAbility 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("EzAbility should be running", Status == EAbilityRunStatus::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("EzAbility should be Failed", Status == EAbilityRunStatus::Failed);
+		Exec.LogClear();
+		
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_SubTree_CascadedSucceeded, "System.EzAbility.SubTree.CascadedSucceeded");
+
+
+struct FEzAbilityTest_SharedInstanceData : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		auto& IntCond = Root.AddEnterCondition<FEzAbilityTestCondition>();
+		IntCond.GetInstanceData().Count = 1;
+
+		auto& Task = Root.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task")));
+		Task.GetNode().TicksToCompletion = 2;
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		// Init, nothing should access the shared data.
+		constexpr int32 NumConcurrent = 100;
+		FEzAbilityTestConditionInstanceData::GlobalCounter = 0;
+
+		bool bInitSucceeded = true;
+		TArray<FEzAbilityInstanceData> InstanceDatas;
+
+		InstanceDatas.SetNum(NumConcurrent);
+		for (int32 Index = 0; Index < NumConcurrent; Index++)
+		{
+			FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceDatas[Index]);
+			bInitSucceeded &= Exec.IsValid();
+		}
+		AITEST_TRUE("All EzAbility contexts should init", bInitSucceeded);
+		AITEST_EQUAL("Test condition global counter should be 0", (int32)FEzAbilityTestConditionInstanceData::GlobalCounter, 0);
+		
+		// Start in parallel
+		// This should create shared data per thread.
+		// We expect that ParallelForWithTaskContext() creates a context per thread.
+		TArray<FEzAbilityTestRunContext> RunContexts;
+		
+		ParallelForWithTaskContext(
+			RunContexts,
+			InstanceDatas.Num(),
+			[&InstanceDatas, &EzAbility](FEzAbilityTestRunContext& RunContext, int32 Index)
+			{
+				FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceDatas[Index]);
+
+				FEzAbilityParameter Parameter;
+				FText OutText;
+				const EAbilityRunStatus Status = Exec.Start(&EzAbility, Parameter, OutText);;
+				if (Status == EAbilityRunStatus::Running)
+				{
+					RunContext.Count++;
+				}
+			}
+		);
+
+		int32 StartTotalRunning = 0;
+		for (FEzAbilityTestRunContext RunContext : RunContexts)
+		{
+			StartTotalRunning += RunContext.Count;
+		}
+		AITEST_EQUAL("All EzAbility contexts should be running after Start", StartTotalRunning, NumConcurrent);
+		AITEST_EQUAL("Test condition global counter should equal context count after Start", (int32)FEzAbilityTestConditionInstanceData::GlobalCounter, InstanceDatas.Num());
+
+		
+		// Tick in parallel
+		// This should not recreate the data, so FEzAbilityTestConditionInstanceData::GlobalCounter should stay as is.
+		for (FEzAbilityTestRunContext RunContext : RunContexts)
+		{
+			RunContext.Count = 0;
+		}
+
+		ParallelForWithTaskContext(
+			RunContexts,
+			InstanceDatas.Num(),
+			[&InstanceDatas, &EzAbility](FEzAbilityTestRunContext& RunContext, int32 Index)
+			{
+				FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceDatas[Index]);
+				const EAbilityRunStatus Status = Exec.Tick(0.1f);
+				if (Status == EAbilityRunStatus::Running)
+				{
+					RunContext.Count++;
+				}
+			}
+		);
+
+		int32 TickTotalRunning = 0;
+		for (FEzAbilityTestRunContext RunContext : RunContexts)
+		{
+			TickTotalRunning += RunContext.Count;
+		}
+		AITEST_EQUAL("All EzAbility contexts should be running after Tick", TickTotalRunning, NumConcurrent);
+		AITEST_EQUAL("Test condition global counter should equal context count after Tick", (int32)FEzAbilityTestConditionInstanceData::GlobalCounter, InstanceDatas.Num());
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_SharedInstanceData, "System.EzAbility.SharedInstanceData");
+
+struct FEzAbilityTest_TransitionPriority : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+
+		/*
+			- 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).
+		*/
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& State1 = Root.AddChildState(FName(TEXT("State1")));
+		UEzAbilityState& State1A = State1.AddChildState(FName(TEXT("State1A")));
+		UEzAbilityState& State1B = State1.AddChildState(FName(TEXT("State1B")));
+		UEzAbilityState& State1C = State1.AddChildState(FName(TEXT("State1C")));
+
+		auto& Task1 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+		Task1.GetNode().TicksToCompletion = 2;
+		State1.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::Succeeded);
+		
+		auto& Task1A = State1A.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1A")));
+		Task1A.GetNode().TicksToCompletion = 1;
+		State1A.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::NextState);
+
+		auto& Task1B = State1B.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1B")));
+		Task1B.GetNode().TicksToCompletion = 2;
+		State1B.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::NextState);
+
+		auto& Task1C = State1C.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1C")));
+		Task1C.GetNode().TicksToCompletion = 2;
+		
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility 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
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task1A should enter state", Exec.Expect(Task1A.GetName(), EnterStateStr));
+		Exec.LogClear();
+
+		// Transition from Task1A to Task1B
+		Status = Exec.Tick(0.1f);
+		AITEST_TRUE("EzAbility Task1A should complete", Exec.Expect(Task1A.GetName(), StateCompletedStr));
+		AITEST_TRUE("EzAbility 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("EzAbility Task1 should complete", Exec.Expect(Task1.GetName(), StateCompletedStr));
+		AITEST_EQUAL("Tree execution should stop on success", Status, EAbilityRunStatus::Succeeded);
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_TransitionPriority, "System.EzAbility.Transition.Priority");
+
+struct FEzAbilityTest_TransitionPriorityEnterState : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+		
+		UEzAbilityState& Root =	EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& State0 = Root.AddChildState(FName(TEXT("State0")));
+		UEzAbilityState& State1 = Root.AddChildState(FName(TEXT("State1")));
+		UEzAbilityState& State1A = State1.AddChildState(FName(TEXT("State1A")));
+		UEzAbilityState& State2 = Root.AddChildState(FName(TEXT("State2")));
+		UEzAbilityState& State3 = Root.AddChildState(FName(TEXT("State3")));
+
+		auto& Task0 = State0.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task0")));
+		State0.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &State1);
+
+		auto& Task1 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+		Task1.GetNode().EnterStateResult = EAbilityRunStatus::Failed;
+		State1.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &State2);
+		
+		auto& Task1A = State1A.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1A")));
+		State1A.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &State3);
+
+		auto& Task2 = State2.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task2")));
+		State2.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::Succeeded);
+
+		auto& Task3 = State3.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task3")));
+		State3.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::Succeeded);
+
+		
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility 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
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_TRUE("EzAbility 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("EzAbility Task0 should complete", Exec.Expect(Task0.GetName(), StateCompletedStr));
+		AITEST_TRUE("EzAbility Task2 should enter state", Exec.Expect(Task2.GetName(), EnterStateStr));
+		AITEST_FALSE("EzAbility Task3 should not enter state", Exec.Expect(Task3.GetName(), EnterStateStr));
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_TransitionPriorityEnterState, "System.EzAbility.Transition.PriorityEnterState");
+
+struct FEzAbilityTest_TransitionNextSelectableState : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+
+		UEzAbilityState& Root =	EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& State0 = Root.AddChildState(FName(TEXT("State0")));
+		UEzAbilityState& State1 = Root.AddChildState(FName(TEXT("State1")));
+		UEzAbilityState& State2 = Root.AddChildState(FName(TEXT("State2")));
+
+		auto& EvalA = EditorData.AddEvaluator<FEzAbilityTestEval_A>();
+		EvalA.GetInstanceData().bBoolA = true;
+
+		auto& Task0 = State0.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task0")));
+		State0.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::NextSelectableState);
+
+		// Add Task 1 with Condition that will always fail
+		auto& Task1 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+		auto& BoolCond1 = State1.AddEnterCondition<FAbilityCompareBoolCondition>();
+
+		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<FAbilityTestTask_Stand>(FName(TEXT("Task2")));
+		auto& BoolCond2 = State2.AddEnterCondition<FAbilityCompareBoolCondition>();
+		State2.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::Succeeded);
+
+		EditorData.AddPropertyBinding(EvalA, TEXT("bBoolA"), BoolCond2, TEXT("bLeft"));
+		BoolCond2.GetInstanceData().bRight = EvalA.GetInstanceData().bBoolA;
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString TickStr(TEXT("Tick"));
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString ExitStateStr(TEXT("ExitState"));
+		const FString StateCompletedStr(TEXT("StateCompleted"));
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		// Start and enter state
+		Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_TRUE("EzAbility 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("EzAbility Task0 should complete", Exec.Expect(Task0.GetName(), StateCompletedStr));
+		AITEST_FALSE("EzAbility Task1 should not enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task2 should enter state", Exec.Expect(Task2.GetName(), EnterStateStr));
+		Exec.LogClear();
+
+		// Complete Task2
+		Exec.Tick(0.1f);
+		AITEST_TRUE("EzAbility Task2 should complete", Exec.Expect(Task2.GetName(), StateCompletedStr));
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_TransitionNextSelectableState, "System.EzAbility.Transition.NextSelectableState");
+
+
+struct FEzAbilityTest_TransitionNextWithParentData : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+
+		UEzAbilityState& Root =	EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& State0 = Root.AddChildState(FName(TEXT("State0")));
+		UEzAbilityState& State1 = Root.AddChildState(FName(TEXT("State1")));
+		UEzAbilityState& State1A = State1.AddChildState(FName(TEXT("State1A")));
+
+		auto& RootTask = Root.AddTask<FEzAbilityTestTask_B>(FName(TEXT("RootTask")));
+		RootTask.GetInstanceData().bBoolB = true;
+
+		auto& Task0 = State0.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task0")));
+		State0.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::NextState);
+
+		auto& Task1A = State1A.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1A")));
+		auto& BoolCond1 = State1A.AddEnterCondition<FAbilityCompareBoolCondition>();
+
+		EditorData.AddPropertyBinding(RootTask, TEXT("bBoolB"), BoolCond1, TEXT("bLeft"));
+		BoolCond1.GetInstanceData().bRight = true;
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString TickStr(TEXT("Tick"));
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString ExitStateStr(TEXT("ExitState"));
+		const FString StateCompletedStr(TEXT("StateCompleted"));
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		
+		// Start and enter state
+		Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_TRUE("EzAbility 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("EzAbility Task0 should complete", Exec.Expect(Task0.GetName(), StateCompletedStr));
+		AITEST_TRUE("EzAbility Task1A should enter state", Exec.Expect(Task1A.GetName(), EnterStateStr));
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_TransitionNextWithParentData, "System.EzAbility.Transition.NextWithParentData");
+
+// struct FEzAbilityTest_LastConditionWithIndent : FAITestBase
+// {
+// 	virtual bool InstantTest() override
+// 	{
+// 		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+// 		
+// 		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+// 		UEzAbilityState& State1 = Root.AddChildState(FName(TEXT("State1")));
+//
+// 		auto& Task1 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+// 		State1.AddEnterCondition<FEzAbilityTestCondition>();
+// 		auto& LastCondition = State1.AddEnterCondition<FEzAbilityTestCondition>();
+//
+// 		// Last condition has Indent
+// 		LastCondition.ExpressionIndent = 1;
+// 		
+// 		State1.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::Succeeded);
+//
+// 		FEzAbilityCompilerLog Log;
+// 		FEzAbilityCompiler Compiler(Log);
+// 		const bool bResult = Compiler.Compile(EzAbility);
+// 		AITEST_TRUE("EzAbility should get compiled", bResult);
+//
+// 		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+// 		FEzAbilityInstanceData InstanceData;
+// 		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 		const bool bInitSucceeded = Exec.IsValid();
+// 		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+//
+// 		const FString TickStr(TEXT("Tick"));
+// 		const FString EnterStateStr(TEXT("EnterState"));
+// 		const FString ExitStateStr(TEXT("ExitState"));
+//
+// 		FEzAbilityParameter Parameter;
+// 		FText OutText;
+// 		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+// 		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+// 		AITEST_FALSE("EzAbility Task1 should not tick", Exec.Expect(Task1.GetName(), TickStr));
+// 		Exec.LogClear();
+//
+// 		Status = Exec.Tick(0.1f);
+// 		AITEST_TRUE("EzAbility Task1 should tick, and exit state", Exec.Expect(Task1.GetName(), TickStr).Then(Task1.GetName(), ExitStateStr));
+// 		AITEST_TRUE("EzAbility should be completed", Status == EAbilityRunStatus::Succeeded);
+// 		Exec.LogClear();
+//
+// 		Status = Exec.Tick(0.1f);
+// 		AITEST_FALSE("EzAbility Task1 should not tick", Exec.Expect(Task1.GetName(), TickStr));
+// 		Exec.LogClear();
+//
+// 		return true;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_LastConditionWithIndent, "System.EzAbility.LastConditionWithIndent");
+
+struct FEzAbilityTest_TransitionGlobalDataView : FAITestBase
+{
+	// Tests that the global eval and task dataviews are kept up to date when transitioning from  
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& StateA = Root.AddChildState(FName(TEXT("A")));
+		UEzAbilityState& StateB = Root.AddChildState(FName(TEXT("B")));
+
+		auto& EvalA = EditorData.AddEvaluator<FEzAbilityTestEval_A>(FName(TEXT("Eval")));
+		EvalA.GetInstanceData().IntA = 42;
+		auto& GlobalTask = EditorData.AddGlobalTask<FAbilityTestTask_PrintValue>(FName(TEXT("Global")));
+		GlobalTask.GetInstanceData().Value = 123;
+		
+		// State A
+		auto& Task0 = StateA.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task0")));
+		StateA.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &StateB);
+
+		// State B
+		auto& Task1 = StateB.AddTask<FAbilityTestTask_PrintValue>(FName(TEXT("Task1")));
+		EditorData.AddPropertyBinding(EvalA, TEXT("IntA"), Task1, TEXT("Value"));
+		auto& Task2 = StateB.AddTask<FAbilityTestTask_PrintValue>(FName(TEXT("Task2")));
+		EditorData.AddPropertyBinding(GlobalTask, TEXT("Value"), Task2, TEXT("Value"));
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString EnterState42Str(TEXT("EnterState42"));
+		const FString EnterState123Str(TEXT("EnterState123"));
+
+		// Start and enter state
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_TRUE("EzAbility 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("EzAbility Task0 should enter state with value 42", Exec.Expect(Task1.GetName(), EnterState42Str));
+		AITEST_TRUE("EzAbility Task1 should enter state with value 123", Exec.Expect(Task2.GetName(), EnterState123Str));
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_TransitionGlobalDataView, "System.EzAbility.Transition.GlobalDataView");
+
+// struct FEzAbilityTest_TransitionDelay : FAITestBase
+// {
+// 	virtual bool InstantTest() override
+// 	{
+// 		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+// 		const FGameplayTag Tag = UE::EzAbility::Tests::FNativeGameplayTags::Get().TestTag;
+//
+// 		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+// 		UEzAbilityState& StateA = Root.AddChildState(FName(TEXT("A")));
+// 		UEzAbilityState& StateB = Root.AddChildState(FName(TEXT("B")));
+//
+// 		// State A
+// 		auto& Task0 = StateA.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task0")));
+// 		Task0.GetNode().TicksToCompletion = 100;
+// 		
+// 		FEzAbilityTransition& Transition = StateA.AddTransition(EEzAbilityTransitionTrigger::OnEvent, EEzAbilityTransitionType::GotoState, &StateB);
+// 		Transition.bDelayTransition = true;
+// 		Transition.DelayDuration = 0.15f;
+// 		Transition.DelayRandomVariance = 0.0f;
+// 		Transition.RequiredEvent.Tag = Tag;
+//
+// 		// State B
+// 		auto& Task1 = StateB.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+// 		Task1.GetNode().TicksToCompletion = 100;
+//
+// 		FEzAbilityCompilerLog Log;
+// 		FEzAbilityCompiler Compiler(Log);
+// 		const bool bResult = Compiler.Compile(EzAbility);
+//
+// 		AITEST_TRUE("EzAbility should get compiled", bResult);
+//
+// 		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+// 		FEzAbilityInstanceData InstanceData;
+// 		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 		const bool bInitSucceeded = Exec.IsValid();
+// 		AITEST_TRUE("EzAbility 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
+// 		FEzAbilityParameter Parameter;
+// 		FText OutText;
+// 		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+// 		AITEST_TRUE("EzAbility 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("EzAbility Task0 should tick", Exec.Expect(Task0.GetName(), TickStr));
+// 		Exec.LogClear();
+//
+// 		// Should have execution frames
+// 		AITEST_TRUE("Should have active frames", InstanceData.GetExecutionState()->ActiveFrames.Num() > 0);
+//
+// 		// Should have delayed transitions
+// 		const int32 NumDelayedTransitions0 = InstanceData.GetExecutionState()->DelayedTransitions.Num();
+// 		AITEST_EQUAL("Should have a delayed transition", NumDelayedTransitions0, 1);
+//
+// 		// Tick and expect a delayed transition. 
+// 		Status = Exec.Tick(0.1f);
+// 		AITEST_TRUE("EzAbility Task0 should tick", Exec.Expect(Task0.GetName(), TickStr));
+// 		Exec.LogClear();
+//
+// 		const int32 NumDelayedTransitions1 = InstanceData.GetExecutionState()->DelayedTransitions.Num();
+// 		AITEST_EQUAL("Should have a delayed transition", NumDelayedTransitions1, 1);
+//
+// 		// Should complete delayed transition.
+// 		Status = Exec.Tick(0.1f);
+// 		AITEST_TRUE("EzAbility Task0 should exit state", Exec.Expect(Task0.GetName(), ExitStateStr));
+// 		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+// 		Exec.LogClear();
+//
+// 		return true;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_TransitionDelay, "System.EzAbility.TransitionDelay");
+
+// struct FEzAbilityTest_TransitionDelayZero : FAITestBase
+// {
+// 	virtual bool InstantTest() override
+// 	{
+// 		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+// 		const FGameplayTag Tag = UE::EzAbility::Tests::FNativeGameplayTags::Get().TestTag;
+//
+// 		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+// 		UEzAbilityState& StateA = Root.AddChildState(FName(TEXT("A")));
+// 		UEzAbilityState& StateB = Root.AddChildState(FName(TEXT("B")));
+//
+// 		// State A
+// 		auto& Task0 = StateA.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task0")));
+// 		Task0.GetNode().TicksToCompletion = 100;
+// 		
+// 		FEzAbilityTransition& Transition = StateA.AddTransition(EEzAbilityTransitionTrigger::OnEvent, EEzAbilityTransitionType::GotoState, &StateB);
+// 		Transition.bDelayTransition = true;
+// 		Transition.DelayDuration = 0.0f;
+// 		Transition.DelayRandomVariance = 0.0f;
+// 		Transition.RequiredEvent.Tag = Tag;
+//
+// 		// State B
+// 		auto& Task1 = StateB.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+// 		Task1.GetNode().TicksToCompletion = 100;
+//
+// 		FEzAbilityCompilerLog Log;
+// 		FEzAbilityCompiler Compiler(Log);
+// 		const bool bResult = Compiler.Compile(EzAbility);
+//
+// 		AITEST_TRUE("EzAbility should get compiled", bResult);
+//
+// 		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+// 		FEzAbilityInstanceData InstanceData;
+// 		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 		const bool bInitSucceeded = Exec.IsValid();
+// 		AITEST_TRUE("EzAbility 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
+// 		FEzAbilityParameter Parameter;
+// 		FText OutText;
+// 		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+// 		AITEST_TRUE("EzAbility 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("EzAbility Task0 should exit state", Exec.Expect(Task0.GetName(), ExitStateStr));
+// 		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+// 		Exec.LogClear();
+//
+// 		return true;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_TransitionDelayZero, "System.EzAbility.TransitionDelayZero");
+
+// struct FEzAbilityTest_StateRequiringEvent : FAITestBase
+// {
+// 	virtual bool InstantTest() override
+// 	{
+// 		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+//
+// 		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+// 		
+// 		FGameplayTag ValidTag = UE::EzAbility::Tests::FNativeGameplayTags::Get().TestTag;
+// 		FGameplayTag InvalidTag = UE::EzAbility::Tests::FNativeGameplayTags::Get().TestTag2;
+//
+// 		using FValidPayload = FEzAbilityTest_PropertyStructA;
+// 		using FInvalidPayload = FEzAbilityTest_PropertyStructB;
+//
+// 		// This state shouldn't be selected as it requires different tag.
+// 		UEzAbilityState& StateA = Root.AddChildState(FName(TEXT("A")));
+// 		StateA.bHasRequiredEventToEnter  = true;
+// 		StateA.RequiredEventToEnter.Tag = InvalidTag;
+// 		auto& TaskA = StateA.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskA")));
+//
+// 		// This state shouldn't be selected as it requires different payload.
+// 		UEzAbilityState& StateB = Root.AddChildState(FName(TEXT("B")));
+// 		StateB.bHasRequiredEventToEnter  = true;
+// 		StateB.RequiredEventToEnter.PayloadStruct = FInvalidPayload::StaticStruct();
+// 		auto& TaskB = StateB.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskB")));
+//
+// 		// This state shouldn't be selected as it requires the same tag, but different payload.
+// 		UEzAbilityState& StateC = Root.AddChildState(FName(TEXT("C")));
+// 		StateC.bHasRequiredEventToEnter  = true;
+// 		StateC.RequiredEventToEnter.Tag = ValidTag;
+// 		StateC.RequiredEventToEnter.PayloadStruct = FInvalidPayload::StaticStruct();
+// 		auto& TaskC = StateC.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskC")));
+//
+// 		// This state shouldn't be selected as it requires the same payload, but different tag.
+// 		UEzAbilityState& StateD = Root.AddChildState(FName(TEXT("D")));
+// 		StateD.bHasRequiredEventToEnter  = true;
+// 		StateD.RequiredEventToEnter.Tag = InvalidTag;
+// 		StateD.RequiredEventToEnter.PayloadStruct = FValidPayload::StaticStruct();
+// 		auto& TaskD = StateD.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskD")));
+//
+// 		// This state should be selected as the arrived event matches the requirement.
+// 		UEzAbilityState& StateE = Root.AddChildState(FName(TEXT("E")));
+// 		StateE.bHasRequiredEventToEnter  = true;
+// 		StateE.RequiredEventToEnter.Tag = ValidTag;
+// 		StateE.RequiredEventToEnter.PayloadStruct = FValidPayload::StaticStruct();
+// 		auto& TaskE = StateE.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskE")));
+//
+// 		// This state should be selected only initially when there's not event in the queue.
+// 		UEzAbilityState& StateInitial = Root.AddChildState(FName(TEXT("Initial")));
+// 		auto& TaskInitial = StateInitial.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskInitial")));
+// 		StateInitial.AddTransition(EEzAbilityTransitionTrigger::OnEvent, ValidTag, EEzAbilityTransitionType::GotoState, &Root);
+//
+// 		FEzAbilityCompilerLog Log;
+// 		FEzAbilityCompiler Compiler(Log);
+// 		const bool bResult = Compiler.Compile(EzAbility);
+//
+// 		AITEST_TRUE("EzAbility should get compiled", bResult);
+//
+// 		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+// 		FEzAbilityInstanceData InstanceData;
+// 		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 		const bool bInitSucceeded = Exec.IsValid();
+// 		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+//
+// 		const FString EnterStateStr(TEXT("EnterState"));
+//
+// 		FEzAbilityParameter Parameter;
+// 		FText OutText;
+// 		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+// 		AITEST_TRUE("EzAbility TaskInitial should enter state", Exec.Expect(TaskInitial.GetName(), EnterStateStr));
+// 		Exec.LogClear();
+//
+// 		Exec.SendEvent(ValidTag, FConstStructView::Make(FValidPayload()));
+// 		Status = Exec.Tick(0.1f);
+//
+// 		AITEST_FALSE("EzAbility TaskA should not enter state", Exec.Expect(TaskA.GetName(), EnterStateStr));
+// 		AITEST_FALSE("EzAbility TaskB should not enter state", Exec.Expect(TaskB.GetName(), EnterStateStr));
+// 		AITEST_FALSE("EzAbility TaskC should not enter state", Exec.Expect(TaskC.GetName(), EnterStateStr));
+// 		AITEST_FALSE("EzAbility TaskD should not enter state", Exec.Expect(TaskD.GetName(), EnterStateStr));
+// 		AITEST_TRUE("EzAbility TaskE should enter state", Exec.Expect(TaskE.GetName(), EnterStateStr));
+// 		Exec.LogClear();
+//
+// 		return true;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_StateRequiringEvent, "System.EzAbility.StateRequiringEvent");
+
+// struct FEzAbilityTest_PassingTransitionEventToStateSelection : FAITestBase
+// {
+// 	virtual bool InstantTest() override
+// 	{
+// 		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+//
+// 		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+//
+// 		FEzAbilityPropertyPath PathToPayloadMember;
+// 		{
+// 			const bool bParseResult = PathToPayloadMember.FromString(TEXT("Payload.A"));
+//
+// 			AITEST_TRUE("Parsing path should succeeed", bParseResult);
+//
+// 			FEzAbilityEvent EventWithPayload;
+// 			EventWithPayload.Payload = FInstancedStruct::Make<FEzAbilityTest_PropertyStructA>();
+// 			const bool bUpdateSegments = PathToPayloadMember.UpdateSegmentsFromValue(FEzAbilityDataView(FStructView::Make(EventWithPayload)));
+// 			AITEST_TRUE("Updating segments should succeeed", bUpdateSegments);
+// 		}
+//
+// 		// This state shouldn't be selected, because transition's condition and state's enter condition exlude each other.
+// 		UEzAbilityState& StateA = Root.AddChildState(FName(TEXT("A")));
+// 		StateA.bHasRequiredEventToEnter  = true;
+// 		StateA.RequiredEventToEnter.PayloadStruct = FEzAbilityTest_PropertyStructA::StaticStruct();
+// 		auto& TaskA = StateA.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskA")));
+// 		TEzAbilityEditorNode<FAbilityCompareIntCondition>& AIntCond = StateA.AddEnterCondition<FAbilityCompareIntCondition>(EGenericCheck::Equal);
+// 		AIntCond.GetInstanceData().Right = 0;
+// 		EditorData.AddPropertyBinding(
+// 			FEzAbilityPropertyPath(StateA.GetEventID(), PathToPayloadMember.GetSegments()),
+// 			FEzAbilityPropertyPath(AIntCond.ID, TEXT("Left")));
+//
+// 		// This state should be selected as the sent event fullfils both transition's condition and state's enter condition.
+// 		UEzAbilityState& StateB = Root.AddChildState(FName(TEXT("B")));
+// 		StateB.bHasRequiredEventToEnter  = true;
+// 		StateB.RequiredEventToEnter.PayloadStruct = FEzAbilityTest_PropertyStructA::StaticStruct();
+// 		auto& TaskB = StateB.AddTask<FAbilityTestTask_PrintValue>(FName(TEXT("TaskB")));
+// 		// Test copying data from the state event. The condition properties are copied from temp instance data during selection, this gets copied from active instance data.
+// 		TaskB.GetInstanceData().Value = -1; // Initially -1, expected to be overridden by property binding below. 
+// 		EditorData.AddPropertyBinding(
+// 			FEzAbilityPropertyPath(StateB.GetEventID(), PathToPayloadMember.GetSegments()),
+// 			FEzAbilityPropertyPath(TaskB.ID, TEXT("Value")));
+// 		
+// 		TEzAbilityEditorNode<FAbilityCompareIntCondition>& BIntCond = StateB.AddEnterCondition<FAbilityCompareIntCondition>(EGenericCheck::Equal);
+// 		BIntCond.GetInstanceData().Right = 1;
+// 		EditorData.AddPropertyBinding(
+// 			FEzAbilityPropertyPath(StateB.GetEventID(), PathToPayloadMember.GetSegments()),
+// 			FEzAbilityPropertyPath(BIntCond.ID, TEXT("Left")));
+//
+// 		// This state should be selected only initially when there's not event in the queue.
+// 		UEzAbilityState& StateInitial = Root.AddChildState(FName(TEXT("Initial")));
+// 		auto& TaskInitial = StateInitial.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskInitial")));
+// 		// Transition from Initial -> StateA
+// 		FEzAbilityTransition& TransA = StateInitial.AddTransition(EEzAbilityTransitionTrigger::OnEvent, FGameplayTag(), EEzAbilityTransitionType::GotoState, &StateA);
+// 		TransA.RequiredEvent.PayloadStruct = FEzAbilityTest_PropertyStructA::StaticStruct();
+// 		TEzAbilityEditorNode<FAbilityCompareIntCondition>& TransAIntCond = TransA.AddCondition<FAbilityCompareIntCondition>(EGenericCheck::Equal);
+// 		TransAIntCond.GetInstanceData().Right = 1;
+// 		EditorData.AddPropertyBinding(
+// 			FEzAbilityPropertyPath(TransA.GetEventID(), PathToPayloadMember.GetSegments()),
+// 			FEzAbilityPropertyPath(TransAIntCond.ID, TEXT("Left")));
+// 		// Transition from Initial -> StateB
+// 		FEzAbilityTransition& TransB = StateInitial.AddTransition(EEzAbilityTransitionTrigger::OnEvent, FGameplayTag(), EEzAbilityTransitionType::GotoState, &StateB);
+// 		TransB.RequiredEvent.PayloadStruct = FEzAbilityTest_PropertyStructA::StaticStruct();
+// 		TEzAbilityEditorNode<FAbilityCompareIntCondition>& TransBIntCond = TransB.AddCondition<FAbilityCompareIntCondition>(EGenericCheck::Equal);
+// 		TransBIntCond.GetInstanceData().Right = 1;
+// 		EditorData.AddPropertyBinding(
+// 			FEzAbilityPropertyPath(TransB.GetEventID(), PathToPayloadMember.GetSegments()),
+// 			FEzAbilityPropertyPath(TransBIntCond.ID, TEXT("Left")));
+//
+// 		FEzAbilityCompilerLog Log;
+// 		FEzAbilityCompiler Compiler(Log);
+// 		const bool bResult = Compiler.Compile(EzAbility);
+//
+// 		AITEST_TRUE("EzAbility should get compiled", bResult);
+//
+// 		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+// 		FEzAbilityInstanceData InstanceData;
+// 		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 		const bool bInitSucceeded = Exec.IsValid();
+// 		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+//
+// 		const FString EnterStateStr(TEXT("EnterState"));
+//
+// 		FEzAbilityParameter Parameter;
+// 		FText OutText;
+// 		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+// 		AITEST_TRUE("EzAbility TaskInitial should enter state", Exec.Expect(TaskInitial.GetName(), EnterStateStr));
+// 		Exec.LogClear();
+//
+// 		// The conditions test for payload Value=1, the first event should not trigger transition. 
+// 		Exec.SendEvent(UE::EzAbility::Tests::FNativeGameplayTags::Get().TestTag, FConstStructView::Make(FEzAbilityTest_PropertyStructA{0}));
+// 		Exec.SendEvent(UE::EzAbility::Tests::FNativeGameplayTags::Get().TestTag, FConstStructView::Make(FEzAbilityTest_PropertyStructA{1}));
+// 		Status = Exec.Tick(0.1f);
+//
+// 		AITEST_FALSE("EzAbility TaskA should not enter state", Exec.Expect(TaskA.GetName(), EnterStateStr));
+// 		AITEST_TRUE("EzAbility TaskB should enter state", Exec.Expect(TaskB.GetName(), TEXT("EnterState1"))); // TaskB decorates "EnterState" with value from the payload.
+// 		Exec.LogClear();
+//
+// 		return true;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_PassingTransitionEventToStateSelection, "System.EzAbility.PassingTransitionEventToStateSelection");
+
+struct FEzAbilityTest_PropertyPathOffset : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityPropertyPath 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<FEzAbilityPropertyPathIndirection> Indirections;
+		const bool bResolveResult = Path.ResolveIndirections(FEzAbilityTest_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(), EEzAbilityPropertyAccessType::Offset);
+		AITEST_EQUAL("Indirection 1 should be Offset type", Indirections[1].GetAccessType(), EEzAbilityPropertyAccessType::Offset);
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_PropertyPathOffset, "System.EzAbility.PropertyPath.Offset");
+
+struct FEzAbilityTest_PropertyPathParseFail : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		{
+			FEzAbilityPropertyPath Path;
+			const bool bParseResult = Path.FromString(TEXT("")); // empty is valid.
+			AITEST_TRUE("Parsing path should succeed", bParseResult);
+		}
+
+		{
+			FEzAbilityPropertyPath Path;
+			const bool bParseResult = Path.FromString(TEXT("StructB.[0]B"));
+			AITEST_FALSE("Parsing path should fail", bParseResult);
+		}
+
+		{
+			FEzAbilityPropertyPath Path;
+			const bool bParseResult = Path.FromString(TEXT("StructB..NoThere"));
+			AITEST_FALSE("Parsing path should fail", bParseResult);
+		}
+
+		{
+			FEzAbilityPropertyPath Path;
+			const bool bParseResult = Path.FromString(TEXT("."));
+			AITEST_FALSE("Parsing path should fail", bParseResult);
+		}
+
+		{
+			FEzAbilityPropertyPath Path;
+			const bool bParseResult = Path.FromString(TEXT("StructB..B"));
+			AITEST_FALSE("Parsing path should fail", bParseResult);
+		}
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_PropertyPathParseFail, "System.EzAbility.PropertyPath.ParseFail");
+
+struct FEzAbilityTest_PropertyPathOffsetFail : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityPropertyPath 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<FEzAbilityPropertyPathIndirection> Indirections;
+		const bool bResolveResult = Path.ResolveIndirections(FEzAbilityTest_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(FEzAbilityTest_PropertyPathOffsetFail, "System.EzAbility.PropertyPath.OffsetFail");
+
+struct FEzAbilityTest_PropertyPathObject : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityPropertyPath 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);
+
+		UEzAbilityTest_PropertyObject* Object = NewObject<UEzAbilityTest_PropertyObject>();
+		Object->InstancedObject = NewObject<UEzAbilityTest_PropertyObjectInstanced>();
+		
+		const bool bUpdateResult = Path.UpdateSegmentsFromValue(FEzAbilityDataView(Object));
+
+		AITEST_TRUE("Update instance types should succeeed", bUpdateResult);
+		AITEST_TRUE("Path segment 0 instance type should be UEzAbilityTest_PropertyObjectInstanced", Path.GetSegment(0).GetInstanceStruct() == UEzAbilityTest_PropertyObjectInstanced::StaticClass());
+		AITEST_TRUE("Path segment 1 instance type should be nullptr", Path.GetSegment(1).GetInstanceStruct() == nullptr);
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_PropertyPathObject, "System.EzAbility.PropertyPath.Object");
+
+struct FEzAbilityTest_PropertyPathWrongObject : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityPropertyPath 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);
+
+		UEzAbilityTest_PropertyObject* Object = NewObject<UEzAbilityTest_PropertyObject>();
+
+		Object->InstancedObject = NewObject<UEzAbilityTest_PropertyObjectInstancedWithB>();
+		{
+			FString ResolveErrors;
+			TArray<FEzAbilityPropertyPathIndirection> Indirections;
+			const bool bResolveResult = Path.ResolveIndirectionsWithValue(FEzAbilityDataView(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() == EEzAbilityPropertyAccessType::ObjectInstance);
+			AITEST_TRUE("Object ", Indirections[0].GetContainerStruct() == Object->GetClass());
+			AITEST_TRUE("Object ", Indirections[0].GetInstanceStruct() == UEzAbilityTest_PropertyObjectInstancedWithB::StaticClass());
+			AITEST_EQUAL("Should not have error", ResolveErrors.Len(), 0);
+		}
+
+		Object->InstancedObject = NewObject<UEzAbilityTest_PropertyObjectInstanced>();
+		{
+			FString ResolveErrors;
+			TArray<FEzAbilityPropertyPathIndirection> Indirections;
+			const bool bResolveResult = Path.ResolveIndirectionsWithValue(FEzAbilityDataView(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(FEzAbilityTest_PropertyPathWrongObject, "System.EzAbility.PropertyPath.WrongObject");
+
+struct FEzAbilityTest_PropertyPathArray : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityPropertyPath 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);
+
+		UEzAbilityTest_PropertyObject* Object = NewObject<UEzAbilityTest_PropertyObject>();
+		Object->ArrayOfInts.Add(42);
+		Object->ArrayOfInts.Add(123);
+
+		FString ResolveErrors;
+		TArray<FEzAbilityPropertyPathIndirection> Indirections;
+		const bool bResolveResult = Path.ResolveIndirectionsWithValue(FEzAbilityDataView(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(), EEzAbilityPropertyAccessType::IndexArray);
+		AITEST_EQUAL("Indirection 1 should be Offset type", Indirections[1].GetAccessType(), EEzAbilityPropertyAccessType::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(FEzAbilityTest_PropertyPathArray, "System.EzAbility.PropertyPath.Array");
+
+struct FEzAbilityTest_PropertyPathArrayInvalidIndex : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityPropertyPath 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);
+
+		UEzAbilityTest_PropertyObject* Object = NewObject<UEzAbilityTest_PropertyObject>();
+		Object->ArrayOfInts.Add(42);
+		Object->ArrayOfInts.Add(123);
+
+		FString ResolveErrors;
+		TArray<FEzAbilityPropertyPathIndirection> Indirections;
+		const bool bResolveResult = Path.ResolveIndirectionsWithValue(FEzAbilityDataView(Object), Indirections, &ResolveErrors);
+
+		AITEST_FALSE("Resolve path should fail", bResolveResult);
+		
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_PropertyPathArrayInvalidIndex, "System.EzAbility.PropertyPath.ArrayInvalidIndex");
+
+struct FEzAbilityTest_PropertyPathArrayOfStructs : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityPropertyPath Path1;
+		Path1.FromString(TEXT("ArrayOfStruct[0].B"));
+
+		FEzAbilityPropertyPath Path2;
+		Path2.FromString(TEXT("ArrayOfStruct[2].StructB.B"));
+
+		UEzAbilityTest_PropertyObject* Object = NewObject<UEzAbilityTest_PropertyObject>();
+		Object->ArrayOfStruct.AddDefaulted_GetRef().B = 3;
+		Object->ArrayOfStruct.AddDefaulted();
+		Object->ArrayOfStruct.AddDefaulted_GetRef().StructB.B = 42;
+
+		{
+			FString ResolveErrors;
+			TArray<FEzAbilityPropertyPathIndirection> Indirections;
+			const bool bResolveResult = Path1.ResolveIndirectionsWithValue(FEzAbilityDataView(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(), EEzAbilityPropertyAccessType::IndexArray);
+			AITEST_EQUAL("Indirection 1 should be Offset type", Indirections[1].GetAccessType(), EEzAbilityPropertyAccessType::Offset);
+			AITEST_EQUAL("Indirection 2 should be Offset type", Indirections[2].GetAccessType(), EEzAbilityPropertyAccessType::Offset);
+
+			const int32 Value = *reinterpret_cast<const int32*>(Indirections[2].GetPropertyAddress());
+			AITEST_EQUAL("Value should be 3", Value, 3);
+		}
+
+		{
+			FString ResolveErrors;
+			TArray<FEzAbilityPropertyPathIndirection> Indirections;
+			const bool bResolveResult = Path2.ResolveIndirectionsWithValue(FEzAbilityDataView(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(), EEzAbilityPropertyAccessType::IndexArray);
+			AITEST_EQUAL("Indirection 1 should be Offset type", Indirections[1].GetAccessType(), EEzAbilityPropertyAccessType::Offset);
+			AITEST_EQUAL("Indirection 2 should be Offset type", Indirections[2].GetAccessType(), EEzAbilityPropertyAccessType::Offset);
+			AITEST_EQUAL("Indirection 3 should be Offset type", Indirections[3].GetAccessType(), EEzAbilityPropertyAccessType::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(FEzAbilityTest_PropertyPathArrayOfStructs, "System.EzAbility.PropertyPath.ArrayOfStructs");
+
+struct FEzAbilityTest_PropertyPathArrayOfInstancedObjects : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityPropertyPath Path;
+		Path.FromString(TEXT("ArrayOfInstancedStructs[0].B"));
+
+		FEzAbilityTest_PropertyStruct Struct;
+		Struct.B = 123;
+		
+		UEzAbilityTest_PropertyObject* Object = NewObject<UEzAbilityTest_PropertyObject>();
+		Object->ArrayOfInstancedStructs.Emplace(FConstStructView::Make(Struct));
+
+		const bool bUpdateResult = Path.UpdateSegmentsFromValue(FEzAbilityDataView(Object));
+		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 FEzAbilityTest_PropertyStruct", Path.GetSegment(0).GetInstanceStruct() == FEzAbilityTest_PropertyStruct::StaticStruct());
+		AITEST_TRUE("Path segment 1 instance type should be nullptr", Path.GetSegment(1).GetInstanceStruct() == nullptr);
+
+		{
+			FString ResolveErrors;
+			TArray<FEzAbilityPropertyPathIndirection> Indirections;
+			const bool bResolveResult = Path.ResolveIndirections(UEzAbilityTest_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(), EEzAbilityPropertyAccessType::IndexArray);
+			AITEST_EQUAL("Indirection 1 should be StructInstance type", Indirections[1].GetAccessType(), EEzAbilityPropertyAccessType::StructInstance);
+			AITEST_EQUAL("Indirection 2 should be Offset type", Indirections[2].GetAccessType(), EEzAbilityPropertyAccessType::Offset);
+		}
+
+		{
+			FString ResolveErrors;
+			TArray<FEzAbilityPropertyPathIndirection> Indirections;
+			const bool bResolveResult = Path.ResolveIndirectionsWithValue(FEzAbilityDataView(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(), EEzAbilityPropertyAccessType::IndexArray);
+			AITEST_EQUAL("Indirection 1 should be StructInstance type", Indirections[1].GetAccessType(), EEzAbilityPropertyAccessType::StructInstance);
+			AITEST_EQUAL("Indirection 2 should be Offset type", Indirections[2].GetAccessType(), EEzAbilityPropertyAccessType::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(FEzAbilityTest_PropertyPathArrayOfInstancedObjects, "System.EzAbility.PropertyPath.ArrayOfInstancedObjects");
+
+struct FEzAbilityTest_BindingsCompiler : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityCompilerLog Log;
+		FEzAbilityPropertyBindings Bindings;
+		FEzAbilityPropertyBindingCompiler BindingCompiler;
+
+		const bool bInitResult = BindingCompiler.Init(Bindings, Log);
+		AITEST_TRUE("Expect init to succeed", bInitResult);
+
+		FEzAbilityBindableStructDesc SourceADesc;
+		SourceADesc.Name = FName(TEXT("SourceA"));
+		SourceADesc.Struct = TBaseStructure<FEzAbilityTest_PropertyCopy>::Get();
+		SourceADesc.DataSource = EEzAbilityBindableStructSource::Parameter;
+		SourceADesc.DataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::ContextData, 0); // Used as index to SourceViews below.
+		SourceADesc.ID = FGuid::NewGuid();
+
+		FEzAbilityBindableStructDesc SourceBDesc;
+		SourceBDesc.Name = FName(TEXT("SourceB"));
+		SourceBDesc.Struct = TBaseStructure<FEzAbilityTest_PropertyCopy>::Get();
+		SourceBDesc.DataSource = EEzAbilityBindableStructSource::Parameter;
+		SourceBDesc.DataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::ContextData, 1); // Used as index to SourceViews below.
+		SourceBDesc.ID = FGuid::NewGuid();
+
+		FEzAbilityBindableStructDesc TargetDesc;
+		TargetDesc.Name = FName(TEXT("Target"));
+		TargetDesc.Struct = TBaseStructure<FEzAbilityTest_PropertyCopy>::Get();
+		TargetDesc.DataSource = EEzAbilityBindableStructSource::Parameter;
+		TargetDesc.ID = FGuid::NewGuid();
+		
+		const int32 SourceAIndex = BindingCompiler.AddSourceStruct(SourceADesc);
+		const int32 SourceBIndex = BindingCompiler.AddSourceStruct(SourceBDesc);
+
+		TArray<FEzAbilityPropertyPathBinding> PropertyBindings;
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceBDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("Array[1]")));
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceADesc.ID, TEXT("Item.B"), TargetDesc.ID, TEXT("Array[1].B")));
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceADesc.ID, TEXT("Array"), TargetDesc.ID, TEXT("Array")));
+
+		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);
+
+		FEzAbilityTest_PropertyCopy SourceA;
+		SourceA.Item.B = 123;
+		SourceA.Array.AddDefaulted_GetRef().A = 1;
+		SourceA.Array.AddDefaulted_GetRef().B = 2;
+
+		FEzAbilityTest_PropertyCopy SourceB;
+		SourceB.Item.A = 41;
+		SourceB.Item.B = 42;
+
+		FEzAbilityTest_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<FEzAbilityDataView> SourceViews;
+		SourceViews.SetNum(Bindings.GetSourceStructNum());
+		SourceViews[SourceAIndex] = FEzAbilityDataView(FStructView::Make(SourceA));
+		SourceViews[SourceBIndex] = FEzAbilityDataView(FStructView::Make(SourceB));
+		FEzAbilityDataView TargetView(FStructView::Make(Target));
+
+		bool bCopyResult = true;
+		for (const FEzAbilityPropertyCopy& Copy : Bindings.GetBatchCopies(FEzAbilityIndex16(CopyBatchIndex)))
+		{
+			bCopyResult &= Bindings.CopyProperty(Copy, SourceViews[Copy.SourceDataHandle.GetIndex()], TargetView);
+		}
+		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(FEzAbilityTest_BindingsCompiler, "System.EzAbility.BindingsCompiler");
+
+// struct FEzAbilityTest_PropertyFunctions : FAITestBase
+// {
+// 	virtual bool InstantTest() override
+// 	{
+// 		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+// 		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+// 		FEzAbilityPropertyPathSegment PathSegmentToFuncResult = FEzAbilityPropertyPathSegment(TEXT("Result"));
+//
+// 		// Condition with property function binding.
+// 		{
+// 			TEzAbilityEditorNode<FAbilityCompareIntCondition>& EnterCond = Root.AddEnterCondition<FAbilityCompareIntCondition>(EGenericCheck::Equal);
+// 			EnterCond.GetInstanceData().Right = 1;
+// 			EditorData.AddPropertyBinding(CastChecked<UScriptStruct>(FTestPropertyFunction::StaticStruct()), {PathSegmentToFuncResult}, FEzAbilityPropertyPath(EnterCond.ID, TEXT("Left")));
+// 		}
+//
+// 		// Task with multiple nested property function bindings.
+// 		auto& TaskA = Root.AddTask<FAbilityTestTask_PrintAndResetValue>(FName(TEXT("TaskA")));
+// 		constexpr int32 TaskAPropertyFunctionsAmount = 10;
+// 		{
+// 			EditorData.AddPropertyBinding(CastChecked<UScriptStruct>(FTestPropertyFunction::StaticStruct()), {PathSegmentToFuncResult}, FEzAbilityPropertyPath(TaskA.ID, TEXT("Value")));
+// 		
+// 			for (int32 i = 0; i < TaskAPropertyFunctionsAmount - 1; ++i)
+// 			{
+// 				const FEzAbilityPropertyPathBinding& LastBinding = EditorData.GetPropertyEditorBindings()->GetBindings().Last();
+// 				const FGuid LastBindingPropertyFuncID = LastBinding.GetPropertyFunctionNode().Get<const FEzAbilityEditorNode>().ID;
+// 				EditorData.AddPropertyBinding(CastChecked<UScriptStruct>(FTestPropertyFunction::StaticStruct()), {PathSegmentToFuncResult}, FEzAbilityPropertyPath(LastBindingPropertyFuncID, TEXT("Input")));
+// 			}
+// 		}
+//
+// 		// Task bound to state parameter with multiple nested property function bindings.
+// 		auto& TaskB = Root.AddTask<FAbilityTestTask_PrintAndResetValue>(FName(TEXT("TaskB")));
+// 		constexpr int32 ParameterPropertyFunctionsAmount = 5;
+// 		{
+// 			Root.Parameters.Parameters.AddProperty(FName(TEXT("Int")), EPropertyBagPropertyType::Int32);
+// 			const FEzAbilityPropertyPath PathToProperty = FEzAbilityPropertyPath(Root.Parameters.ID, TEXT("Int"));
+// 			EditorData.AddPropertyBinding(PathToProperty, FEzAbilityPropertyPath(TaskB.ID, TEXT("Value")));
+// 			EditorData.AddPropertyBinding(CastChecked<UScriptStruct>(FTestPropertyFunction::StaticStruct()), {PathSegmentToFuncResult}, PathToProperty);
+// 		
+// 			for (int32 i = 0; i < ParameterPropertyFunctionsAmount - 1; ++i)
+// 			{
+// 				const FEzAbilityPropertyPathBinding& LastBinding = EditorData.GetPropertyEditorBindings()->GetBindings().Last();
+// 				const FGuid LastBindingPropertyFuncID = LastBinding.GetPropertyFunctionNode().Get<const FEzAbilityEditorNode>().ID;
+// 				EditorData.AddPropertyBinding(CastChecked<UScriptStruct>(FTestPropertyFunction::StaticStruct()), {PathSegmentToFuncResult}, FEzAbilityPropertyPath(LastBindingPropertyFuncID, TEXT("Input")));
+// 			}
+// 		}
+//
+// 		FEzAbilityCompilerLog Log;
+// 		FEzAbilityCompiler Compiler(Log);
+// 		const bool bResult = Compiler.Compile(EzAbility);
+// 		AITEST_TRUE("EzAbility should get compiled", bResult);
+//
+// 		FEzAbilityInstanceData InstanceData;
+// 		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 		const bool bInitSucceeded = Exec.IsValid();
+// 		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+//
+// 		FEzAbilityParameter Parameter;
+// 		FText OutText;
+// 		Exec.Start(&EzAbility, Parameter, OutText);;
+// 		AITEST_TRUE(*FString::Printf(TEXT("EzAbility TaskA should enter state with value %d"), TaskAPropertyFunctionsAmount), Exec.Expect(TaskA.GetName(), *FString::Printf(TEXT("EnterState%d"), TaskAPropertyFunctionsAmount)));
+// 		AITEST_TRUE(*FString::Printf(TEXT("EzAbility TaskB should enter state with value %d"), ParameterPropertyFunctionsAmount), Exec.Expect(TaskB.GetName(), *FString::Printf(TEXT("EnterState%d"), ParameterPropertyFunctionsAmount)));
+// 		Exec.LogClear();
+//
+// 		Exec.Tick(0.1f);
+// 		AITEST_TRUE(*FString::Printf(TEXT("EzAbility TaskA should tick with value %d"), TaskAPropertyFunctionsAmount), Exec.Expect(TaskA.GetName(), *FString::Printf(TEXT("Tick%d"), TaskAPropertyFunctionsAmount)));
+// 		AITEST_TRUE(*FString::Printf(TEXT("EzAbility TaskB should tick with value %d"), ParameterPropertyFunctionsAmount), Exec.Expect(TaskB.GetName(), *FString::Printf(TEXT("Tick%d"), ParameterPropertyFunctionsAmount)));
+// 		Exec.LogClear();
+//
+// 		Exec.Stop(EAbilityRunStatus::Stopped);
+// 		AITEST_TRUE(*FString::Printf(TEXT("EzAbility TaskA should exit state with value %d"), TaskAPropertyFunctionsAmount), Exec.Expect(TaskA.GetName(), *FString::Printf(TEXT("ExitState%d"), TaskAPropertyFunctionsAmount)));
+// 		AITEST_TRUE(*FString::Printf(TEXT("EzAbility TaskB should exit state with value %d"), ParameterPropertyFunctionsAmount), Exec.Expect(TaskB.GetName(), *FString::Printf(TEXT("ExitState%d"), ParameterPropertyFunctionsAmount)));
+// 		Exec.LogClear();
+//
+// 		return true;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_PropertyFunctions, "System.EzAbility.PropertyFunctions");
+
+struct FEzAbilityTest_CopyObjects : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityCompilerLog Log;
+		FEzAbilityPropertyBindings Bindings;
+		FEzAbilityPropertyBindingCompiler BindingCompiler;
+
+		const bool bInitResult = BindingCompiler.Init(Bindings, Log);
+		AITEST_TRUE("Expect init to succeed", bInitResult);
+
+		FEzAbilityBindableStructDesc SourceDesc;
+		SourceDesc.Name = FName(TEXT("Source"));
+		SourceDesc.Struct = TBaseStructure<FEzAbilityTest_PropertyCopyObjects>::Get();
+		SourceDesc.DataSource = EEzAbilityBindableStructSource::Parameter;
+		SourceDesc.DataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::ContextData, 0); // Used as index to SourceViews below.
+		SourceDesc.ID = FGuid::NewGuid();
+
+		FEzAbilityBindableStructDesc TargetADesc;
+		TargetADesc.Name = FName(TEXT("TargetA"));
+		TargetADesc.Struct = TBaseStructure<FEzAbilityTest_PropertyCopyObjects>::Get();
+		TargetADesc.DataSource = EEzAbilityBindableStructSource::Parameter;
+		TargetADesc.ID = FGuid::NewGuid();
+
+		FEzAbilityBindableStructDesc TargetBDesc;
+		TargetBDesc.Name = FName(TEXT("TargetB"));
+		TargetBDesc.Struct = TBaseStructure<FEzAbilityTest_PropertyCopyObjects>::Get();
+		TargetBDesc.DataSource = EEzAbilityBindableStructSource::Parameter;
+		TargetBDesc.ID = FGuid::NewGuid();
+
+		const int32 SourceIndex = BindingCompiler.AddSourceStruct(SourceDesc);
+
+		TArray<FEzAbilityPropertyPathBinding> PropertyBindings;
+		// One-to-one copy from source to target A
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("Object"), TargetADesc.ID, TEXT("Object")));
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("SoftObject"), TargetADesc.ID, TEXT("SoftObject")));
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("Class"), TargetADesc.ID, TEXT("Class")));
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("SoftClass"), TargetADesc.ID, TEXT("SoftClass")));
+
+		// Cross copy from source to target B
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("SoftObject"), TargetBDesc.ID, TEXT("Object")));
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("Object"), TargetBDesc.ID, TEXT("SoftObject")));
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("SoftClass"), TargetBDesc.ID, TEXT("Class")));
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("Class"), TargetBDesc.ID, TEXT("SoftClass")));
+		
+		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);
+
+		UEzAbilityTest_PropertyObject* ObjectA = NewObject<UEzAbilityTest_PropertyObject>();
+		UEzAbilityTest_PropertyObject2* ObjectB = NewObject<UEzAbilityTest_PropertyObject2>();
+		
+		FEzAbilityTest_PropertyCopyObjects Source;
+		Source.Object = ObjectA;
+		Source.SoftObject = ObjectB;
+		Source.Class = UEzAbilityTest_PropertyObject::StaticClass();
+		Source.SoftClass = UEzAbilityTest_PropertyObject::StaticClass();
+
+		AITEST_TRUE("SourceIndex should be less than max number of source structs.", SourceIndex < Bindings.GetSourceStructNum());
+
+		TArray<FEzAbilityDataView> SourceViews;
+		SourceViews.SetNum(Bindings.GetSourceStructNum());
+		SourceViews[SourceIndex] = FEzAbilityDataView(FStructView::Make(Source));
+
+		FEzAbilityTest_PropertyCopyObjects TargetA;
+		bool bCopyResultA = true;
+		for (const FEzAbilityPropertyCopy& Copy : Bindings.GetBatchCopies(FEzAbilityIndex16(TargetACopyBatchIndex)))
+		{
+			bCopyResultA &= Bindings.CopyProperty(Copy, SourceViews[Copy.SourceDataHandle.GetIndex()], FStructView::Make(TargetA));
+		}
+		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;
+		
+		FEzAbilityTest_PropertyCopyObjects TargetB;
+		bool bCopyResultB = true;
+		for (const FEzAbilityPropertyCopy& Copy : Bindings.GetBatchCopies(FEzAbilityIndex16(TargetBCopyBatchIndex)))
+		{
+			bCopyResultB &= Bindings.CopyProperty(Copy, SourceViews[Copy.SourceDataHandle.GetIndex()], FStructView::Make(TargetB));
+		}
+		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);
+
+		FEzAbilityTest_PropertyCopyObjects TargetC;
+		bool bCopyResultC = true;
+		for (const FEzAbilityPropertyCopy& Copy : Bindings.GetBatchCopies(FEzAbilityIndex16(TargetACopyBatchIndex)))
+		{
+			bCopyResultB &= Bindings.CopyProperty(Copy, SourceViews[Copy.SourceDataHandle.GetIndex()], FStructView::Make(TargetC));
+		}
+
+		
+		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(FEzAbilityTest_CopyObjects, "System.EzAbility.CopyObjects");
+
+struct FEzAbilityTest_References : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityCompilerLog Log;
+		FEzAbilityPropertyBindings Bindings;
+		FEzAbilityPropertyBindingCompiler BindingCompiler;
+
+		const bool bInitResult = BindingCompiler.Init(Bindings, Log);
+		AITEST_TRUE("Expect init to succeed", bInitResult);
+
+		FEzAbilityBindableStructDesc SourceDesc;
+		SourceDesc.Name = FName(TEXT("Source"));
+		SourceDesc.Struct = TBaseStructure<FEzAbilityTest_PropertyRefSourceStruct>::Get();
+		SourceDesc.DataSource = EEzAbilityBindableStructSource::Parameter;
+		SourceDesc.DataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::ContextData, 0);
+		SourceDesc.ID = FGuid::NewGuid();
+		BindingCompiler.AddSourceStruct(SourceDesc);
+
+		FEzAbilityBindableStructDesc TargetDesc;
+		TargetDesc.Name = FName(TEXT("Target"));
+		TargetDesc.Struct = TBaseStructure<FEzAbilityTest_PropertyRefTargetStruct>::Get();
+		TargetDesc.DataSource = EEzAbilityBindableStructSource::Parameter;
+		TargetDesc.ID = FGuid::NewGuid();
+		
+		TArray<FEzAbilityPropertyPathBinding> PropertyBindings;
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("RefToStruct")));
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("Item.A"), TargetDesc.ID, TEXT("RefToInt")));
+		PropertyBindings.Add(UE::EzAbility::Tests::MakeBinding(SourceDesc.ID, TEXT("Array"), TargetDesc.ID, TEXT("RefToStructArray")));
+
+		FEzAbilityTest_PropertyRefSourceStruct Source;
+		FEzAbilityDataView SourceView = FEzAbilityDataView(FStructView::Make(Source));
+
+		FEzAbilityTest_PropertyRefTargetStruct Target;
+		FEzAbilityDataView TargetView(FStructView::Make(Target));
+
+		TMap<FGuid, const FEzAbilityDataView> IDToStructValue;
+		IDToStructValue.Emplace(SourceDesc.ID, SourceView);
+		IDToStructValue.Emplace(TargetDesc.ID, TargetView);
+
+		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);
+
+		{
+			const FEzAbilityPropertyAccess* PropertyAccess = Bindings.GetPropertyAccess(Target.RefToStruct);
+			AITEST_NOT_NULL("GetPropertyAccess should succeed", PropertyAccess);
+			
+			FEzAbilityTest_PropertyStruct* Reference = Bindings.GetMutablePropertyPtr<FEzAbilityTest_PropertyStruct>(SourceView, *PropertyAccess);
+			AITEST_EQUAL("Expect RefToStruct to point to SourceA.Item", Reference, &Source.Item);
+		}
+
+		{
+			const FEzAbilityPropertyAccess* 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 FEzAbilityPropertyAccess* PropertyAccess = Bindings.GetPropertyAccess(Target.RefToStructArray);
+			AITEST_NOT_NULL("GetPropertyAccess should succeed", PropertyAccess);
+
+			TArray<FEzAbilityTest_PropertyStruct>* Reference = Bindings.GetMutablePropertyPtr<TArray<FEzAbilityTest_PropertyStruct>>(SourceView, *PropertyAccess);
+			AITEST_EQUAL("Expect RefToStructArray to point to SourceA.Array", Reference, &Source.Array);
+		}
+		
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_References, "System.EzAbility.References");
+
+struct FEzAbilityTest_ReferencesConstness : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		FEzAbilityCompilerLog Log;
+		FEzAbilityPropertyBindings Bindings;
+		FEzAbilityPropertyBindingCompiler BindingCompiler;
+
+		const bool bInitResult = BindingCompiler.Init(Bindings, Log);
+		AITEST_TRUE("Expect init to succeed", bInitResult);
+
+		FEzAbilityBindableStructDesc SourceAsTaskDesc;
+		SourceAsTaskDesc.Name = FName(TEXT("SourceTask"));
+		SourceAsTaskDesc.Struct = TBaseStructure<FEzAbilityTest_PropertyRefSourceStruct>::Get();
+		SourceAsTaskDesc.DataSource = EEzAbilityBindableStructSource::Task;
+		SourceAsTaskDesc.DataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::ContextData, 0);
+		SourceAsTaskDesc.ID = FGuid::NewGuid();
+		BindingCompiler.AddSourceStruct(SourceAsTaskDesc);
+
+		FEzAbilityBindableStructDesc SourceAsContextDesc;
+		SourceAsContextDesc.Name = FName(TEXT("SourceContext"));
+		SourceAsContextDesc.Struct = TBaseStructure<FEzAbilityTest_PropertyRefSourceStruct>::Get();
+		SourceAsContextDesc.DataSource = EEzAbilityBindableStructSource::Context;
+		SourceAsContextDesc.DataHandle = FEzAbilityDataHandle(EEzAbilityDataSourceType::ContextData, 0);
+		SourceAsContextDesc.ID = FGuid::NewGuid();
+		BindingCompiler.AddSourceStruct(SourceAsContextDesc);
+
+		FEzAbilityBindableStructDesc TargetDesc;
+		TargetDesc.Name = FName(TEXT("Target"));
+		TargetDesc.Struct = TBaseStructure<FEzAbilityTest_PropertyRefTargetStruct>::Get();
+		TargetDesc.DataSource = EEzAbilityBindableStructSource::Parameter;
+		TargetDesc.ID = FGuid::NewGuid();
+		
+		FEzAbilityPropertyPathBinding TaskPropertyBinding = UE::EzAbility::Tests::MakeBinding(SourceAsTaskDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("RefToStruct"));
+		FEzAbilityPropertyPathBinding TaskOutputPropertyBinding = UE::EzAbility::Tests::MakeBinding(SourceAsTaskDesc.ID, TEXT("OutputItem"), TargetDesc.ID, TEXT("RefToStruct"));
+
+		FEzAbilityPropertyPathBinding ContextPropertyBinding = UE::EzAbility::Tests::MakeBinding(SourceAsTaskDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("RefToStruct"));
+		FEzAbilityPropertyPathBinding ContextOutputPropertyBinding = UE::EzAbility::Tests::MakeBinding(SourceAsTaskDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("RefToStruct"));
+
+		FEzAbilityTest_PropertyRefSourceStruct SourceAsTask;
+		FEzAbilityDataView SourceAsTaskView(FStructView::Make(SourceAsTask));
+
+		FEzAbilityTest_PropertyRefSourceStruct SourceAsContext;
+		FEzAbilityDataView SourceAsContextView(FStructView::Make(SourceAsContext));
+
+		FEzAbilityTest_PropertyRefTargetStruct Target;
+		FEzAbilityDataView TargetView(FStructView::Make(Target));
+
+		TMap<FGuid, const FEzAbilityDataView> IDToStructValue;
+		IDToStructValue.Emplace(SourceAsTaskDesc.ID, SourceAsTaskView);
+		IDToStructValue.Emplace(SourceAsContextDesc.ID, SourceAsContextView);
+		IDToStructValue.Emplace(TargetDesc.ID, TargetView);
+
+		{
+			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(FEzAbilityTest_ReferencesConstness, "System.EzAbility.ReferencesConstness");
+
+struct FEzAbilityTest_FollowTransitions : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+
+		EditorData.RootParameters.Parameters.AddProperty(FName(TEXT("Int")), EPropertyBagPropertyType::Int32);
+		EditorData.RootParameters.Parameters.SetValueInt32(FName(TEXT("Int")), 1);
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& StateTrans = Root.AddChildState(FName(TEXT("Trans")));
+		UEzAbilityState& StateA = Root.AddChildState(FName(TEXT("A")));
+		UEzAbilityState& StateB = Root.AddChildState(FName(TEXT("B")));
+		UEzAbilityState& StateC = Root.AddChildState(FName(TEXT("C")));
+
+		// Root
+
+		// Trans
+		{
+			StateTrans.SelectionBehavior = EEzAbilityStateSelectionBehavior::TryFollowTransitions;
+
+			{
+				// This transition should be skipped due to the condition
+				FEzAbilityTransition& TransA = StateTrans.AddTransition(EEzAbilityTransitionTrigger::OnTick, EEzAbilityTransitionType::GotoState, &StateA);
+				TEzAbilityEditorNode<FAbilityCompareIntCondition>& TransIntCond = TransA.AddCondition<FAbilityCompareIntCondition>(EGenericCheck::Equal);
+				TransIntCond.GetInstanceData().Right = 0;
+				EditorData.AddPropertyBinding(
+					FEzAbilityPropertyPath(EditorData.RootParameters.ID, TEXT("Int")),
+					FEzAbilityPropertyPath(TransIntCond.ID, TEXT("Left")));
+			}
+
+			{
+				// This transition leads to selection, but will be overridden.
+				FEzAbilityTransition& TransB = StateTrans.AddTransition(EEzAbilityTransitionTrigger::OnTick, EEzAbilityTransitionType::GotoState, &StateB);
+				TransB.Priority = EEzAbilityTransitionPriority::Normal;
+				TEzAbilityEditorNode<FAbilityCompareIntCondition>& TransIntCond = TransB.AddCondition<FAbilityCompareIntCondition>(EGenericCheck::Equal);
+				TransIntCond.GetInstanceData().Right = 1;
+				EditorData.AddPropertyBinding(
+					FEzAbilityPropertyPath(EditorData.RootParameters.ID, TEXT("Int")),
+					FEzAbilityPropertyPath(TransIntCond.ID, TEXT("Left")));
+			}
+
+			{
+				// This transition is selected, should override previous one due to priority.
+				FEzAbilityTransition& TransC = StateTrans.AddTransition(EEzAbilityTransitionTrigger::OnTick, EEzAbilityTransitionType::GotoState, &StateC);
+				TransC.Priority = EEzAbilityTransitionPriority::High;
+				TEzAbilityEditorNode<FAbilityCompareIntCondition>& TransIntCond = TransC.AddCondition<FAbilityCompareIntCondition>(EGenericCheck::Equal);
+				TransIntCond.GetInstanceData().Right = 1;
+				EditorData.AddPropertyBinding(
+					FEzAbilityPropertyPath(EditorData.RootParameters.ID, TEXT("Int")),
+					FEzAbilityPropertyPath(TransIntCond.ID, TEXT("Left")));
+			}
+		}
+
+		auto& TaskA = StateA.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskA")));
+		auto& TaskB = StateB.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskB")));
+		auto& TaskC = StateC.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskC")));
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString TickStr(TEXT("Tick"));
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString ExitStateStr(TEXT("ExitState"));
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_FALSE("EzAbility TaskA should not enter state", Exec.Expect(TaskA.GetName(), EnterStateStr));
+		AITEST_FALSE("EzAbility TaskB should not enter state", Exec.Expect(TaskB.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility TaskC should enter state", Exec.Expect(TaskC.GetName(), EnterStateStr));
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_FollowTransitions, "System.EzAbility.FollowTransitions");
+
+struct FEzAbilityTest_InfiniteLoop : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+
+		EditorData.RootParameters.Parameters.AddProperty(FName(TEXT("Int")), EPropertyBagPropertyType::Int32);
+		EditorData.RootParameters.Parameters.SetValueInt32(FName(TEXT("Int")), 1);
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& StateA = Root.AddChildState(FName(TEXT("A")));
+		UEzAbilityState& StateB = StateA.AddChildState(FName(TEXT("B")));
+
+		// Root
+
+		// State A
+		{
+			StateA.SelectionBehavior = EEzAbilityStateSelectionBehavior::TryFollowTransitions;
+			{
+				// A -> B
+				FEzAbilityTransition& Trans = StateA.AddTransition(EEzAbilityTransitionTrigger::OnTick, EEzAbilityTransitionType::GotoState, &StateB);
+				TEzAbilityEditorNode<FAbilityCompareIntCondition>& TransIntCond = Trans.AddCondition<FAbilityCompareIntCondition>(EGenericCheck::Equal);
+				TransIntCond.GetInstanceData().Right = 1;
+				EditorData.AddPropertyBinding(
+					FEzAbilityPropertyPath(EditorData.RootParameters.ID, TEXT("Int")),
+					FEzAbilityPropertyPath(TransIntCond.ID, TEXT("Left")));
+			}
+		}
+
+		// State B
+		{
+			StateB.SelectionBehavior = EEzAbilityStateSelectionBehavior::TryFollowTransitions;
+			{
+				// B -> A
+				FEzAbilityTransition& Trans = StateB.AddTransition(EEzAbilityTransitionTrigger::OnTick, EEzAbilityTransitionType::GotoState, &StateA);
+				TEzAbilityEditorNode<FAbilityCompareIntCondition>& TransIntCond = Trans.AddCondition<FAbilityCompareIntCondition>(EGenericCheck::Equal);
+				TransIntCond.GetInstanceData().Right = 1;
+				EditorData.AddPropertyBinding(
+					FEzAbilityPropertyPath(EditorData.RootParameters.ID, TEXT("Int")),
+					FEzAbilityPropertyPath(TransIntCond.ID, TEXT("Left")));
+			}
+		}
+		
+		auto& TaskA = StateA.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskA")));
+		auto& TaskB = StateB.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskB")));
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility 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);
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_EQUAL("Start should fail", Status, EAbilityRunStatus::Failed);
+		Exec.LogClear();
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_InfiniteLoop, "System.EzAbility.InfiniteLoop");
+
+
+//
+// The stop tests test how the combinations of execution path to stop the tree are reported on ExitState() transition.  
+//
+struct FEzAbilityTest_Stop : FAITestBase
+{
+	UEzAbility& SetupTree()
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& StateA = Root.AddChildState(FName(TEXT("A")));
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& TaskA = StateA.AddTask<FAbilityTestTask_Stand>(TaskAName);
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& GlobalTask = EditorData.AddGlobalTask<FAbilityTestTask_Stand>(GlobalTaskName);
+
+		// Transition success 
+		StateA.AddTransition(EEzAbilityTransitionTrigger::OnStateSucceeded, EEzAbilityTransitionType::Succeeded);
+		StateA.AddTransition(EEzAbilityTransitionTrigger::OnStateFailed, EEzAbilityTransitionType::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 EzAbility;
+	}
+	
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = SetupTree();
+		
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString TickStr(TEXT("Tick"));
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString ExitStateStr(TEXT("ExitState"));
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_EQUAL("Start should be running", Status, EAbilityRunStatus::Running);
+		AITEST_TRUE("EzAbility GlobalTask should enter state", Exec.Expect(GlobalTaskName, EnterStateStr));
+		AITEST_TRUE("EzAbility 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("EzAbility GlobalTask should get exit state expectedly", Exec.Expect(GlobalTaskName, ExpectedExitStatusStr));
+		AITEST_TRUE("EzAbility 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"));
+	
+	EAbilityRunStatus NormalTaskStatus = EAbilityRunStatus::Succeeded;
+	EAbilityRunStatus NormalTaskEnterStatus = EAbilityRunStatus::Running;
+	int32 NormalTaskTicks = 1;
+
+	EAbilityRunStatus GlobalTaskStatus = EAbilityRunStatus::Succeeded;
+	EAbilityRunStatus GlobalTaskEnterStatus = EAbilityRunStatus::Running;
+	int32 GlobalTaskTicks = 1;
+
+	EAbilityRunStatus ExpectedStatusAfterTick = EAbilityRunStatus::Succeeded;
+
+	FString ExpectedExitStatusStr = TEXT("ExitSucceeded");
+};
+
+struct FEzAbilityTest_Stop_NormalSucceeded : FEzAbilityTest_Stop
+{
+	virtual bool SetUp() override
+	{
+		// Normal task completes as succeeded.
+		NormalTaskStatus = EAbilityRunStatus::Succeeded;
+		NormalTaskTicks = 1;
+
+		// Global task completes later
+		GlobalTaskTicks = 2;
+
+		// Tree should complete as succeeded.
+		ExpectedStatusAfterTick = EAbilityRunStatus::Succeeded;
+		
+		// Tasks should have Transition.CurrentRunStatus as succeeded 
+		ExpectedExitStatusStr = TEXT("ExitSucceeded");
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Stop_NormalSucceeded, "System.EzAbility.Stop.NormalSucceeded");
+
+struct FEzAbilityTest_Stop_NormalFailed : FEzAbilityTest_Stop
+{
+	virtual bool SetUp() override
+	{
+		// Normal task completes as failed.
+		NormalTaskStatus = EAbilityRunStatus::Failed;
+		NormalTaskTicks = 1;
+
+		// Global task completes later.
+		GlobalTaskTicks = 2;
+
+		// Tree should complete as failed.
+		ExpectedStatusAfterTick = EAbilityRunStatus::Failed;
+		
+		// Tasks should have Transition.CurrentRunStatus as failed.
+		ExpectedExitStatusStr = TEXT("ExitFailed");
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Stop_NormalFailed, "System.EzAbility.Stop.NormalFailed");
+
+
+struct FEzAbilityTest_Stop_GlobalSucceeded : FEzAbilityTest_Stop
+{
+	virtual bool SetUp() override
+	{
+		// Normal task completes later.
+		NormalTaskTicks = 2;
+
+		// Global task completes as succeeded.
+		GlobalTaskStatus = EAbilityRunStatus::Succeeded;
+		GlobalTaskTicks = 1;
+
+		// Tree should complete as succeeded.
+		ExpectedStatusAfterTick = EAbilityRunStatus::Succeeded;
+		
+		// Tasks should have Transition.CurrentRunStatus as succeeded.
+		ExpectedExitStatusStr = TEXT("ExitSucceeded");
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Stop_GlobalSucceeded, "System.EzAbility.Stop.GlobalSucceeded");
+
+struct FEzAbilityTest_Stop_GlobalFailed : FEzAbilityTest_Stop
+{
+	virtual bool SetUp() override
+	{
+		// Normal task completes later
+		NormalTaskTicks = 2;
+
+		// Global task completes as failed.
+		GlobalTaskStatus = EAbilityRunStatus::Failed;
+		GlobalTaskTicks = 1;
+
+		// Tree should complete as failed.
+		ExpectedStatusAfterTick = EAbilityRunStatus::Failed;
+		
+		// Tasks should have Transition.CurrentRunStatus as failed.
+		ExpectedExitStatusStr = TEXT("ExitFailed");
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Stop_GlobalFailed, "System.EzAbility.Stop.GlobalFailed");
+
+
+//
+// Tests combinations of completing the tree on EnterState.
+//
+struct FEzAbilityTest_StopEnterNormal : FEzAbilityTest_Stop
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = SetupTree();
+		
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString TickStr(TEXT("Tick"));
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString ExitStateStr(TEXT("ExitState"));
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		
+		// 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(&EzAbility, Parameter, OutText);;
+		AITEST_EQUAL("Tree should be running after start", Status, EAbilityRunStatus::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("EzAbility GlobalTask should get exit state expectedly", Exec.Expect(GlobalTaskName, ExpectedExitStatusStr));
+
+		AITEST_TRUE("EzAbility TaskA should enter state", Exec.Expect(TaskAName, EnterStateStr));
+		AITEST_TRUE("EzAbility TaskA should report exit status", Exec.Expect(TaskAName, ExpectedExitStatusStr));
+
+		Exec.LogClear();
+		
+		return true;
+	}
+
+	EAbilityRunStatus ExpectedStatusAfterStart = EAbilityRunStatus::Succeeded;
+	FString ExpectedExitStatusStr = TEXT("ExitSucceeded");
+	bool bExpectNormalTaskToRun = true; 
+};
+
+struct FEzAbilityTest_Stop_NormalEnterSucceeded : FEzAbilityTest_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 = EAbilityRunStatus::Succeeded;
+
+		// Tree should complete as succeeded.
+		ExpectedStatusAfterStart = EAbilityRunStatus::Succeeded;
+		
+		// Tasks should have Transition.CurrentRunStatus as succeeded.
+		ExpectedExitStatusStr = TEXT("ExitSucceeded");
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Stop_NormalEnterSucceeded, "System.EzAbility.Stop.NormalEnterSucceeded");
+
+struct FEzAbilityTest_Stop_NormalEnterFailed : FEzAbilityTest_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 = EAbilityRunStatus::Failed;
+
+		// Tree should complete as failed.
+		ExpectedStatusAfterStart = EAbilityRunStatus::Failed;
+		
+		// Tasks should have Transition.CurrentRunStatus as failed.
+		ExpectedExitStatusStr = TEXT("ExitFailed");
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Stop_NormalEnterFailed, "System.EzAbility.Stop.NormalEnterFailed");
+
+
+
+
+struct FEzAbilityTest_StopEnterGlobal : FEzAbilityTest_Stop
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = SetupTree();
+		
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString TickStr(TEXT("Tick"));
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString ExitStateStr(TEXT("ExitState"));
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_EQUAL("Start should be expected value", Status, ExpectedStatusAfterStart);
+		AITEST_TRUE("EzAbility GlobalTask should get exit state expectedly", Exec.Expect(GlobalTaskName, ExpectedExitStatusStr));
+
+		// Normal tasks should not run
+		AITEST_FALSE("EzAbility TaskA should not enter state", Exec.Expect(TaskAName, EnterStateStr));
+		AITEST_FALSE("EzAbility TaskA should not report exit status", Exec.Expect(TaskAName, ExpectedExitStatusStr));
+
+		Exec.LogClear();
+		
+		return true;
+	}
+
+	EAbilityRunStatus ExpectedStatusAfterStart = EAbilityRunStatus::Succeeded;
+	FString ExpectedExitStatusStr = TEXT("ExitSucceeded");
+};
+
+struct FEzAbilityTest_Stop_GlobalEnterSucceeded : FEzAbilityTest_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 = EAbilityRunStatus::Succeeded;
+
+		// Tree should complete as succeeded.
+		ExpectedStatusAfterStart = EAbilityRunStatus::Succeeded;
+		
+		// Tasks should have Transition.CurrentRunStatus as Succeeded.
+		ExpectedExitStatusStr = TEXT("ExitSucceeded");
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Stop_GlobalEnterSucceeded, "System.EzAbility.Stop.GlobalEnterSucceeded");
+
+struct FEzAbilityTest_Stop_GlobalEnterFailed : FEzAbilityTest_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 = EAbilityRunStatus::Failed;
+
+		// Tree should complete as failed.
+		ExpectedStatusAfterStart = EAbilityRunStatus::Failed;
+		
+		// Tasks should have Transition.CurrentRunStatus as failed.
+		ExpectedExitStatusStr = TEXT("ExitFailed");
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Stop_GlobalEnterFailed, "System.EzAbility.Stop.GlobalEnterFailed");
+
+struct FEzAbilityTest_Stop_ExternalStop : FEzAbilityTest_Stop
+{
+	virtual bool SetUp() override
+	{
+		// Tasks should complete later.
+		NormalTaskTicks = 2;
+		GlobalTaskTicks = 2;
+
+		// Tree should tick and keep on running.
+		ExpectedStatusAfterTick = EAbilityRunStatus::Running;
+
+		// Tree should stop as stopped.
+		ExpectedStatusAfterStop = EAbilityRunStatus::Stopped;
+		
+		// Tasks should have Transition.CurrentRunStatus as stopped. 
+		ExpectedExitStatusStr = TEXT("ExitStopped");
+
+		return true;
+	}
+	
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = SetupTree();
+		
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString TickStr(TEXT("Tick"));
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString ExitStateStr(TEXT("ExitState"));
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+		AITEST_EQUAL("Start should be running", Status, EAbilityRunStatus::Running);
+		AITEST_TRUE("EzAbility GlobalTask should enter state", Exec.Expect(GlobalTaskName, EnterStateStr));
+		AITEST_TRUE("EzAbility 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(EAbilityRunStatus::Stopped);
+		AITEST_EQUAL("Start should be running", Status, ExpectedStatusAfterStop);
+		if (!ExpectedExitStatusStr.IsEmpty())
+		{
+			AITEST_TRUE("EzAbility GlobalTask should get exit state expectedly", Exec.Expect(GlobalTaskName, ExpectedExitStatusStr));
+			AITEST_TRUE("EzAbility TaskA should get exit state expectedly", Exec.Expect(TaskAName, ExpectedExitStatusStr));
+		}
+		
+		return true;
+	}
+
+	EAbilityRunStatus ExpectedStatusAfterStop = EAbilityRunStatus::Stopped;
+	
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Stop_ExternalStop, "System.EzAbility.Stop.ExternalStop");
+
+struct FEzAbilityTest_Stop_AlreadyStopped : FEzAbilityTest_Stop_ExternalStop
+{
+	virtual bool SetUp() override
+	{
+		// Normal task completes before stop.
+		NormalTaskTicks = 1;
+		NormalTaskStatus = EAbilityRunStatus::Succeeded;
+
+		// Global task completes later
+		GlobalTaskTicks = 2;
+
+		// Tree should tick stop as succeeded.
+		ExpectedStatusAfterTick = EAbilityRunStatus::Succeeded;
+
+		// Tree is already stopped, should keep the status (not Stopped).
+		ExpectedStatusAfterStop = EAbilityRunStatus::Succeeded;
+		
+		// Skip exit status check.
+		ExpectedExitStatusStr = TEXT("");
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_Stop_AlreadyStopped, "System.EzAbility.Stop.AlreadyStopped");
+
+//
+// The deferred stop tests validates that the tree can be properly stopped if requested in the main entry points (Start, Tick, Stop).  
+//
+struct FEzAbilityTest_DeferredStop : FAITestBase
+{
+	UEzAbility& SetupTree() const
+	{
+		UEzAbility& Ability = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(Ability.EditorData);
+
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+		UEzAbilityState& StateA = Root.AddChildState(FName(TEXT("A")));
+		TEzAbilityEditorNode<FAbilityTestTask_StopTree>& TaskA = StateA.AddTask<FAbilityTestTask_StopTree>(TEXT("Task"));
+		TEzAbilityEditorNode<FAbilityTestTask_StopTree>& GlobalTask = EditorData.AddGlobalTask<FAbilityTestTask_StopTree>(TEXT("GlobalTask"));
+
+		StateA.AddTransition(EEzAbilityTransitionTrigger::OnStateSucceeded, EEzAbilityTransitionType::Succeeded);
+		StateA.AddTransition(EEzAbilityTransitionTrigger::OnStateFailed, EEzAbilityTransitionType::Failed);
+
+		GlobalTask.GetNode().Phase = GlobalTaskPhase;
+		TaskA.GetNode().Phase = TaskPhase;
+
+		return Ability;
+	}
+
+	virtual bool RunDerivedTest(FTestEzAbilityExecutionContext& Exec) = 0;
+
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = SetupTree();
+		WeakAbility = &EzAbility;
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		return RunDerivedTest(Exec);
+	}
+
+protected:
+	TWeakObjectPtr<UEzAbility>	WeakAbility;
+	EEzAbilityUpdatePhase GlobalTaskPhase = EEzAbilityUpdatePhase::Unset;
+	EEzAbilityUpdatePhase TaskPhase = EEzAbilityUpdatePhase::Unset;
+};
+
+struct FEzAbilityTest_DeferredStop_EnterGlobalTask : FEzAbilityTest_DeferredStop
+{
+	FEzAbilityTest_DeferredStop_EnterGlobalTask() { GlobalTaskPhase = EEzAbilityUpdatePhase::EnterStates; }
+	virtual bool RunDerivedTest(FTestEzAbilityExecutionContext& Exec) override
+	{
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(WeakAbility.Get(), Parameter, OutText);;
+		AITEST_EQUAL("Tree should be stopped", Status, EAbilityRunStatus::Stopped);
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_DeferredStop_EnterGlobalTask, "System.EzAbility.DeferredStop.EnterGlobalTask");
+
+struct FEzAbilityTest_DeferredStop_TickGlobalTask : FEzAbilityTest_DeferredStop
+{
+	FEzAbilityTest_DeferredStop_TickGlobalTask() { GlobalTaskPhase = EEzAbilityUpdatePhase::Tick; }
+	virtual bool RunDerivedTest(FTestEzAbilityExecutionContext& Exec) override
+	{
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(WeakAbility.Get(), Parameter, OutText);;
+		AITEST_EQUAL("Tree should be running", Status, EAbilityRunStatus::Running);
+
+		Status = Exec.Tick(0.1f);
+		AITEST_EQUAL("Tree should be stopped", Status, EAbilityRunStatus::Stopped);
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_DeferredStop_TickGlobalTask, "System.EzAbility.DeferredStop.TickGlobalTask");
+
+struct FEzAbilityTest_DeferredStop_ExitGlobalTask : FEzAbilityTest_DeferredStop
+{
+	FEzAbilityTest_DeferredStop_ExitGlobalTask() { GlobalTaskPhase = EEzAbilityUpdatePhase::ExitStates; }
+	virtual bool RunDerivedTest(FTestEzAbilityExecutionContext& Exec) override
+	{
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(WeakAbility.Get(), Parameter, OutText);;
+		AITEST_EQUAL("Tree should be running", Status, EAbilityRunStatus::Running);
+
+		Status = Exec.Tick(0.1f);
+		AITEST_EQUAL("Tree should be running", Status, EAbilityRunStatus::Running);
+
+		Status = Exec.Stop();
+		AITEST_EQUAL("Tree should be stopped", Status, EAbilityRunStatus::Stopped);
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_DeferredStop_ExitGlobalTask, "System.EzAbility.DeferredStop.ExitGlobalTask");
+
+struct FEzAbilityTest_DeferredStop_EnterTask : FEzAbilityTest_DeferredStop
+{
+	FEzAbilityTest_DeferredStop_EnterTask() { TaskPhase = EEzAbilityUpdatePhase::EnterStates; }
+	virtual bool RunDerivedTest(FTestEzAbilityExecutionContext& Exec) override
+	{
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(WeakAbility.Get(), Parameter, OutText);;
+		AITEST_EQUAL("Tree should be running", Status, EAbilityRunStatus::Stopped);
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_DeferredStop_EnterTask, "System.EzAbility.DeferredStop.EnterTask");
+
+struct FEzAbilityTest_DeferredStop_TickTask : FEzAbilityTest_DeferredStop
+{
+	FEzAbilityTest_DeferredStop_TickTask() { TaskPhase = EEzAbilityUpdatePhase::Tick; }
+	virtual bool RunDerivedTest(FTestEzAbilityExecutionContext& Exec) override
+	{
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(WeakAbility.Get(), Parameter, OutText);;
+		AITEST_EQUAL("Tree should be running", Status, EAbilityRunStatus::Running);
+
+		Status = Exec.Tick(0.1f);
+		AITEST_EQUAL("Tree should be stopped", Status, EAbilityRunStatus::Stopped);
+
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_DeferredStop_TickTask, "System.EzAbility.DeferredStop.TickTask");
+
+struct FEzAbilityTest_DeferredStop_ExitTask : FEzAbilityTest_DeferredStop
+{
+	FEzAbilityTest_DeferredStop_ExitTask() { TaskPhase = EEzAbilityUpdatePhase::ExitStates; }
+	virtual bool RunDerivedTest(FTestEzAbilityExecutionContext& Exec) override
+	{
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		Status = Exec.Start(WeakAbility.Get(), Parameter, OutText);;
+		AITEST_EQUAL("Tree should be running", Status, EAbilityRunStatus::Running);
+
+		Status = Exec.Tick(0.1f);
+		AITEST_EQUAL("Tree should be running", Status, EAbilityRunStatus::Running);
+
+		Status = Exec.Stop();
+		AITEST_EQUAL("Tree should be stopped", Status, EAbilityRunStatus::Stopped);
+		
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_DeferredStop_ExitTask, "System.EzAbility.DeferredStop.ExitTask");
+
+// struct FEzAbilityTest_FailEnterLinkedAsset : FAITestBase
+// {
+// 	virtual bool InstantTest() override
+// 	{
+// 		FEzAbilityCompilerLog Log;
+//
+// 		// Asset 2
+// 		UEzAbility& EzAbility2 = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData2 = *Cast<UEzAbilityEditorData>(EzAbility2.EditorData);
+// 		UEzAbilityState& Root2 = EditorData2.AddSubTree(FName(TEXT("Root2")));
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task2 = Root2.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task2")));
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& GlobalTask2 = EditorData2.AddGlobalTask<FAbilityTestTask_Stand>(FName(TEXT("GlobalTask2")));
+// 		GlobalTask2.GetInstanceData().Value = 123;
+//
+// 		// Always failing enter condition
+// 		TEzAbilityEditorNode<FAbilityCompareIntCondition>& IntCond2 = Root2.AddEnterCondition<FAbilityCompareIntCondition>();
+// 		EditorData2.AddPropertyBinding(GlobalTask2, TEXT("Value"), IntCond2, TEXT("Left"));
+// 		IntCond2.GetInstanceData().Right = 0;
+//
+// 		FEzAbilityCompiler Compiler2(Log);
+// 		const bool bResult2 = Compiler2.Compile(EzAbility2);
+// 		AITEST_TRUE("EzAbility2 should get compiled", bResult2);
+//
+// 		// Main asset
+// 		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+// 		
+// 		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root1")));
+// 		UEzAbilityState& A1 = Root.AddChildState(FName(TEXT("A1")), EEzAbilityStateType::LinkedAsset);
+// 		A1.SetLinkedStateAsset(&EzAbility2);
+//
+// 		UEzAbilityState& B1 = Root.AddChildState(FName(TEXT("B1")), EEzAbilityStateType::State);
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task1 = B1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+//
+// 		FEzAbilityCompiler Compiler(Log);
+// 		const bool bResult = Compiler.Compile(EzAbility);
+// 		AITEST_TRUE("EzAbility should get compiled", bResult);
+//
+// 		const FString EnterStateStr(TEXT("EnterState"));
+// 		const FString ExitStateStr(TEXT("ExitState"));
+//
+// 		{
+// 			EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+// 			FEzAbilityInstanceData InstanceData;
+// 			FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 			const bool bInitSucceeded = Exec.IsValid();
+// 			AITEST_TRUE("EzAbility should init", bInitSucceeded);
+//
+// 			FEzAbilityParameter Parameter;
+// 			FText OutText;
+// 			Status = Exec.Start(&EzAbility, Parameter, OutText);;
+// 			AITEST_EQUAL("Start should complete with Running", Status, EAbilityRunStatus::Running);
+// 			AITEST_TRUE("EzAbility should enter GlobalTask2", Exec.Expect(GlobalTask2.GetName(), EnterStateStr));
+// 			AITEST_TRUE("EzAbility should exit GlobalTask2", Exec.Expect(GlobalTask2.GetName(), ExitStateStr));
+// 			AITEST_FALSE("EzAbility should not enter Task2", Exec.Expect(Task2.GetName(), EnterStateStr));
+// 			AITEST_TRUE("EzAbility should enter Task1", Exec.Expect(Task1.GetName(), EnterStateStr));
+//
+// 			Exec.LogClear();
+// 		}
+//
+// 		return true;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_FailEnterLinkedAsset, "System.EzAbility.FailEnterLinkedAsset");
+
+// struct FEzAbilityTest_EnterAndExitLinkedAsset : FAITestBase
+// {
+// 	virtual bool InstantTest() override
+// 	{
+// 		FEzAbilityCompilerLog Log;
+//
+// 		// Asset 2
+// 		UEzAbility& EzAbility2 = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData2 = *Cast<UEzAbilityEditorData>(EzAbility2.EditorData);
+// 		UEzAbilityState& Root2 = EditorData2.AddSubTree(FName(TEXT("Root2")));
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task2 = Root2.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task2")));
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& GlobalTask2 = EditorData2.AddGlobalTask<FAbilityTestTask_Stand>(FName(TEXT("GlobalTask2")));
+// 		GlobalTask2.GetNode().TicksToCompletion = 2;
+//
+// 		FEzAbilityCompiler Compiler2(Log);
+// 		const bool bResult2 = Compiler2.Compile(EzAbility2);
+// 		AITEST_TRUE("EzAbility2 should get compiled", bResult2);
+//
+// 		// Main asset
+// 		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+// 		
+// 		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root1")));
+// 		UEzAbilityState& A1 = Root.AddChildState(FName(TEXT("A1")), EEzAbilityStateType::LinkedAsset);
+// 		A1.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::NextState);
+// 		A1.SetLinkedStateAsset(&EzAbility2);
+//
+// 		UEzAbilityState& B1 = Root.AddChildState(FName(TEXT("B1")), EEzAbilityStateType::State);
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task1 = B1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+//
+// 		FEzAbilityCompiler Compiler(Log);
+// 		const bool bResult = Compiler.Compile(EzAbility);
+// 		AITEST_TRUE("EzAbility should get compiled", bResult);
+//
+// 		const FString EnterStateStr(TEXT("EnterState"));
+// 		const FString ExitStateStr(TEXT("ExitState"));
+//
+// 		{
+// 			EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+// 			FEzAbilityInstanceData InstanceData;
+// 			FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 			const bool bInitSucceeded = Exec.IsValid();
+// 			AITEST_TRUE("EzAbility should init", bInitSucceeded);
+//
+// 			FEzAbilityParameter Parameter;
+// 			FText OutText;
+// 			Status = Exec.Start(&EzAbility, Parameter, OutText);;
+// 			AITEST_EQUAL("Start should complete with Running", Status, EAbilityRunStatus::Running);
+// 			AITEST_TRUE("EzAbility should enter GlobalTask2", Exec.Expect(GlobalTask2.GetName(), EnterStateStr));
+// 			AITEST_FALSE("EzAbility should not exit GlobalTask2", Exec.Expect(GlobalTask2.GetName(), ExitStateStr));
+// 			AITEST_TRUE("EzAbility should enter Task2", Exec.Expect(Task2.GetName(), EnterStateStr));
+// 			AITEST_FALSE("EzAbility should not exit Task2", Exec.Expect(Task2.GetName(), ExitStateStr));
+// 			AITEST_FALSE("EzAbility should not enter Task1", Exec.Expect(Task1.GetName(), EnterStateStr));
+// 			Exec.LogClear();
+//
+// 			Status = Exec.Tick(0.1f);
+// 			AITEST_EQUAL("Tick should complete with Running", Status, EAbilityRunStatus::Running);
+// 			AITEST_FALSE("EzAbility should not enter GlobalTask2", Exec.Expect(GlobalTask2.GetName(), EnterStateStr));
+// 			AITEST_TRUE("EzAbility should exit GlobalTask2", Exec.Expect(GlobalTask2.GetName(), ExitStateStr));
+// 			AITEST_FALSE("EzAbility should not enter Task2", Exec.Expect(Task2.GetName(), EnterStateStr));
+// 			AITEST_TRUE("EzAbility should exit Task2", Exec.Expect(Task2.GetName(), ExitStateStr));
+// 			AITEST_TRUE("EzAbility should enter Task1", Exec.Expect(Task1.GetName(), EnterStateStr));
+// 			Exec.LogClear();
+// 		}
+//
+// 		return true;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_EnterAndExitLinkedAsset, "System.EzAbility.EnterAndExitLinkedAsset");
+
+// // Test nested tree overrides
+// struct FEzAbilityTest_NestedOverride : FAITestBase
+// {
+// 	virtual bool InstantTest() override
+// 	{
+// 		FEzAbilityCompilerLog Log;
+// 		
+// 		const FGameplayTag Tag = UE::EzAbility::Tests::FNativeGameplayTags::Get().TestTag;
+//
+// 		// Asset 2
+// 		UEzAbility& EzAbility2 = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData2 = *Cast<UEzAbilityEditorData>(EzAbility2.EditorData);
+// 		EditorData2.RootParameters.Parameters.AddProperty(FName(TEXT("Int")), EPropertyBagPropertyType::Int32);
+// 		UEzAbilityState& Root2 = EditorData2.AddSubTree(FName(TEXT("Root2")));
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& TaskRoot2 = Root2.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskRoot2")));
+//
+// 		FEzAbilityCompiler Compiler2(Log);
+// 		const bool bResult2 = Compiler2.Compile(EzAbility2);
+// 		AITEST_TRUE("EzAbility2 should get compiled", bResult2);
+//
+// 		
+// 		// Asset 3
+// 		UEzAbility& EzAbility3 = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData3 = *Cast<UEzAbilityEditorData>(EzAbility3.EditorData);
+// 		EditorData3.RootParameters.Parameters.AddProperty(FName(TEXT("Float")), EPropertyBagPropertyType::Float); // Different parameters
+// 		UEzAbilityState& Root3 = EditorData3.AddSubTree(FName(TEXT("Root3")));
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& TaskRoot3 = Root3.AddTask<FAbilityTestTask_Stand>(FName(TEXT("TaskRoot3")));
+//
+// 		FEzAbilityCompiler Compiler3(Log);
+// 		const bool bResult3 = Compiler3.Compile(EzAbility3);
+// 		AITEST_TRUE("EzAbility3 should get compiled", bResult3);
+//
+// 		// Main asset
+// 		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+//
+// 		EditorData.RootParameters.Parameters.AddProperty(FName(TEXT("Int")), EPropertyBagPropertyType::Int32);
+// 		
+// 		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root1")));
+// 		UEzAbilityState& StateA = Root.AddChildState(FName(TEXT("A1")), EEzAbilityStateType::LinkedAsset);
+// 		StateA.Tag = Tag;
+// 		StateA.SetLinkedStateAsset(&EzAbility2);
+//
+// 		FEzAbilityCompiler Compiler(Log);
+// 		const bool bResult = Compiler.Compile(EzAbility);
+// 		AITEST_TRUE("EzAbility should get compiled", bResult);
+//
+// 		const FString TickStr(TEXT("Tick"));
+// 		const FString EnterStateStr(TEXT("EnterState"));
+// 		const FString ExitStateStr(TEXT("ExitState"));
+//
+// 		// Without overrides
+// 		{
+// 			EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+// 			FEzAbilityInstanceData InstanceData;
+// 			FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 			const bool bInitSucceeded = Exec.IsValid();
+// 			AITEST_TRUE("EzAbility should init", bInitSucceeded);
+//
+// 			FEzAbilityParameter Parameter;
+// 			FText OutText;
+// 			Status = Exec.Start(&EzAbility, Parameter, OutText);;
+// 			AITEST_EQUAL("Start should complete with Running", Status, EAbilityRunStatus::Running);
+// 			AITEST_TRUE("EzAbility should enter TaskRoot2", Exec.Expect(TaskRoot2.GetName(), EnterStateStr));
+//
+// 			Exec.LogClear();
+// 		}
+//
+// 		// With overrides
+// 		{
+// 			EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+// 			FEzAbilityInstanceData InstanceData;
+//
+// 			FEzAbilityReferenceOverrides Overrides;
+// 			FEzAbilityReference OverrideRef;
+// 			OverrideRef.SetEzAbility(&EzAbility3);
+// 			Overrides.AddOverride(Tag, OverrideRef);
+// 			
+// 			FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 			Exec.SetLinkedEzAbilityOverrides(&Overrides);
+// 			
+// 			const bool bInitSucceeded = Exec.IsValid();
+// 			AITEST_TRUE("EzAbility should init", bInitSucceeded);
+//
+// 			FEzAbilityParameter Parameter;
+// 			FText OutText;
+// 			Status = Exec.Start(&EzAbility, Parameter, OutText);;
+// 			AITEST_EQUAL("Start should complete with Running", Status, EAbilityRunStatus::Running);
+// 			AITEST_TRUE("EzAbility should enter TaskRoot3", Exec.Expect(TaskRoot3.GetName(), EnterStateStr));
+//
+// 			Exec.LogClear();
+// 		}
+//
+// 		return true;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_NestedOverride, "System.EzAbility.NestedOverride");
+
+// // Test parallel tree event priority handling.
+// struct FEzAbilityTest_ParallelEventPriority : FAITestBase
+// {
+// 	EEzAbilityTransitionPriority ParallelTreePriority = EEzAbilityTransitionPriority::Normal;
+// 	
+// 	virtual bool InstantTest() override
+// 	{
+// 		FEzAbilityCompilerLog Log;
+// 		
+// 		const FGameplayTag EventTag = UE::EzAbility::Tests::FNativeGameplayTags::Get().TestTag;
+//
+// 		// Parallel tree
+// 		// - Root
+// 		//   - State1 ?-> State2
+// 		//   - State2
+// 		UEzAbility& EzAbilityPar = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorDataPar = *Cast<UEzAbilityEditorData>(EzAbilityPar.EditorData);
+//
+// 		UEzAbilityState& RootPar = EditorDataPar.AddSubTree(FName(TEXT("Root")));
+// 		UEzAbilityState& State1 = RootPar.AddChildState(FName(TEXT("State1")));
+// 		UEzAbilityState& State2 = RootPar.AddChildState(FName(TEXT("State2")));
+//
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task1 = State1.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+// 		Task1.GetNode().TicksToCompletion = 100;
+// 		State1.AddTransition(EEzAbilityTransitionTrigger::OnEvent, EventTag, EEzAbilityTransitionType::NextState);
+//
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task2 = State2.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task2")));
+// 		Task2.GetNode().TicksToCompletion = 100;
+//
+// 		{
+// 			FEzAbilityCompiler Compiler(Log);
+// 			const bool bResult = Compiler.Compile(EzAbilityPar);
+// 			AITEST_TRUE("EzAbilityPar should get compiled", bResult);
+// 		}
+//
+// 		// Main asset
+// 		// - Root [EzAbilityPar]
+// 		//   - State3 ?-> State4
+// 		//   - State4
+// 		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+// 		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+//
+// 		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+// 		UEzAbilityState& State3 = Root.AddChildState(FName(TEXT("State3")));
+// 		UEzAbilityState& State4 = Root.AddChildState(FName(TEXT("State4")));
+//
+// 		TEzAbilityEditorNode<FEzAbilityRunParallelEzAbilityTask>& TaskPar = Root.AddTask<FEzAbilityRunParallelEzAbilityTask>();
+// 		TaskPar.GetNode().SetEventHandlingPriority(ParallelTreePriority);
+// 		
+// 		TaskPar.GetInstanceData().EzAbility.SetEzAbility(&EzAbilityPar);
+//
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task3 = State3.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task3")));
+// 		Task3.GetNode().TicksToCompletion = 100;
+// 		State3.AddTransition(EEzAbilityTransitionTrigger::OnEvent, EventTag, EEzAbilityTransitionType::NextState);
+//
+// 		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task4 = State4.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task4")));
+// 		Task4.GetNode().TicksToCompletion = 100;
+// 		
+// 		{
+// 			FEzAbilityCompiler Compiler(Log);
+// 			const bool bResult = Compiler.Compile(EzAbility);
+// 			AITEST_TRUE("EzAbility should get compiled", bResult);
+// 		}
+//
+// 		const FString TickStr(TEXT("Tick"));
+// 		const FString EnterStateStr(TEXT("EnterState"));
+// 		const FString ExitStateStr(TEXT("ExitState"));
+//
+// 		// Run EzAbilityPar in parallel with the main tree.
+// 		// Both trees have a transition on same event.
+// 		// Setting the priority to Low, should make the main tree to take the transition.
+// 		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+// 		FEzAbilityInstanceData InstanceData;
+// 		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+// 		const bool bInitSucceeded = Exec.IsValid();
+// 		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+//
+// 		FEzAbilityParameter Parameter;
+// 		FText OutText;
+// 		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+// 		AITEST_EQUAL("Start should complete with Running", Status, EAbilityRunStatus::Running);
+// 		AITEST_TRUE("EzAbility should enter Task1, Task3", Exec.Expect(Task1.GetName(), EnterStateStr).Then(Task3.GetName(), EnterStateStr));
+// 		Exec.LogClear();
+//
+// 		Status = Exec.Tick(0.1f);
+// 		AITEST_EQUAL("Tick should complete with Running", Status, EAbilityRunStatus::Running);
+// 		AITEST_TRUE("EzAbility should tick Task1, Task3", Exec.Expect(Task1.GetName(), TickStr).Then(Task3.GetName(), TickStr));
+// 		Exec.LogClear();
+//
+// 		Exec.SendEvent(EventTag);
+//
+// 		// If the parallel tree priority is < Normal, then it should always be handled after the main tree.
+// 		// If the parallel tree priority is Normal, then the state order decides (leaf to root)
+// 		// If the parallel tree priority is > Normal, then it should always be handled before the main tree.
+// 		if (ParallelTreePriority <= EEzAbilityTransitionPriority::Normal)
+// 		{
+// 			// Main tree should do the transition.
+// 			Status = Exec.Tick(0.1f);
+// 			AITEST_EQUAL("Tick should complete with Running", Status, EAbilityRunStatus::Running);
+// 			AITEST_TRUE("EzAbility should enter Task4", Exec.Expect(Task4.GetName(), EnterStateStr));
+// 			Exec.LogClear();
+//
+// 			Status = Exec.Tick(0.1f);
+// 			AITEST_EQUAL("Tick should complete with Running", Status, EAbilityRunStatus::Running);
+// 			AITEST_TRUE("EzAbility should tick Task1, Task4", Exec.Expect(Task1.GetName(), TickStr).Then(Task4.GetName(), TickStr));
+// 			Exec.LogClear();
+// 		}
+// 		else
+// 		{
+// 			// Parallel tree should do the transition.
+// 			Status = Exec.Tick(0.1f);
+// 			AITEST_EQUAL("Tick should complete with Running", Status, EAbilityRunStatus::Running);
+// 			AITEST_TRUE("EzAbility should enter Task2", Exec.Expect(Task2.GetName(), EnterStateStr));
+// 			Exec.LogClear();
+//
+// 			Status = Exec.Tick(0.1f);
+// 			AITEST_EQUAL("Tick should complete with Running", Status, EAbilityRunStatus::Running);
+// 			AITEST_TRUE("EzAbility should tick Task2, Task3", Exec.Expect(Task2.GetName(), TickStr).Then(Task3.GetName(), TickStr));
+// 			Exec.LogClear();
+// 		}
+// 		
+// 		return true;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_ParallelEventPriority, "System.EzAbility.ParallelEventPriority");
+
+
+// struct FEzAbilityTest_ParallelEventPriority_Low : FEzAbilityTest_ParallelEventPriority
+// {
+// 	FEzAbilityTest_ParallelEventPriority_Low()
+// 	{
+// 		ParallelTreePriority = EEzAbilityTransitionPriority::Low;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_ParallelEventPriority_Low, "System.EzAbility.ParallelEventPriority.Low");
+//
+// struct FEzAbilityTest_ParallelEventPriority_High : FEzAbilityTest_ParallelEventPriority
+// {
+// 	FEzAbilityTest_ParallelEventPriority_High()
+// 	{
+// 		ParallelTreePriority = EEzAbilityTransitionPriority::High;
+// 	}
+// };
+// IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_ParallelEventPriority_High, "System.EzAbility.ParallelEventPriority.High");
+
+struct FEzAbilityTest_SubTreeTransition : FAITestBase
+{
+	virtual bool InstantTest() override
+	{
+		UEzAbility& EzAbility = UE::EzAbility::Tests::NewAbility(&GetWorld());
+		UEzAbilityEditorData& EditorData = *Cast<UEzAbilityEditorData>(EzAbility.EditorData);
+
+		/*
+		- Root
+			- PreLastStand [Task1] -> Reinforcements
+				- BusinessAsUsual [Task2]
+			- LastStand [Task3]
+				- Reinforcements>TimeoutChecker
+			- (f)TimeoutChecker
+				- RemainingCount [Task4]
+		*/
+		
+		UEzAbilityState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
+
+		UEzAbilityState& PreLastStand = Root.AddChildState(FName(TEXT("PreLastStand")));
+		UEzAbilityState& BusinessAsUsual = PreLastStand.AddChildState(FName(TEXT("BusinessAsUsual")));
+
+		UEzAbilityState& LastStand = Root.AddChildState(FName(TEXT("LastStand")));
+		UEzAbilityState& Reinforcements = LastStand.AddChildState(FName(TEXT("Reinforcements")), EEzAbilityStateType::Linked);
+		
+		UEzAbilityState& TimeoutChecker = LastStand.AddChildState(FName(TEXT("TimeoutChecker")), EEzAbilityStateType::Subtree);
+		UEzAbilityState& RemainingCount = TimeoutChecker.AddChildState(FName(TEXT("RemainingCount")));
+
+		Reinforcements.LinkedSubtree = TimeoutChecker.GetLinkToState();
+
+
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task1 = PreLastStand.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task1")));
+		PreLastStand.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &Reinforcements);
+		Task1.GetInstanceData().Value = 1; // This should finish before the child state
+
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task2 = BusinessAsUsual.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task2")));
+		Task2.GetInstanceData().Value = 2;
+
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task3 = LastStand.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task3")));
+		Task3.GetInstanceData().Value = 2;
+
+		TEzAbilityEditorNode<FAbilityTestTask_Stand>& Task4 = LastStand.AddTask<FAbilityTestTask_Stand>(FName(TEXT("Task4")));
+		Task4.GetInstanceData().Value = 2;
+
+		FEzAbilityCompilerLog Log;
+		FEzAbilityCompiler Compiler(Log);
+		const bool bResult = Compiler.Compile(EzAbility);
+		AITEST_TRUE("EzAbility should get compiled", bResult);
+
+		EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+		FEzAbilityInstanceData InstanceData;
+		FTestEzAbilityExecutionContext Exec(EzAbility, EzAbility, InstanceData);
+		const bool bInitSucceeded = Exec.IsValid();
+		AITEST_TRUE("EzAbility should init", bInitSucceeded);
+
+		const FString TickStr(TEXT("Tick"));
+		const FString EnterStateStr(TEXT("EnterState"));
+		const FString ExitStateStr(TEXT("ExitState"));
+		const FString StateCompletedStr(TEXT("StateCompleted"));
+		FEzAbilityParameter Parameter;
+		FText OutText;
+		// Start and enter state
+		Status = Exec.Start(&EzAbility, Parameter, OutText);;
+
+		AITEST_TRUE("EzAbility Active States should be in Root/PreLastStand/BusinessAsUsual", Exec.ExpectInActiveStates(Root.Name, PreLastStand.Name, BusinessAsUsual.Name));
+		AITEST_TRUE("EzAbility Task1 should enter state", Exec.Expect(Task1.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task2 should enter state", Exec.Expect(Task2.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+		Exec.LogClear();
+
+		// Transition to Reinforcements
+		Status = Exec.Tick(0.1f);
+		AITEST_TRUE("EzAbility Active States should be in Root/LastStand/Reinforcements/TimeoutChecker/RemainingCount", Exec.ExpectInActiveStates(Root.Name, LastStand.Name, Reinforcements.Name, TimeoutChecker.Name, RemainingCount.Name));
+		AITEST_TRUE("EzAbility Task3 should enter state", Exec.Expect(Task3.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility Task4 should enter state", Exec.Expect(Task4.GetName(), EnterStateStr));
+		AITEST_TRUE("EzAbility should be running", Status == EAbilityRunStatus::Running);
+		Exec.LogClear();
+		
+		return true;
+	}
+};
+IMPLEMENT_AI_INSTANT_TEST(FEzAbilityTest_SubTreeTransition, "System.EzAbility.SubTreeTransition");
+
+
 UE_ENABLE_OPTIMIZATION_SHIP
 #undef LOCTEXT_NAMESPACE

+ 373 - 361
Ability/Plugins/EzAbility/Source/EzAbilityTest/Private/EzAblityTestTypes.h

@@ -1,6 +1,7 @@
 
 #pragma once
 #include "EzAbilityContext.h"
+#include "EzAbilityPropertyRef.h"
 #include "Condition/EzAbilityCondition.h"
 #include "Evaluator/EzAbilityEvaluator.h"
 #include "Task/EzAbilityTask.h"
@@ -157,366 +158,377 @@ struct FEzAbilityTestTask_B : public FEzAbilityTask
 	}
 };
 
-// USTRUCT()
-// struct FTestTask_PrintValueInstanceData
-// {
-// 	GENERATED_BODY()
-//
-// 	UPROPERTY(EditAnywhere, Category = "Parameter")
-// 	int32 Value = 0;
-// };
-//
-// USTRUCT()
-// struct FTestTask_PrintValue : public FEzAbilityTask
-// {
-// 	GENERATED_BODY()
-//
-// 	using FInstanceDataType = FTestTask_PrintValueInstanceData;
-//
-// 	FTestTask_PrintValue() = default;
-// 	FTestTask_PrintValue(const FName InName) { Name = InName; }
-// 	virtual ~FTestTask_PrintValue() override {}
-// 	
-// 	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
-//
-// 	virtual EAbilityRunStatus EnterState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
-// 	{
-// 		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
-// 		const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
-// 		TestContext.Log(Name,  FString::Printf(TEXT("EnterState%d"), InstanceData.Value));
-// 		return EAbilityRunStatus::Running;
-// 	}
-//
-// 	virtual void ExitState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
-// 	{
-// 		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
-// 		const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
-// 		TestContext.Log(Name,  FString::Printf(TEXT("ExitState%d"), InstanceData.Value));
-// 	}
-//
-// 	virtual EAbilityRunStatus Tick(FEzAbilityContext& Context, const float DeltaTime) const override
-// 	{
-// 		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
-// 		const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
-// 		TestContext.Log(Name,  FString::Printf(TEXT("Tick%d"), InstanceData.Value));
-// 		
-// 		return EAbilityRunStatus::Running;
-// 	};
-// };
-//
-//
-// USTRUCT()
-// struct FTestTask_StopTreeInstanceData
-// {
-// 	GENERATED_BODY()
-// };
-//
-// USTRUCT()
-// struct FTestTask_StopTree : public FEzAbilityTask
-// {
-// 	GENERATED_BODY()
-//
-// 	using FInstanceDataType = FTestTask_PrintValueInstanceData;
-//
-// 	FTestTask_StopTree() = default;
-// 	explicit FTestTask_StopTree(const FName InName) { Name = InName; }
-// 	virtual ~FTestTask_StopTree() override {}
-// 	
-// 	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
-//
-// 	virtual EAbilityRunStatus EnterState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
-// 	{
-// 		if (Phase == EEzAbilityUpdatePhase::EnterStates)
-// 		{
-// 			return Context.Stop();
-// 		}
-// 		return EAbilityRunStatus::Running;
-// 	}
-//
-// 	virtual void ExitState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
-// 	{
-// 		if (Phase == EEzAbilityUpdatePhase::ExitStates)
-// 		{
-// 			Context.Stop();
-// 		}
-// 	}
-//
-// 	virtual EAbilityRunStatus Tick(FEzAbilityContext& Context, const float DeltaTime) const override
-// 	{
-// 		if (Phase == EEzAbilityUpdatePhase::TickEzAbility)
-// 		{
-// 			return Context.Stop();
-// 		}
-// 		return EAbilityRunStatus::Running;
-// 	};
-//
-// 	/** Indicates in which phase the call to Stop should be performed. Possible values are EnterStates, ExitStats and TickEzAbility */
-// 	UPROPERTY()
-// 	EEzAbilityUpdatePhase Phase = EEzAbilityUpdatePhase::Unset;
-// };
-//
-// USTRUCT()
-// struct FTestTask_StandInstanceData
-// {
-// 	GENERATED_BODY()
-//
-// 	UPROPERTY()
-// 	int32 CurrentTick = 0;
-// };
-//
-// USTRUCT()
-// struct FTestTask_Stand : public FEzAbilityTask
-// {
-// 	GENERATED_BODY()
-//
-// 	using FInstanceDataType = FTestTask_StandInstanceData;
-// 	
-// 	FTestTask_Stand() = default;
-// 	FTestTask_Stand(const FName InName) { Name = InName; }
-// 	virtual ~FTestTask_Stand() {}
-//
-// 	virtual const UStruct* GetInstanceDataType() const { return FInstanceDataType::StaticStruct(); }
-//
-// 	virtual EAbilityRunStatus EnterState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
-// 	{
-// 		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
-// 		TestContext.Log(Name, TEXT("EnterState"));
-//
-// 		FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
-// 		
-// 		if (Transition.ChangeType == EEzAbilityStateChangeType::Changed)
-// 		{
-// 			InstanceData.CurrentTick = 0;
-// 		}
-// 		return EnterStateResult;
-// 	}
-//
-// 	virtual void ExitState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
-// 	{
-// 		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
-//
-// 		if (Transition.CurrentRunStatus == EAbilityRunStatus::Succeeded)
-// 		{
-// 			TestContext.Log(Name, TEXT("ExitSucceeded"));
-// 		}
-// 		else if (Transition.CurrentRunStatus == EAbilityRunStatus::Failed)
-// 		{
-// 			TestContext.Log(Name, TEXT("ExitFailed"));
-// 		}
-// 		else if (Transition.CurrentRunStatus == EAbilityRunStatus::Stopped)
-// 		{
-// 			TestContext.Log(Name, TEXT("ExitStopped"));
-// 		}
-// 		
-// 		TestContext.Log(Name, TEXT("ExitState"));
-// 	}
-//
-// 	virtual void StateCompleted(FEzAbilityContext& Context, const EAbilityRunStatus CompletionStatus, const FEzAbilityActiveStates& CompletedActiveStates) const override
-// 	{
-// 		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
-// 		TestContext.Log(Name, TEXT("StateCompleted"));
-// 	}
-// 	
-// 	virtual EAbilityRunStatus Tick(FEzAbilityContext& Context, const float DeltaTime) const override
-// 	{
-// 		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
-// 		TestContext.Log(Name, TEXT("Tick"));
-//
-// 		FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
-// 		
-// 		InstanceData.CurrentTick++;
-// 		
-// 		return (InstanceData.CurrentTick >= TicksToCompletion) ? TickCompletionResult : EAbilityRunStatus::Running;
-// 	};
-//
-// 	UPROPERTY(EditAnywhere, Category = Parameter)
-// 	int32 TicksToCompletion = 1;
-//
-// 	UPROPERTY(EditAnywhere, Category = Parameter)
-// 	EAbilityRunStatus TickCompletionResult = EAbilityRunStatus::Succeeded;
-//
-// 	UPROPERTY(EditAnywhere, Category = Parameter)
-// 	EAbilityRunStatus EnterStateResult = EAbilityRunStatus::Running;
-// };
-//
-// USTRUCT()
-// struct FEzAbilityTestConditionInstanceData
-// {
-// 	GENERATED_BODY()
-// 	
-// 	UPROPERTY(EditAnywhere, Category = Parameters)
-// 	int32 Count = 1;
-//
-// 	static std::atomic<int32> GlobalCounter;
-// };
-//
-// USTRUCT(meta = (Hidden))
-// struct FEzAbilityTestCondition : public FEzAbilityCondition
-// {
-// 	GENERATED_BODY()
-//
-// 	using FInstanceDataType = FEzAbilityTestConditionInstanceData;
-//
-// 	FEzAbilityTestCondition() = default;
-//
-// 	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
-// 	virtual bool TestCondition(FEzAbilityContext& Context) const override
-// 	{
-// 		FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
-// 		InstanceData.GlobalCounter.fetch_add(InstanceData.Count);
-// 		return true;
-// 	}
-// };
-//
-// struct FEzAbilityTestRunContext
-// {
-// 	int32 Count = 0;
-// };
-//
-//
-// USTRUCT()
-// struct FEzAbilityTest_PropertyStructB
-// {
-// 	GENERATED_BODY()
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	int32 B = 0;
-// };
-//
-// USTRUCT()
-// struct FEzAbilityTest_PropertyStruct
-// {
-// 	GENERATED_BODY()
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	int32 A = 0;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	int32 B = 0;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	FEzAbilityTest_PropertyStructB StructB;
-// };
-//
-// UCLASS(HideDropdown)
-// class UEzAbilityTest_PropertyObjectInstanced : public UObject
-// {
-// 	GENERATED_BODY()
-// public:
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	int32 A = 0;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	FInstancedStruct InstancedStruct;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	TArray<FGameplayTag> ArrayOfTags;
-// };
-//
-// UCLASS(HideDropdown)
-// class UEzAbilityTest_PropertyObjectInstancedWithB : public UEzAbilityTest_PropertyObjectInstanced
-// {
-// 	GENERATED_BODY()
-// public:
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	int32 B = 0;
-// };
-//
-// UCLASS(HideDropdown)
-// class UEzAbilityTest_PropertyObject : public UObject
-// {
-// 	GENERATED_BODY()
-// public:
-// 	
-// 	UPROPERTY(EditAnywhere, Instanced, Category = "")
-// 	TObjectPtr<UEzAbilityTest_PropertyObjectInstanced> InstancedObject;
-//
-// 	UPROPERTY(EditAnywhere, Instanced, Category = "")
-// 	TArray<TObjectPtr<UEzAbilityTest_PropertyObjectInstanced>> ArrayOfInstancedObjects;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	TArray<int32> ArrayOfInts;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	FInstancedStruct InstancedStruct;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	TArray<FInstancedStruct> ArrayOfInstancedStructs;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	FEzAbilityTest_PropertyStruct Struct;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	TArray<FEzAbilityTest_PropertyStruct> ArrayOfStruct;
-// };
-//
-// UCLASS(HideDropdown)
-// class UEzAbilityTest_PropertyObject2 : public UObject
-// {
-// 	GENERATED_BODY()
-// public:
-// };
-//
-// USTRUCT()
-// struct FEzAbilityTest_PropertyCopy
-// {
-// 	GENERATED_BODY()
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	FEzAbilityTest_PropertyStruct Item;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	TArray<FEzAbilityTest_PropertyStruct> Array;
-// };
-//
-// USTRUCT()
-// struct FEzAbilityTest_PropertyRefSourceStruct
-// {
-// 	GENERATED_BODY()
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	FEzAbilityTest_PropertyStruct Item;
-//
-// 	UPROPERTY(EditAnywhere, Category = "Output")
-// 	FEzAbilityTest_PropertyStruct OutputItem;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	TArray<FEzAbilityTest_PropertyStruct> Array;
-// };
-//
-// USTRUCT()
-// struct FEzAbilityTest_PropertyRefTargetStruct
-// {
-// 	GENERATED_BODY()
-//
-// 	UPROPERTY(EditAnywhere, Category = "", meta = (RefType = "/Script/EzAbilityTestSuite.EzAbilityTest_PropertyStruct"))
-// 	FEzAbilityPropertyRef RefToStruct;
-//
-// 	UPROPERTY(EditAnywhere, Category = "", meta = (RefType = "Int32"))
-// 	FEzAbilityPropertyRef RefToInt;
-//
-// 	UPROPERTY(EditAnywhere, Category = "", meta = (RefType = "/Script/EzAbilityTestSuite.EzAbilityTest_PropertyStruct", IsRefToArray))
-// 	FEzAbilityPropertyRef RefToStructArray;
-// };
-//
-// USTRUCT()
-// struct FEzAbilityTest_PropertyCopyObjects
-// {
-// 	GENERATED_BODY()
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	TObjectPtr<UObject> Object;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	TSubclassOf<UObject> Class;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	TSoftObjectPtr<UObject> SoftObject;
-//
-// 	UPROPERTY(EditAnywhere, Category = "")
-// 	TSoftClassPtr<UObject> SoftClass;
-// };
+USTRUCT()
+struct FAbilityTestTask_PrintValueInstanceData
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	int32 Value = 0;
+};
+
+USTRUCT()
+struct FAbilityTestTask_PrintValue : public FEzAbilityTask
+{
+	GENERATED_BODY()
+
+	using FInstanceDataType = FAbilityTestTask_PrintValueInstanceData;
+
+	FAbilityTestTask_PrintValue() = default;
+	FAbilityTestTask_PrintValue(const FName InName) { Name = InName; }
+	virtual ~FAbilityTestTask_PrintValue() override {}
+	
+	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
+
+	virtual EAbilityRunStatus EnterState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
+	{
+		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
+		const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+		TestContext.Log(Name,  FString::Printf(TEXT("EnterState%d"), InstanceData.Value));
+		return EAbilityRunStatus::Running;
+	}
+
+	virtual void ExitState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
+	{
+		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
+		const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+		TestContext.Log(Name,  FString::Printf(TEXT("ExitState%d"), InstanceData.Value));
+	}
+
+	virtual EAbilityRunStatus Tick(FEzAbilityContext& Context, const float DeltaTime) const override
+	{
+		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
+		const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+		TestContext.Log(Name,  FString::Printf(TEXT("Tick%d"), InstanceData.Value));
+		
+		return EAbilityRunStatus::Running;
+	};
+};
+
+
+USTRUCT()
+struct FAbilityTestTask_StopTreeInstanceData
+{
+	GENERATED_BODY()
+};
+
+USTRUCT()
+struct FAbilityTestTask_StopTree : public FEzAbilityTask
+{
+	GENERATED_BODY()
+
+	using FInstanceDataType = FAbilityTestTask_PrintValueInstanceData;
+
+	FAbilityTestTask_StopTree() = default;
+	explicit FAbilityTestTask_StopTree(const FName InName) { Name = InName; }
+	virtual ~FAbilityTestTask_StopTree() override {}
+	
+	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
+
+	virtual EAbilityRunStatus EnterState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
+	{
+		if (Phase == EEzAbilityUpdatePhase::EnterStates)
+		{
+			return Context.Stop();
+		}
+		return EAbilityRunStatus::Running;
+	}
+
+	virtual void ExitState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
+	{
+		if (Phase == EEzAbilityUpdatePhase::ExitStates)
+		{
+			Context.Stop();
+		}
+	}
+
+	virtual EAbilityRunStatus Tick(FEzAbilityContext& Context, const float DeltaTime) const override
+	{
+		if (Phase == EEzAbilityUpdatePhase::Tick)
+		{
+			return Context.Stop();
+		}
+		return EAbilityRunStatus::Running;
+	};
+
+	/** Indicates in which phase the call to Stop should be performed. Possible values are EnterStates, ExitStats and TickEzAbility */
+	UPROPERTY()
+	EEzAbilityUpdatePhase Phase = EEzAbilityUpdatePhase::Unset;
+};
+
+USTRUCT()
+struct FAbilityTestTask_StandInstanceData
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	int32 Value = 0;
+	
+	UPROPERTY()
+	int32 CurrentTick = 0;
+};
+
+USTRUCT()
+struct FAbilityTestTask_Stand : public FEzAbilityTask
+{
+	GENERATED_BODY()
+
+	using FInstanceDataType = FAbilityTestTask_StandInstanceData;
+	
+	FAbilityTestTask_Stand() = default;
+	FAbilityTestTask_Stand(const FName InName) { Name = InName; }
+	virtual ~FAbilityTestTask_Stand() {}
+
+	virtual const UStruct* GetInstanceDataType() const { return FInstanceDataType::StaticStruct(); }
+
+	virtual EAbilityRunStatus EnterState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
+	{
+		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
+		TestContext.Log(Name, TEXT("EnterState"));
+
+		FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+		
+		if (Transition.ChangeType == EEzAbilityStateChangeType::Changed)
+		{
+			InstanceData.CurrentTick = 0;
+		}
+		return EnterStateResult;
+	}
+
+	virtual void ExitState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const override
+	{
+		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
+
+		if (Transition.CurrentRunStatus == EAbilityRunStatus::Succeeded)
+		{
+			TestContext.Log(Name, TEXT("ExitSucceeded"));
+		}
+		else if (Transition.CurrentRunStatus == EAbilityRunStatus::Failed)
+		{
+			TestContext.Log(Name, TEXT("ExitFailed"));
+		}
+		else if (Transition.CurrentRunStatus == EAbilityRunStatus::Stopped)
+		{
+			TestContext.Log(Name, TEXT("ExitStopped"));
+		}
+		
+		TestContext.Log(Name, TEXT("ExitState"));
+	}
+
+	virtual void StateCompleted(FEzAbilityContext& Context, const EAbilityRunStatus CompletionStatus, const FEzAbilityActiveStates& CompletedActiveStates) const override
+	{
+		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
+		TestContext.Log(Name, TEXT("StateCompleted"));
+	}
+	
+	virtual EAbilityRunStatus Tick(FEzAbilityContext& Context, const float DeltaTime) const override
+	{
+		FTestEzAbilityExecutionContext& TestContext = static_cast<FTestEzAbilityExecutionContext&>(Context);
+		TestContext.Log(Name, TEXT("Tick"));
+
+		FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+		
+		InstanceData.CurrentTick++;
+		
+		return (InstanceData.CurrentTick >= TicksToCompletion) ? TickCompletionResult : EAbilityRunStatus::Running;
+	};
+
+	UPROPERTY(EditAnywhere, Category = Parameter)
+	int32 TicksToCompletion = 1;
+
+	UPROPERTY(EditAnywhere, Category = Parameter)
+	EAbilityRunStatus TickCompletionResult = EAbilityRunStatus::Succeeded;
+
+	UPROPERTY(EditAnywhere, Category = Parameter)
+	EAbilityRunStatus EnterStateResult = EAbilityRunStatus::Running;
+};
+
+USTRUCT()
+struct FEzAbilityTestConditionInstanceData
+{
+	GENERATED_BODY()
+	
+	UPROPERTY(EditAnywhere, Category = Parameters)
+	int32 Count = 1;
+
+	static std::atomic<int32> GlobalCounter;
+};
+
+USTRUCT(meta = (Hidden))
+struct FEzAbilityTestCondition : public FEzAbilityCondition
+{
+	GENERATED_BODY()
+
+	using FInstanceDataType = FEzAbilityTestConditionInstanceData;
+
+	FEzAbilityTestCondition() = default;
+
+	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
+	virtual bool TestCondition(FEzAbilityContext& Context) const override
+	{
+		FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+		InstanceData.GlobalCounter.fetch_add(InstanceData.Count);
+		return true;
+	}
+};
+
+struct FEzAbilityTestRunContext
+{
+	int32 Count = 0;
+};
+
+USTRUCT()
+struct FEzAbilityTest_PropertyStructA
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "")
+	int32 A = 0;
+};
+
+USTRUCT()
+struct FEzAbilityTest_PropertyStructB
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "")
+	int32 B = 0;
+};
+
+USTRUCT()
+struct FEzAbilityTest_PropertyStruct
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "")
+	int32 A = 0;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	int32 B = 0;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	FEzAbilityTest_PropertyStructB StructB;
+};
+
+UCLASS(HideDropdown)
+class UEzAbilityTest_PropertyObjectInstanced : public UObject
+{
+	GENERATED_BODY()
+public:
+
+	UPROPERTY(EditAnywhere, Category = "")
+	int32 A = 0;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	FInstancedStruct InstancedStruct;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	TArray<FGameplayTag> ArrayOfTags;
+};
+
+UCLASS(HideDropdown)
+class UEzAbilityTest_PropertyObjectInstancedWithB : public UEzAbilityTest_PropertyObjectInstanced
+{
+	GENERATED_BODY()
+public:
+
+	UPROPERTY(EditAnywhere, Category = "")
+	int32 B = 0;
+};
+
+UCLASS(HideDropdown)
+class UEzAbilityTest_PropertyObject : public UObject
+{
+	GENERATED_BODY()
+public:
+	
+	UPROPERTY(EditAnywhere, Instanced, Category = "")
+	TObjectPtr<UEzAbilityTest_PropertyObjectInstanced> InstancedObject;
+
+	UPROPERTY(EditAnywhere, Instanced, Category = "")
+	TArray<TObjectPtr<UEzAbilityTest_PropertyObjectInstanced>> ArrayOfInstancedObjects;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	TArray<int32> ArrayOfInts;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	FInstancedStruct InstancedStruct;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	TArray<FInstancedStruct> ArrayOfInstancedStructs;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	FEzAbilityTest_PropertyStruct Struct;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	TArray<FEzAbilityTest_PropertyStruct> ArrayOfStruct;
+};
+
+UCLASS(HideDropdown)
+class UEzAbilityTest_PropertyObject2 : public UObject
+{
+	GENERATED_BODY()
+public:
+};
+
+USTRUCT()
+struct FEzAbilityTest_PropertyCopy
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "")
+	FEzAbilityTest_PropertyStruct Item;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	TArray<FEzAbilityTest_PropertyStruct> Array;
+};
+
+USTRUCT()
+struct FEzAbilityTest_PropertyRefSourceStruct
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "")
+	FEzAbilityTest_PropertyStruct Item;
+
+	UPROPERTY(EditAnywhere, Category = "Output")
+	FEzAbilityTest_PropertyStruct OutputItem;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	TArray<FEzAbilityTest_PropertyStruct> Array;
+};
+
+USTRUCT()
+struct FEzAbilityTest_PropertyRefTargetStruct
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "", meta = (RefType = "/Script/EzAbilityTestSuite.EzAbilityTest_PropertyStruct"))
+	FEzAbilityPropertyRef RefToStruct;
+
+	UPROPERTY(EditAnywhere, Category = "", meta = (RefType = "Int32"))
+	FEzAbilityPropertyRef RefToInt;
+
+	UPROPERTY(EditAnywhere, Category = "", meta = (RefType = "/Script/EzAbilityTestSuite.EzAbilityTest_PropertyStruct", IsRefToArray))
+	FEzAbilityPropertyRef RefToStructArray;
+};
+
+USTRUCT()
+struct FEzAbilityTest_PropertyCopyObjects
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "")
+	TObjectPtr<UObject> Object;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	TSubclassOf<UObject> Class;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	TSoftObjectPtr<UObject> SoftObject;
+
+	UPROPERTY(EditAnywhere, Category = "")
+	TSoftClassPtr<UObject> SoftClass;
+};