孟宇 3 meses atrás
pai
commit
e566b0b363

+ 1799 - 63
Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbilityContext.cpp

@@ -9,6 +9,7 @@
 #include "Evaluator/EzAbilityEvaluator.h"
 #include "Task/EzAbilityTask.h"
 #include "EzAbilityLog.h"
+#include "Condition/EzAbilityCondition.h"
 #include "Debugger/EzAbilityTrace.h"
 #include "Debugger/EzAbilityTraceTypes.h"
 
@@ -207,6 +208,13 @@ EAbilityRunStatus FEzAbilityContext::Start(UEzAbility* InAbility, const FEzAbili
 	
 	UpdateInstanceData({}, State.ActiveFrames);
 
+	if (!CollectActiveExternalData())
+	{
+		EZ_ABILITY_LOG(Warning, TEXT("%hs: Failed to collect external data ('%s' using EzAbility '%s')"),
+			__FUNCTION__, *GetNameSafe(Owner), *GetFullNameSafe(Ability));
+		return EAbilityRunStatus::Failed;
+	}
+	
 	// Must sent instance creation event first 
 	EZ_ABILITY_TRACE_INSTANCE_EVENT(EEzAbilityTraceEventType::Push);
 	
@@ -223,11 +231,57 @@ EAbilityRunStatus FEzAbilityContext::Start(UEzAbility* InAbility, const FEzAbili
 
 	if (GlobalTasksRunStatus == EAbilityRunStatus::Running)
 	{
-		
+		// First tick.
+		// Tasks are not ticked here, since their behavior is that EnterState() (called above) is treated as a tick.  
+		TickEvaluatorsAndGlobalTasks(0.0f, /*bTickGlobalTasks*/false);
+
+		// Initialize to unset running state.
+		State.TreeRunStatus = EAbilityRunStatus::Running;
+		State.LastTickStatus = EAbilityRunStatus::Unset;
+
+		static const FEzAbilityStateHandle RootState = FEzAbilityStateHandle(0);
+
+		TArray<FEzAbilityExecutionFrame, TFixedAllocator<MaxExecutionFrames>> NextActiveFrames;
+		if (SelectState(InitFrame, RootState, NextActiveFrames))
+		{
+			check(!NextActiveFrames.IsEmpty());
+			if (NextActiveFrames.Last().ActiveStates.Last().IsCompletionState())
+			{
+				// Transition to a terminal state (succeeded/failed).
+				EZ_ABILITY_LOG(Warning, TEXT("%hs: Tree %s at EzAbility start on '%s' using EzAbility '%s'."),
+					__FUNCTION__, NextActiveFrames.Last().ActiveStates.Last() == FEzAbilityStateHandle::Succeeded ? TEXT("succeeded") : TEXT("failed"), *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
+				State.TreeRunStatus = NextActiveFrames.Last().ActiveStates.Last().ToCompletionStatus();
+			}
+			else
+			{
+				// Enter state tasks can fail/succeed, treat it same as tick.
+				FEzAbilityTransitionResult Transition;
+				Transition.TargetState = RootState;
+				Transition.CurrentRunStatus = State.LastTickStatus;
+				Transition.NextActiveFrames = NextActiveFrames; // Enter state will update Exec.ActiveFrames.
+				const EAbilityRunStatus LastTickStatus = EnterState(Transition);
+			
+				State.LastTickStatus = LastTickStatus;
+
+				// Report state completed immediately.
+				if (State.LastTickStatus != EAbilityRunStatus::Running)
+				{
+					StateCompleted();
+				}
+			}
+		}
+
+		if (State.LastTickStatus == EAbilityRunStatus::Unset)
+		{
+			// Should not happen. This may happen if initial state could not be selected.
+			EZ_ABILITY_LOG(Error, TEXT("%hs: Failed to select initial state on '%s' using EzAbility '%s'. This should not happen, check that the EzAbility logic can always select a state at start."),
+				__FUNCTION__, *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
+			State.TreeRunStatus = EAbilityRunStatus::Failed;
+		}
 	}
 	else
 	{
-		//StopEvaluatorsAndGlobalTasks(GlobalTasksRunStatus, LastInitializedTaskIndex);
+		StopEvaluatorsAndGlobalTasks(GlobalTasksRunStatus, LastInitializedTaskIndex);
 
 		EZ_ABILITY_LOG(VeryVerbose, TEXT("%hs: Global tasks completed the EzAbility %s on start in status '%s'."),
 			__FUNCTION__, *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()), *UEnum::GetDisplayValueAsText(GlobalTasksRunStatus).ToString());
@@ -611,6 +665,106 @@ void FEzAbilityContext::UpdateInstanceData(TConstArrayView<FEzAbilityExecutionFr
 	InstanceData.ResetTemporaryInstances();
 }
 
+EAbilityRunStatus FEzAbilityContext::StartTemporaryEvaluatorsAndGlobalTasks(const FEzAbilityExecutionFrame* CurrentParentFrame, const FEzAbilityExecutionFrame& CurrentFrame)
+{
+	if (!CurrentFrame.bIsGlobalFrame)
+	{
+		return EAbilityRunStatus::Failed;
+	}
+	
+	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_StartEvaluators);
+	// @todo: figure out debugger phase for temporary start.
+	//	EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::StartGlobalTasks);
+
+	EZ_ABILITY_LOG(Verbose, TEXT("Start Temporary Evaluators & Global tasks while trying to select linked asset: %s"), *GetNameSafe(CurrentFrame.Ability));
+
+	FEzAbilityExecutionState& Exec = GetExecState();
+	EAbilityRunStatus Result = EAbilityRunStatus::Running;
+	FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
+	const UEzAbility* CurrentEzAbility = CurrentFrame.Ability;
+
+	// Start evaluators
+	for (int32 EvalIndex = CurrentEzAbility->EvaluatorsBegin; EvalIndex < (CurrentEzAbility->EvaluatorsBegin + CurrentEzAbility->EvaluatorsNum); EvalIndex++)
+	{
+		const FEzAbilityEvaluator& Eval = CurrentEzAbility->Nodes[EvalIndex].Get<const FEzAbilityEvaluator>();
+		FEzAbilityDataView EvalInstanceView = GetDataViewOrTemporary(CurrentParentFrame, CurrentFrame, Eval.InstanceDataHandle);
+		bool bWasCreated = false;
+		if (!EvalInstanceView.IsValid())
+		{
+			EvalInstanceView = AddTemporaryInstance(CurrentFrame, FEzAbilityIndex16(EvalIndex), Eval.InstanceDataHandle, CurrentFrame.Ability->DefaultInstanceData.GetStruct(Eval.InstanceTemplateIndex.Get()));
+			check(EvalInstanceView.IsValid());
+			bWasCreated = true;
+		}
+		
+		FNodeInstanceDataScope DataScope(*this, Eval.InstanceDataHandle, EvalInstanceView);
+		// Copy bound properties.
+		if (Eval.BindingsBatch.IsValid())
+		{
+			CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, EvalInstanceView, Eval.BindingsBatch);
+		}
+
+		if (bWasCreated)
+		{
+			EZ_ABILITY_LOG(Verbose, TEXT("  Start: '%s'"), *Eval.Name.ToString());
+			{
+				QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Eval_TreeStart);
+				Eval.Start(*this);
+
+				EZ_ABILITY_TRACE_EVALUATOR_EVENT(EvalIndex, EvalInstanceView, EEzAbilityTraceEventType::OnAbilityStarted);
+			}
+		}
+	}
+
+	// Start Global tasks
+	// Even if we call Enter/ExitState() on global tasks, they do not enter any specific state.
+	const FEzAbilityTransitionResult Transition = {}; // Empty transition
+
+	for (int32 TaskIndex = CurrentEzAbility->GlobalTasksBegin; TaskIndex < (CurrentEzAbility->GlobalTasksBegin + CurrentEzAbility->GlobalTasksNum); TaskIndex++)
+	{
+		const FEzAbilityTask& Task =  CurrentEzAbility->Nodes[TaskIndex].Get<const FEzAbilityTask>();
+		// Ignore disabled task
+		if (Task.bTaskEnabled == false)
+		{
+			EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'EnterState' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
+			continue;
+		}
+
+		FEzAbilityDataView TaskDataView = GetDataViewOrTemporary(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
+		bool bWasCreated = false;
+		if (!TaskDataView.IsValid())
+		{
+			TaskDataView = AddTemporaryInstance(CurrentFrame, FEzAbilityIndex16(TaskIndex), Task.InstanceDataHandle, CurrentFrame.Ability->DefaultInstanceData.GetStruct(Task.InstanceTemplateIndex.Get()));
+			check(TaskDataView.IsValid())
+			bWasCreated = true;
+		}
+
+		FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskDataView);
+
+		// Copy bound properties.
+		if (Task.BindingsBatch.IsValid())
+		{
+			CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskDataView, Task.BindingsBatch);
+		}
+
+		EZ_ABILITY_LOG(Verbose, TEXT("  Start: '%s'"), *Task.Name.ToString());
+		if (bWasCreated)
+		{
+			QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Task_TreeStart);
+			const EAbilityRunStatus TaskStatus = Task.EnterState(*this, Transition);
+
+			EZ_ABILITY_TRACE_TASK_EVENT(TaskIndex, TaskDataView, EEzAbilityTraceEventType::OnEntered, TaskStatus);
+
+			if (TaskStatus != EAbilityRunStatus::Running)
+			{
+				Result = TaskStatus;
+				break;
+			}
+		}
+	}
+
+	return Result;
+}
+
 void FEzAbilityContext::StopTemporaryEvaluatorsAndGlobalTasks(TArrayView<FEzAbilityTemporaryInstanceData> TempInstances)
 {
 	EZ_ABILITY_CLOG(!TempInstances.IsEmpty(), Verbose, TEXT("Stop Temporary Evaluators & Global tasks left over from previous state selection"));
@@ -668,91 +822,1673 @@ void FEzAbilityContext::StopTemporaryEvaluatorsAndGlobalTasks(TArrayView<FEzAbil
 	}
 }
 
-EAbilityRunStatus FEzAbilityContext::StartEvaluatorsAndGlobalTasks(FEzAbilityIndex16& OutLastInitializedTaskIndex)
+EAbilityRunStatus FEzAbilityContext::EnterState(FEzAbilityTransitionResult& Transition)
 {
-	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_StartEvaluators);
-	EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::StartGlobalTasks);
+	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_EnterState);
 
-	EZ_ABILITY_LOG(Verbose, TEXT("Start Evaluators & Global tasks"));
+	if (Transition.NextActiveFrames.IsEmpty())
+	{
+		return EAbilityRunStatus::Failed;
+	}
 
-	FEzAbilityExecutionState& State = GetExecState();
+	FEzAbilityExecutionState& Exec = GetExecState();
 
-	OutLastInitializedTaskIndex = FEzAbilityIndex16();
+	// Allocate new tasks.
+	UpdateInstanceData(Exec.ActiveFrames, Transition.NextActiveFrames);
+
+	Exec.StateChangeCount++;
+	Exec.CompletedFrameIndex = FEzAbilityIndex16::Invalid;
+	Exec.CompletedStateHandle = FEzAbilityStateHandle::Invalid;
+	Exec.EnterStateFailedFrameIndex = FEzAbilityIndex16::Invalid; // This will make all tasks to be accepted.
+	Exec.EnterStateFailedTaskIndex = FEzAbilityIndex16::Invalid; // This will make all tasks to be accepted.
+	
+	// On target branch means that the state is the target of current transition or child of it.
+	// States which were active before and will remain active, but are not on target branch will not get
+	// EnterState called. That is, a transition is handled as "replan from this state".
+	bool bOnTargetBranch = false;
+	FEzAbilityTransitionResult CurrentTransition = Transition;
 	EAbilityRunStatus Result = EAbilityRunStatus::Running;
 
-	for (int32 FrameIndex = 0; FrameIndex < State.ActiveFrames.Num(); FrameIndex++)
+	EZ_ABILITY_LOG(Log, TEXT("Enter state '%s' (%d)"), *DebugGetStatePath(Transition.NextActiveFrames), Exec.StateChangeCount);
+	EZ_ABILITY_TRACE_PHASE_BEGIN(EEzAbilityUpdatePhase::EnterStates);
+
+	// The previous active frames are needed for state enter logic.
+	TArray<FEzAbilityExecutionFrame, TConcurrentLinearArrayAllocator<FDefaultBlockAllocationTag>> PreviousActiveFrames;
+	PreviousActiveFrames = Exec.ActiveFrames;
+
+	// Reset the current active frames, new ones are added one by one.
+	Exec.ActiveFrames.Reset();
+
+	for (int32 FrameIndex = 0; FrameIndex < Transition.NextActiveFrames.Num() && Result != EAbilityRunStatus::Failed; FrameIndex++)
 	{
-		FEzAbilityExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &State.ActiveFrames[FrameIndex - 1] : nullptr;
-		FEzAbilityExecutionFrame& CurrentFrame = State.ActiveFrames[FrameIndex];
+		const FEzAbilityExecutionFrame& NextFrame = Transition.NextActiveFrames[FrameIndex];
+		
+		FEzAbilityExecutionFrame* CurrentParentFrame = !Exec.ActiveFrames.IsEmpty() ? &Exec.ActiveFrames.Last() : nullptr;
+		FEzAbilityExecutionFrame& CurrentFrame = Exec.ActiveFrames.Add_GetRef(NextFrame);
+		
+		// We'll add new states one by one, so that active states contain only the states which have EnterState called.
+		CurrentFrame.ActiveStates.Reset();
 
-		if (CurrentFrame.bIsGlobalFrame)
-        {
-        	FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
-        			
-        	const UEzAbility* CurrentAbility = CurrentFrame.Ability;
-        
-        	// Start evaluators
-        	for (int32 EvalIndex = CurrentAbility->EvaluatorsBegin; EvalIndex < (CurrentAbility->EvaluatorsBegin + CurrentAbility->EvaluatorsNum); EvalIndex++)
-        	{
-        		const FEzAbilityEvaluator& Eval = CurrentAbility->Nodes[EvalIndex].Get<const FEzAbilityEvaluator>();
-        		const FEzAbilityDataView EvalInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Eval.InstanceDataHandle);
-        		FNodeInstanceDataScope DataScope(*this, Eval.InstanceDataHandle, EvalInstanceView);
-        
-        		// Copy bound properties.
-        		if (Eval.BindingsBatch.IsValid())
-        		{
-        			//CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, EvalInstanceView, Eval.BindingsBatch);
-        		}
-        		EZ_ABILITY_LOG(Verbose, TEXT("  Start: '%s'"), *Eval.Name.ToString());
-        		{
-        			QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Eval_TreeStart);
-        			// Eval.TreeStart(*this);
-           //
-        			// EZ_ABILITY_TRACE_EVALUATOR_EVENT(EvalIndex, EvalInstanceView, EEzAbilityTraceEventType::OnTreeStarted);
-        		}
-        	}
-        
-			// Start Global tasks
-			// Even if we call Enter/ExitState() on global tasks, they do not enter any specific state.
-			const FEzAbilityTransitionResult Transition = {}; // Empty transition
-        		
-			for (int32 TaskIndex = CurrentAbility->GlobalTasksBegin; TaskIndex < (CurrentAbility->GlobalTasksBegin + CurrentAbility->GlobalTasksNum); TaskIndex++)
+		// Get previous active states, they are used to calculate transition type.
+		FEzAbilityActiveStates PreviousActiveStates;
+		if (PreviousActiveFrames.IsValidIndex(FrameIndex)
+			&& PreviousActiveFrames[FrameIndex].IsSameFrame(NextFrame))
+		{
+			PreviousActiveStates = PreviousActiveFrames[FrameIndex].ActiveStates;
+		}
+
+		FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
+		const UEzAbility* CurrentEzAbility = NextFrame.Ability;
+
+		for (int32 Index = 0; Index < NextFrame.ActiveStates.Num() && Result != EAbilityRunStatus::Failed; Index++)
+		{
+			const FEzAbilityStateHandle CurrentHandle = NextFrame.ActiveStates[Index];
+			const FEzAbilityStateHandle PreviousHandle = PreviousActiveStates.GetStateSafe(Index);
+			const FCompactEzAbilityState& State = CurrentEzAbility->States[CurrentHandle.Index];
+
+			FCurrentlyProcessedStateScope StateScope(*this, CurrentHandle);
+			
+			// Add only enabled States to the list of active States
+			if (State.bEnabled && !CurrentFrame.ActiveStates.Push(CurrentHandle))
 			{
-				const FEzAbilityTask& Task =  CurrentAbility->Nodes[TaskIndex].Get<const FEzAbilityTask>();
+				EZ_ABILITY_LOG(Error, TEXT("%hs: Reached max execution depth when trying to enter state '%s'.  '%s' using EzAbility '%s'."),
+					__FUNCTION__, *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
+				break;
+			}
+			CurrentFrame.NumCurrentlyActiveStates = static_cast<uint8>(CurrentFrame.ActiveStates.Num());
+
+			if (State.Type == EEzAbilityStateType::Linked
+				|| State.Type == EEzAbilityStateType::LinkedAsset)
+			{
+				if (State.ParameterDataHandle.IsValid()
+					&& State.ParameterBindingsBatch.IsValid())
+				{
+					const FEzAbilityDataView StateParamsDataView = GetDataView(CurrentParentFrame, CurrentFrame, State.ParameterDataHandle);
+					CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, StateParamsDataView, State.ParameterBindingsBatch);
+				}
+			}
+
+			bOnTargetBranch = bOnTargetBranch || CurrentHandle == Transition.TargetState;
+			const bool bWasActive = PreviousHandle == CurrentHandle;
+
+			// Do not enter a disabled State tasks but maintain property bindings
+			const bool bIsEnteringState = (!bWasActive || bOnTargetBranch) && State.bEnabled;
+
+			CurrentTransition.CurrentState = CurrentHandle;
+			CurrentTransition.ChangeType = bWasActive ? EEzAbilityStateChangeType::Sustained : EEzAbilityStateChangeType::Changed;
+
+			if (bIsEnteringState)
+			{
+				EZ_ABILITY_TRACE_STATE_EVENT(CurrentHandle, EEzAbilityTraceEventType::OnEntering);
+				EZ_ABILITY_LOG(Log, TEXT("%*sState '%s' %s"), Index*UE::EzAbility::DebugIndentSize, TEXT(""),
+					*DebugGetStatePath(Transition.NextActiveFrames, &NextFrame, Index),
+					*UEnum::GetDisplayValueAsText(CurrentTransition.ChangeType).ToString());
+			}
+
+			// Activate tasks on current state.
+			for (int32 TaskIndex = State.TasksBegin; TaskIndex < (State.TasksBegin + State.TasksNum); TaskIndex++)
+			{
+				const FEzAbilityTask& Task = NextFrame.Ability->Nodes[TaskIndex].Get<const FEzAbilityTask>();
 				const FEzAbilityDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
+
 				FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
-        
+
 				// Copy bound properties.
 				if (Task.BindingsBatch.IsValid())
 				{
-					//CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
+					CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
 				}
-        
+
 				// Ignore disabled task
 				if (Task.bTaskEnabled == false)
 				{
 					EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'EnterState' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
 					continue;
 				}
-        
-				EZ_ABILITY_LOG(Verbose, TEXT("  Start: '%s'"), *Task.Name.ToString());
+
+				const bool bShouldCallStateChange = CurrentTransition.ChangeType == EEzAbilityStateChangeType::Changed
+													|| (CurrentTransition.ChangeType == EEzAbilityStateChangeType::Sustained && Task.bShouldStateChangeOnReselect);
+
+				if (bIsEnteringState && bShouldCallStateChange)
 				{
-					QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Task_TreeStart);
-					// const EAbilityRunStatus TaskStatus = Task.EnterState(*this, Transition); 
-     //    
-					// EZ_ABILITY_TRACE_TASK_EVENT(TaskIndex, TaskInstanceView, EEzAbilityTraceEventType::OnEntered, TaskStatus);
-     //    
-					// if (TaskStatus != EAbilityRunStatus::Running)
-					// {
-					// 	OutLastInitializedTaskIndex = FEzAbilityIndex16(TaskIndex);
-					// 	Result = TaskStatus;
-					// 	break;
-     //    			}
-        		}
-        	}
-        }
+					EZ_ABILITY_LOG(Verbose, TEXT("%*s  Task '%s'"), Index*UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
+
+					EAbilityRunStatus Status = EAbilityRunStatus::Unset;
+					{
+						QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Task_EnterState);
+						CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_Task_EnterState);
+					
+						Status = Task.EnterState(*this, CurrentTransition);
+					}
+
+					EZ_ABILITY_TRACE_TASK_EVENT(TaskIndex, TaskInstanceView, EEzAbilityTraceEventType::OnEntered, Status);
+
+					if (Status != EAbilityRunStatus::Running)
+					{
+						// Store the first state that completed, will be used to decide where to trigger transitions.
+						if (!Exec.CompletedStateHandle.IsValid())
+						{
+							Exec.CompletedFrameIndex = FEzAbilityIndex16(FrameIndex);
+							Exec.CompletedStateHandle = CurrentHandle;
+						}
+						Result = Status;
+					}
+					
+					if (Status == EAbilityRunStatus::Failed)
+					{
+						// Store how far in the enter state we got. This will be used to match the StateCompleted() and ExitState() calls.
+						Exec.EnterStateFailedFrameIndex = FEzAbilityIndex16(FrameIndex); 
+						Exec.EnterStateFailedTaskIndex = FEzAbilityIndex16(TaskIndex);
+						break;
+					}
+				}
+			}
+
+			if (bIsEnteringState)
+			{
+				EZ_ABILITY_TRACE_STATE_EVENT(CurrentHandle, EEzAbilityTraceEventType::OnEntered);
+			}
+		}
+	}
+
+	EZ_ABILITY_TRACE_PHASE_END(EEzAbilityUpdatePhase::EnterStates);
+
+	EZ_ABILITY_TRACE_ACTIVE_STATES_EVENT(Exec.ActiveFrames);
+
+	return Result;
+}
+
+void FEzAbilityContext::ExitState(const FEzAbilityTransitionResult& Transition)
+{
+	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_ExitState);
+
+	FEzAbilityExecutionState& Exec = GetExecState();
+
+	if (Exec.ActiveFrames.IsEmpty())
+	{
+		return;
 	}
+
+	// On target branch means that the state is the target of current transition or child of it.
+	// States which were active before and will remain active, but are not on target branch will not get
+	// EnterState called. That is, a transition is handled as "replan from this state".
+	bool bOnTargetBranch = false;
+
+	struct FExitStateCall
+	{
+		FExitStateCall() = default;
+		FExitStateCall(const EEzAbilityStateChangeType InChangeType, const bool bInShouldCall)
+			: ChangeType(InChangeType)
+			, bShouldCall(bInShouldCall)
+		{
+		}
+
+		EEzAbilityStateChangeType ChangeType = EEzAbilityStateChangeType::None;
+		bool bShouldCall = false; 
+	};
+
+	TArray<FExitStateCall, TConcurrentLinearArrayAllocator<FDefaultBlockAllocationTag>> ExitStateCalls;
 	
+	for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); FrameIndex++)
+	{
+		FEzAbilityExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
+		FEzAbilityExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
+		const UEzAbility* CurrentEzAbility = CurrentFrame.Ability;
+
+		FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
+
+		const FEzAbilityExecutionFrame* NextFrame = nullptr;
+		if (Transition.NextActiveFrames.IsValidIndex(FrameIndex)
+			&& Transition.NextActiveFrames[FrameIndex].IsSameFrame(CurrentFrame))
+		{
+			NextFrame = &Transition.NextActiveFrames[FrameIndex];
+		}
+
+		for (int32 Index = 0; Index < CurrentFrame.ActiveStates.Num(); Index++)
+		{
+			const FEzAbilityStateHandle CurrentHandle = CurrentFrame.ActiveStates[Index];
+			const FEzAbilityStateHandle NextHandle = NextFrame ? NextFrame->ActiveStates.GetStateSafe(Index) : FEzAbilityStateHandle::Invalid;
+			const FCompactEzAbilityState& State = CurrentEzAbility->States[CurrentHandle.Index];
+
+			FCurrentlyProcessedStateScope StateScope(*this, CurrentHandle);
+
+			if (State.Type == EEzAbilityStateType::Linked
+				|| State.Type == EEzAbilityStateType::LinkedAsset)
+			{
+				if (State.ParameterDataHandle.IsValid()
+					&& State.ParameterBindingsBatch.IsValid())
+				{
+					const FEzAbilityDataView StateParamsDataView = GetDataView(CurrentParentFrame, CurrentFrame, State.ParameterDataHandle);
+					CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, StateParamsDataView, State.ParameterBindingsBatch);
+				}
+			}
+
+			const bool bRemainsActive = NextHandle == CurrentHandle;
+			bOnTargetBranch = bOnTargetBranch || NextHandle == Transition.TargetState;
+			const EEzAbilityStateChangeType ChangeType = bRemainsActive ? EEzAbilityStateChangeType::Sustained : EEzAbilityStateChangeType::Changed;
+			
+			// Should call ExitState() on this state.
+			const bool bShouldCall = !bRemainsActive || bOnTargetBranch; 
+			ExitStateCalls.Emplace(ChangeType, bShouldCall);
+
+			// Do property copies, ExitState() is called below.
+			for (int32 TaskIndex = State.TasksBegin; TaskIndex < (State.TasksBegin + State.TasksNum); TaskIndex++)
+			{
+				const FEzAbilityTask& Task = CurrentEzAbility->Nodes[TaskIndex].Get<const FEzAbilityTask>();
+				const FEzAbilityDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
+
+				// Copy bound properties.
+				if (Task.BindingsBatch.IsValid() && Task.bShouldCopyBoundPropertiesOnExitState)
+				{
+					CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
+				}
+			}
+		}
+	}
+
+	// Call in reverse order.
+	EZ_ABILITY_LOG(Log, TEXT("Exit state '%s' (%d)"), *DebugGetStatePath(Exec.ActiveFrames), Exec.StateChangeCount);
+	EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::ExitStates);
+
+	FEzAbilityTransitionResult CurrentTransition = Transition;
+	int32 CallIndex = ExitStateCalls.Num() - 1;
+
+	for (int32 FrameIndex = Exec.ActiveFrames.Num() - 1; FrameIndex >= 0; FrameIndex--)
+	{
+		FEzAbilityExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
+		FEzAbilityExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
+		const UEzAbility* CurrentEzAbility = CurrentFrame.Ability;
+
+		FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
+
+		for (int32 Index = CurrentFrame.ActiveStates.Num() - 1; Index >= 0; Index--)
+		{
+			const FEzAbilityStateHandle CurrentHandle = CurrentFrame.ActiveStates[Index];
+			const FCompactEzAbilityState& State = CurrentEzAbility->States[CurrentHandle.Index];
+
+			const FExitStateCall& ExitCall = ExitStateCalls[CallIndex--];
+			CurrentTransition.ChangeType = ExitCall.ChangeType;
+
+			EZ_ABILITY_LOG(Log, TEXT("%*sState '%s' %s"), Index*UE::EzAbility::DebugIndentSize, TEXT(""), *DebugGetStatePath(Exec.ActiveFrames, &CurrentFrame, CurrentHandle.Index), *UEnum::GetDisplayValueAsText(CurrentTransition.ChangeType).ToString());
+
+			EZ_ABILITY_TRACE_STATE_EVENT(CurrentHandle, EEzAbilityTraceEventType::OnExiting);
+				
+			if (ExitCall.bShouldCall)
+			{
+				FCurrentlyProcessedStateScope StateScope(*this, CurrentHandle);
+
+				// Remove any delayed transitions that belong to this state.
+				Exec.DelayedTransitions.RemoveAllSwap(
+					[EzAbility = CurrentFrame.Ability, Begin = State.TransitionsBegin, End = State.TransitionsBegin + State.TransitionsNum](const FEzAbilityTransitionDelayedState& DelayedState)
+					{
+						return  DelayedState.Ability == EzAbility && DelayedState.TransitionIndex.Get() >= Begin && DelayedState.TransitionIndex.Get() < End;
+					});
+
+				CurrentTransition.CurrentState = CurrentHandle;
+
+				// Do property copies, ExitState() is called below.
+				for (int32 TaskIndex = (State.TasksBegin + State.TasksNum) - 1; TaskIndex >= State.TasksBegin; TaskIndex--)
+				{
+
+					// Call task completed only if EnterState() was called.
+					// The task order in the tree (BF) allows us to use the comparison.
+					// Relying here that invalid value of Exec.EnterStateFailedTaskIndex == MAX_uint16.
+					if (TaskIndex <= Exec.EnterStateFailedTaskIndex.Get())
+					{
+						const FEzAbilityTask& Task = CurrentEzAbility->Nodes[TaskIndex].Get<const FEzAbilityTask>();
+						const FEzAbilityDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
+
+						FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
+
+						// Ignore disabled task
+						if (Task.bTaskEnabled == false)
+						{
+							EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'ExitState' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
+							continue;
+						}
+
+						const bool bShouldCallStateChange = CurrentTransition.ChangeType == EEzAbilityStateChangeType::Changed
+									|| (CurrentTransition.ChangeType == EEzAbilityStateChangeType::Sustained && Task.bShouldStateChangeOnReselect);
+
+						if (bShouldCallStateChange)
+						{
+
+							EZ_ABILITY_LOG(Verbose, TEXT("%*s  Task '%s'"), Index*UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
+							{
+								QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Task_ExitState);
+								CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_Task_ExitState);
+								Task.ExitState(*this, CurrentTransition);
+							}
+							EZ_ABILITY_TRACE_TASK_EVENT(TaskIndex, TaskInstanceView, EEzAbilityTraceEventType::OnExited, Transition.CurrentRunStatus);
+						}
+					}
+				}
+			}
+
+			EZ_ABILITY_TRACE_STATE_EVENT(CurrentHandle, EEzAbilityTraceEventType::OnExited);
+		}
+	}
+}
+
+void FEzAbilityContext::StateCompleted()
+{
+	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_StateCompleted);
+
+	const FEzAbilityExecutionState& Exec = GetExecState();
+
+	if (Exec.ActiveFrames.IsEmpty())
+	{
+		return;
+	}
+
+	EZ_ABILITY_LOG(Verbose, TEXT("State Completed %s (%d)"), *UEnum::GetDisplayValueAsText(Exec.LastTickStatus).ToString(), Exec.StateChangeCount);
+	EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::StateCompleted);
+
+	// Call from child towards root to allow to pass results back.
+	// Note: Completed is assumed to be called immediately after tick or enter state, so there's no property copying.
+
+	for (int32 FrameIndex = Exec.ActiveFrames.Num() - 1; FrameIndex >= 0; FrameIndex--)
+	{
+		const FEzAbilityExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
+		const FEzAbilityExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
+		const UEzAbility* CurrentEzAbility = CurrentFrame.Ability;
+
+		FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
+
+		if (FrameIndex <= Exec.EnterStateFailedFrameIndex.Get())
+		{
+			for (int32 Index = CurrentFrame.ActiveStates.Num() - 1; Index >= 0; Index--)
+			{
+				const FEzAbilityStateHandle CurrentHandle = CurrentFrame.ActiveStates[Index];
+				const FCompactEzAbilityState& State = CurrentEzAbility->States[CurrentHandle.Index];
+
+				FCurrentlyProcessedStateScope StateScope(*this, CurrentHandle);
+				
+				EZ_ABILITY_LOG(Verbose, TEXT("%*sState '%s'"), Index*UE::EzAbility::DebugIndentSize, TEXT(""), *DebugGetStatePath(Exec.ActiveFrames, &CurrentFrame, Index));
+				EZ_ABILITY_TRACE_STATE_EVENT(CurrentHandle, EEzAbilityTraceEventType::OnStateCompleted);
+
+				// Notify Tasks
+				for (int32 TaskIndex = (State.TasksBegin + State.TasksNum) - 1; TaskIndex >= State.TasksBegin; TaskIndex--)
+				{
+					// Call task completed only if EnterState() was called.
+					// The task order in the tree (BF) allows us to use the comparison.
+					// Relying here that invalid value of Exec.EnterStateFailedTaskIndex == MAX_uint16.
+					if (TaskIndex <= Exec.EnterStateFailedTaskIndex.Get())
+					{
+						const FEzAbilityTask& Task = CurrentEzAbility->Nodes[TaskIndex].Get<const FEzAbilityTask>();
+						const FEzAbilityDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
+						FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
+
+						// Ignore disabled task
+						if (Task.bTaskEnabled == false)
+						{
+							EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'StateCompleted' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
+							continue;
+						}
+						
+						EZ_ABILITY_LOG(Verbose, TEXT("%*s  Task '%s'"), Index*UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
+						Task.StateCompleted(*this, Exec.LastTickStatus, CurrentFrame.ActiveStates);
+					}
+				}
+			}
+		}
+	}
+}
+
+EAbilityRunStatus FEzAbilityContext::TickEvaluatorsAndGlobalTasks(const float DeltaTime, bool bTickGlobalTasks)
+{
+	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_TickEvaluators);
+	EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::TickingGlobalTasks);
+
+	EZ_ABILITY_LOG(VeryVerbose, TEXT("Ticking Evaluators & Global Tasks"));
+
+	FEzAbilityExecutionState& Exec = GetExecState();
+
+	EAbilityRunStatus Result = EAbilityRunStatus::Running;
+
+	for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); FrameIndex++)
+	{
+		FEzAbilityExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
+		FEzAbilityExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
+		if (CurrentFrame.bIsGlobalFrame)
+		{
+			FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
+
+			const UEzAbility* CurrentEzAbility = CurrentFrame.Ability;
+
+			// Tick evaluators
+			for (int32 EvalIndex = CurrentEzAbility->EvaluatorsBegin; EvalIndex < (CurrentEzAbility->EvaluatorsBegin + CurrentEzAbility->EvaluatorsNum); EvalIndex++)
+			{
+				const FEzAbilityEvaluator& Eval = CurrentEzAbility->Nodes[EvalIndex].Get<const FEzAbilityEvaluator>();
+				const FEzAbilityDataView EvalInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Eval.InstanceDataHandle);
+				FNodeInstanceDataScope DataScope(*this, Eval.InstanceDataHandle, EvalInstanceView);
+
+				// Copy bound properties.
+				if (Eval.BindingsBatch.IsValid())
+				{
+					CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, EvalInstanceView, Eval.BindingsBatch);
+				}
+				
+				EZ_ABILITY_LOG(VeryVerbose, TEXT("  Tick: '%s'"), *Eval.Name.ToString());
+				{
+					QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Eval_Tick);
+					Eval.Tick(*this, DeltaTime);
+				
+					EZ_ABILITY_TRACE_EVALUATOR_EVENT(EvalIndex, EvalInstanceView, EEzAbilityTraceEventType::OnTicked);
+				}
+			}
+
+			if (bTickGlobalTasks)
+			{
+				// Used to stop ticking tasks after one fails, but we still want to keep updating the data views so that property binding works properly.
+				bool bShouldTickTasks = true;
+				const bool bHasEvents = !EventsToProcess.IsEmpty();
+
+				for (int32 TaskIndex = CurrentEzAbility->GlobalTasksBegin; TaskIndex < (CurrentEzAbility->GlobalTasksBegin + CurrentEzAbility->GlobalTasksNum); TaskIndex++)
+				{
+					const FEzAbilityTask& Task = CurrentEzAbility->Nodes[TaskIndex].Get<const FEzAbilityTask>();
+					const FEzAbilityDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
+					FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
+
+					// Ignore disabled task
+					if (Task.bTaskEnabled == false)
+					{
+						EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'Tick' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
+						continue;
+					}
+
+					 const bool bNeedsTick = bShouldTickTasks && (Task.bShouldCallTick || (bHasEvents && Task.bShouldCallTickOnlyOnEvents));
+					EZ_ABILITY_LOG(VeryVerbose, TEXT("  Tick: '%s' %s"), *Task.Name.ToString(), !bNeedsTick ? TEXT("[not ticked]") : TEXT(""));
+					if (!bNeedsTick)
+					{
+						continue;
+					}
+					
+					// Copy bound properties.
+					// Only copy properties when the task is actually ticked, and copy properties at tick is requested.
+					if (Task.BindingsBatch.IsValid() && Task.bShouldCopyBoundPropertiesOnTick)
+					{
+						CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
+					}
+					
+					//EZ_ABILITY_TRACE_TASK_EVENT(TaskIndex, TaskDataView, EEzAbilityTraceEventType::OnTickingTask, EAbilityRunStatus::Running);
+					EAbilityRunStatus TaskResult = EAbilityRunStatus::Unset;
+					{
+						QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Task_Tick);
+						CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_Task_Tick);
+					
+						TaskResult = Task.Tick(*this, DeltaTime);
+					}
+
+					EZ_ABILITY_TRACE_TASK_EVENT(TaskIndex, TaskInstanceView,
+						TaskResult != EAbilityRunStatus::Running ? EEzAbilityTraceEventType::OnTaskCompleted : EEzAbilityTraceEventType::OnTicked,
+						TaskResult);
+					
+					// If a global task succeeds or fails, it will stop the whole tree.
+					if (TaskResult != EAbilityRunStatus::Running)
+					{
+						Result = TaskResult;
+					}
+						
+					if (TaskResult == EAbilityRunStatus::Failed)
+					{
+						bShouldTickTasks = false;
+					}
+				}
+			}
+		}
+	}
+
+	return Result;
+}
+
+EAbilityRunStatus FEzAbilityContext::StartEvaluatorsAndGlobalTasks(FEzAbilityIndex16& OutLastInitializedTaskIndex)
+{
+	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_StartEvaluators);
+	EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::StartGlobalTasks);
+
+	EZ_ABILITY_LOG(Verbose, TEXT("Start Evaluators & Global tasks"));
+
+	FEzAbilityExecutionState& State = GetExecState();
+
+	OutLastInitializedTaskIndex = FEzAbilityIndex16();
+	EAbilityRunStatus Result = EAbilityRunStatus::Running;
+
+	for (int32 FrameIndex = 0; FrameIndex < State.ActiveFrames.Num(); FrameIndex++)
+	{
+		FEzAbilityExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &State.ActiveFrames[FrameIndex - 1] : nullptr;
+		FEzAbilityExecutionFrame& CurrentFrame = State.ActiveFrames[FrameIndex];
+
+		if (CurrentFrame.bIsGlobalFrame)
+        {
+        	FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
+        			
+        	const UEzAbility* CurrentAbility = CurrentFrame.Ability;
+        
+        	// Start evaluators
+        	for (int32 EvalIndex = CurrentAbility->EvaluatorsBegin; EvalIndex < (CurrentAbility->EvaluatorsBegin + CurrentAbility->EvaluatorsNum); EvalIndex++)
+        	{
+        		const FEzAbilityEvaluator& Eval = CurrentAbility->Nodes[EvalIndex].Get<const FEzAbilityEvaluator>();
+        		const FEzAbilityDataView EvalInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Eval.InstanceDataHandle);
+        		FNodeInstanceDataScope DataScope(*this, Eval.InstanceDataHandle, EvalInstanceView);
+        
+        		// Copy bound properties.
+        		if (Eval.BindingsBatch.IsValid())
+        		{
+        			CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, EvalInstanceView, Eval.BindingsBatch);
+        		}
+        		
+        		EZ_ABILITY_LOG(Verbose, TEXT("  Start: '%s'"), *Eval.Name.ToString());
+        		{
+        			QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Eval_TreeStart);
+        			Eval.Start(*this);
+           
+        			EZ_ABILITY_TRACE_EVALUATOR_EVENT(EvalIndex, EvalInstanceView, EEzAbilityTraceEventType::OnAbilityStarted);
+        		}
+        	}
+        
+			// Start Global tasks
+			// Even if we call Enter/ExitState() on global tasks, they do not enter any specific state.
+			const FEzAbilityTransitionResult Transition = {}; // Empty transition
+        		
+			for (int32 TaskIndex = CurrentAbility->GlobalTasksBegin; TaskIndex < (CurrentAbility->GlobalTasksBegin + CurrentAbility->GlobalTasksNum); TaskIndex++)
+			{
+				const FEzAbilityTask& Task =  CurrentAbility->Nodes[TaskIndex].Get<const FEzAbilityTask>();
+				const FEzAbilityDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
+				FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
+        
+				// Copy bound properties.
+				if (Task.BindingsBatch.IsValid())
+				{
+					CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
+				}
+        
+				// Ignore disabled task
+				if (Task.bTaskEnabled == false)
+				{
+					EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'EnterState' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
+					continue;
+				}
+        
+				EZ_ABILITY_LOG(Verbose, TEXT("  Start: '%s'"), *Task.Name.ToString());
+				{
+					QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Task_TreeStart);
+					const EAbilityRunStatus TaskStatus = Task.EnterState(*this, Transition); 
+        
+					EZ_ABILITY_TRACE_TASK_EVENT(TaskIndex, TaskInstanceView, EEzAbilityTraceEventType::OnEntered, TaskStatus);
+        
+					if (TaskStatus != EAbilityRunStatus::Running)
+					{
+						OutLastInitializedTaskIndex = FEzAbilityIndex16(TaskIndex);
+						Result = TaskStatus;
+						break;
+        			}
+        		}
+        	}
+        }
+	}
+	
+	return Result;
+}
+
+void FEzAbilityContext::StopEvaluatorsAndGlobalTasks(const EAbilityRunStatus CompletionStatus, const FEzAbilityIndex16 LastInitializedTaskIndex)
+{
+	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_StopEvaluators);
+	EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::StopGlobalTasks);
+
+	EZ_ABILITY_LOG(Verbose, TEXT("Stop Evaluators & Global Tasks"));
+
+	FEzAbilityExecutionState& Exec = GetExecState();
+
+	// Update bindings
+	for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); FrameIndex++)
+	{
+		FEzAbilityExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
+		FEzAbilityExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
+		if (CurrentFrame.bIsGlobalFrame)
+		{
+			FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
+			
+			const UEzAbility* CurrentEzAbility = CurrentFrame.Ability;
+
+			for (int32 EvalIndex = CurrentEzAbility->EvaluatorsBegin; EvalIndex < (CurrentEzAbility->EvaluatorsBegin + CurrentEzAbility->EvaluatorsNum); EvalIndex++)
+			{
+				const FEzAbilityEvaluator& Eval = CurrentEzAbility->Nodes[EvalIndex].Get<const FEzAbilityEvaluator>();
+				const FEzAbilityDataView EvalInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Eval.InstanceDataHandle);
+				FNodeInstanceDataScope DataScope(*this, Eval.InstanceDataHandle, EvalInstanceView);
+
+				// Copy bound properties.
+				if (Eval.BindingsBatch.IsValid())
+				{
+					CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, EvalInstanceView, Eval.BindingsBatch);
+				}
+			}
+
+			for (int32 TaskIndex = CurrentEzAbility->GlobalTasksBegin; TaskIndex < (CurrentEzAbility->GlobalTasksBegin + CurrentEzAbility->GlobalTasksNum); TaskIndex++)
+			{
+				const FEzAbilityTask& Task = CurrentEzAbility->Nodes[TaskIndex].Get<const FEzAbilityTask>();
+				const FEzAbilityDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
+				FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
+
+				// Copy bound properties.
+				if (Task.BindingsBatch.IsValid() && Task.bShouldCopyBoundPropertiesOnExitState)
+				{
+					CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
+				}
+			}
+		}
+	}
+
+	// Call in reverse order.
+	FEzAbilityTransitionResult Transition;
+	Transition.TargetState = FEzAbilityStateHandle::FromCompletionStatus(CompletionStatus);
+	Transition.CurrentRunStatus = CompletionStatus;
+
+	for (int32 FrameIndex = Exec.ActiveFrames.Num() - 1; FrameIndex >= 0; FrameIndex--)
+	{
+		const FEzAbilityExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
+		const FEzAbilityExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
+		if (CurrentFrame.bIsGlobalFrame)
+		{
+			FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
+
+			const UEzAbility* CurrentEzAbility = CurrentFrame.Ability;
+
+			for (int32 TaskIndex = (CurrentEzAbility->GlobalTasksBegin + CurrentEzAbility->GlobalTasksNum) - 1;  TaskIndex >= CurrentEzAbility->GlobalTasksBegin; TaskIndex--)
+			{
+				const FEzAbilityTask& Task =  CurrentEzAbility->Nodes[TaskIndex].Get<const FEzAbilityTask>();
+				const FEzAbilityDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
+				FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
+
+				// Ignore disabled task
+				if (Task.bTaskEnabled == false)
+				{
+					EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'ExitState' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
+					continue;
+				}
+
+				// Relying here that invalid value of LastInitializedTaskIndex == MAX_uint16.
+				if (TaskIndex <= LastInitializedTaskIndex.Get())
+				{
+					EZ_ABILITY_LOG(Verbose, TEXT("  Stop: '%s'"), *Task.Name.ToString());
+					{
+						QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Task_TreeStop);
+						Task.ExitState(*this, Transition);
+					}
+					EZ_ABILITY_TRACE_TASK_EVENT(TaskIndex, TaskInstanceView, EEzAbilityTraceEventType::OnExited, Transition.CurrentRunStatus);
+				}
+			}
+
+			for (int32 EvalIndex = (CurrentEzAbility->EvaluatorsBegin + CurrentEzAbility->EvaluatorsNum) - 1; EvalIndex >= CurrentEzAbility->EvaluatorsBegin; EvalIndex--)
+			{
+				const FEzAbilityEvaluator& Eval = CurrentEzAbility->Nodes[EvalIndex].Get<const FEzAbilityEvaluator>();
+				const FEzAbilityDataView EvalInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Eval.InstanceDataHandle);
+				FNodeInstanceDataScope DataScope(*this, Eval.InstanceDataHandle, EvalInstanceView);
+
+				EZ_ABILITY_LOG(Verbose, TEXT("  Stop: '%s'"), *Eval.Name.ToString());
+				{
+					QUICK_SCOPE_CYCLE_COUNTER(EzAbility_Eval_TreeStop);
+					Eval.Stop(*this);
+
+					EZ_ABILITY_TRACE_EVALUATOR_EVENT(EvalIndex, EvalInstanceView, EEzAbilityTraceEventType::OnAbilityStopped);
+				}
+			}
+		}
+	}
+}
+
+bool FEzAbilityContext::IsHandleSourceValid(const FEzAbilityExecutionFrame* ParentFrame, const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataHandle Handle) const
+{
+	// Checks that the instance data is valid for specific handle types.
+	// 
+	// The CurrentFrame may not be yet properly initialized, for that reason we need to check
+	// that the path to the handle makes sense (it's part of the active states) as well as that
+	// we actually have instance data for the handle (index is valid).
+	// 
+	// The (base) indices can be invalid if the frame/state is not entered yet.
+	// For active instance data we need to check that the frame is initialized for a specific state,
+	// as well as that the instance data is initialized.
+
+	switch (Handle.GetSource())
+	{
+	case EEzAbilityDataSourceType::None:
+		return true;
+
+	case EEzAbilityDataSourceType::GlobalInstanceData:
+	case EEzAbilityDataSourceType::GlobalInstanceDataObject:
+		return CurrentFrame.GlobalInstanceIndexBase.IsValid()
+			&& InstanceDataStorage->IsValidIndex(CurrentFrame.GlobalInstanceIndexBase.Get() + Handle.GetIndex());
+
+	case EEzAbilityDataSourceType::ActiveInstanceData:
+	case EEzAbilityDataSourceType::ActiveInstanceDataObject:
+		return CurrentFrame.ActiveInstanceIndexBase.IsValid()
+			&& CurrentFrame.ActiveStates.Contains(Handle.GetState(), CurrentFrame.NumCurrentlyActiveStates)
+			&& InstanceDataStorage->IsValidIndex(CurrentFrame.ActiveInstanceIndexBase.Get() + Handle.GetIndex());
+		
+	case EEzAbilityDataSourceType::SharedInstanceData:
+	case EEzAbilityDataSourceType::SharedInstanceDataObject:
+		return true;
+
+	case EEzAbilityDataSourceType::ContextData:
+		return true;
+
+	case EEzAbilityDataSourceType::ExternalData:
+		return CurrentFrame.ExternalDataBaseIndex.IsValid()
+			&& ContextAndExternalDataViews.IsValidIndex(CurrentFrame.ExternalDataBaseIndex.Get() + Handle.GetIndex());
+
+	case EEzAbilityDataSourceType::GlobalParameterData:
+		return ParentFrame
+			? IsHandleSourceValid(nullptr, *ParentFrame, CurrentFrame.GlobalParameterDataHandle)
+			: CurrentFrame.GlobalParameterDataHandle.IsValid();
+
+	case EEzAbilityDataSourceType::SubtreeParameterData:
+		if (ParentFrame)
+		{
+			// Linked subtree, params defined in parent scope.
+			return IsHandleSourceValid(nullptr, *ParentFrame, CurrentFrame.StateParameterDataHandle);
+		}
+		// Standalone subtree, params define as state params.
+		return CurrentFrame.ActiveInstanceIndexBase.IsValid()
+			&& CurrentFrame.ActiveStates.Contains(Handle.GetState(), CurrentFrame.NumCurrentlyActiveStates)
+			&& InstanceDataStorage->IsValidIndex(CurrentFrame.ActiveInstanceIndexBase.Get() + Handle.GetIndex());
+
+	case EEzAbilityDataSourceType::StateParameterData:
+		return CurrentFrame.ActiveInstanceIndexBase.IsValid()
+			&& CurrentFrame.ActiveStates.Contains(Handle.GetState(), CurrentFrame.NumCurrentlyActiveStates)
+			&& InstanceDataStorage->IsValidIndex(CurrentFrame.ActiveInstanceIndexBase.Get() + Handle.GetIndex());
+
+	default:
+		checkf(false, TEXT("Unhandle case %s"), *UEnum::GetValueAsString(Handle.GetSource()));
+	}
+
+	return false;
+}
+
+FEzAbilityDataView FEzAbilityContext::GetDataViewOrTemporary(const FEzAbilityExecutionFrame* ParentFrame,
+                                                             const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataHandle Handle) const
+{
+	if (IsHandleSourceValid(ParentFrame, CurrentFrame, Handle))
+	{
+		return GetDataView(ParentFrame, CurrentFrame, Handle);
+	}
+
+	check(InstanceDataStorage);
+	
+	switch (Handle.GetSource())
+	{
+	case EEzAbilityDataSourceType::GlobalInstanceData:
+	case EEzAbilityDataSourceType::ActiveInstanceData:
+		return InstanceDataStorage->GetMutableTemporaryStruct(CurrentFrame, Handle);
+		
+	case EEzAbilityDataSourceType::GlobalInstanceDataObject:
+	case EEzAbilityDataSourceType::ActiveInstanceDataObject:
+		return InstanceDataStorage->GetMutableTemporaryObject(CurrentFrame, Handle);
+
+	case EEzAbilityDataSourceType::GlobalParameterData:
+		if (ParentFrame)
+		{
+			if (FCompactEzAbilityParameters* Params = InstanceDataStorage->GetMutableTemporaryStruct(*ParentFrame, CurrentFrame.GlobalParameterDataHandle).GetPtr<FCompactEzAbilityParameters>())
+			{
+				return Params->Parameters.GetMutableValue();
+			}
+		}
+		break;
+		
+	case EEzAbilityDataSourceType::SubtreeParameterData:
+		if (ParentFrame)
+		{
+			// Linked subtree, params defined in parent scope.
+			if (FCompactEzAbilityParameters* Params = InstanceDataStorage->GetMutableTemporaryStruct(*ParentFrame, CurrentFrame.StateParameterDataHandle).GetPtr<FCompactEzAbilityParameters>())
+			{
+				return Params->Parameters.GetMutableValue();
+			}
+		}
+		// Standalone subtree, params define as state params.
+		if (FCompactEzAbilityParameters* Params = InstanceDataStorage->GetMutableTemporaryStruct(CurrentFrame, Handle).GetPtr<FCompactEzAbilityParameters>())
+		{
+			return Params->Parameters.GetMutableValue();
+		}
+		break;
+
+	case EEzAbilityDataSourceType::StateParameterData:
+		{
+			if (FCompactEzAbilityParameters* Params = InstanceDataStorage->GetMutableTemporaryStruct(CurrentFrame, Handle).GetPtr<FCompactEzAbilityParameters>())
+			{
+				return Params->Parameters.GetMutableValue();
+			}
+		}
+		break;
+
+	default:
+		return {};
+	}
+
+	return {};
+}
+
+FEzAbilityDataView FEzAbilityContext::AddTemporaryInstance(const FEzAbilityExecutionFrame& Frame, const FEzAbilityIndex16 OwnerNodeIndex, const FEzAbilityDataHandle DataHandle, FConstStructView NewInstanceData)
+{
+	check(InstanceDataStorage);
+	const FStructView NewInstance = InstanceDataStorage->AddTemporaryInstance(*Owner, Frame, OwnerNodeIndex, DataHandle, NewInstanceData);
+	if (FEzAbilityInstanceObjectWrapper* Wrapper = NewInstance.GetPtr<FEzAbilityInstanceObjectWrapper>())
+	{
+		return FEzAbilityDataView(Wrapper->InstanceObject);
+	}
+	return NewInstance;
+}
+
+bool FEzAbilityContext::CopyBatchOnActiveInstances(const FEzAbilityExecutionFrame* ParentFrame, const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataView TargetView, const FEzAbilityIndex16 BindingsBatch) const
+{
+	const FEzAbilityPropertyCopyBatch& Batch = CurrentFrame.Ability->PropertyBindings.GetBatch(BindingsBatch);
+	check(TargetView.GetStruct() == Batch.TargetStruct.Struct);
+
+	bool bSucceed = true;
+	for (const FEzAbilityPropertyCopy& Copy : CurrentFrame.Ability->PropertyBindings.GetBatchCopies(Batch))
+	{
+		const FEzAbilityDataView SourceView = GetDataView(ParentFrame, CurrentFrame, Copy.SourceDataHandle);
+		bSucceed &= CurrentFrame.Ability->PropertyBindings.CopyProperty(Copy, SourceView, TargetView);
+	}
+	return bSucceed;
+}
+
+bool FEzAbilityContext::CopyBatchWithValidation(const FEzAbilityExecutionFrame* ParentFrame, const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataView TargetView, const FEzAbilityIndex16 BindingsBatch) const
+{
+	const FEzAbilityPropertyCopyBatch& Batch = CurrentFrame.Ability->PropertyBindings.GetBatch(BindingsBatch);
+	check(TargetView.GetStruct() == Batch.TargetStruct.Struct);
+
+	bool bSucceed = true;
+	for (const FEzAbilityPropertyCopy& Copy : CurrentFrame.Ability->PropertyBindings.GetBatchCopies(Batch))
+	{
+		const FEzAbilityDataView SourceView = GetDataViewOrTemporary(ParentFrame, CurrentFrame, Copy.SourceDataHandle);
+		if (!SourceView.IsValid())
+		{
+			bSucceed = false;
+			break;
+		}
+		
+		bSucceed &= CurrentFrame.Ability->PropertyBindings.CopyProperty(Copy, SourceView, TargetView);
+	}
+	return bSucceed;
+}
+
+bool FEzAbilityContext::CollectActiveExternalData()
+{
+	if (bActiveExternalDataCollected)
+	{
+		return true;
+	}
+
+	bool bAllExternalDataValid = true;
+	FEzAbilityExecutionState& Exec = GetExecState();
+	const FEzAbilityExecutionFrame* PrevFrame = nullptr;
+	
+	for (FEzAbilityExecutionFrame& Frame : Exec.ActiveFrames)
+	{
+		if (PrevFrame && PrevFrame->Ability == Frame.Ability)
+		{
+			Frame.ExternalDataBaseIndex = PrevFrame->ExternalDataBaseIndex;
+		}
+		else
+		{
+			Frame.ExternalDataBaseIndex = CollectExternalData(Frame.Ability);
+		}
+
+		if (!Frame.ExternalDataBaseIndex.IsValid())
+		{
+			bAllExternalDataValid = false;
+		}
+		
+		PrevFrame = &Frame;
+	}
+
+	if (bAllExternalDataValid)
+	{
+		bActiveExternalDataCollected = true;
+	}
+	
+	return bAllExternalDataValid;
+}
+
+FEzAbilityIndex16 FEzAbilityContext::CollectExternalData(const UEzAbility* InAbility)
+{
+	if (!InAbility)
+	{
+		return FEzAbilityIndex16::Invalid;
+	}
+
+	// If one of the active states share the same state tree, get the external data from there.
+	for (const FCollectedExternalDataCache& Cache : CollectedExternalCache)
+	{
+		if (Cache.Ability == InAbility)
+		{
+			return Cache.BaseIndex;
+		}
+	}
+	
+	const TConstArrayView<FEzAbilityExternalDataDesc> ExternalDataDescs = InAbility->GetExternalDataDescs();
+	const int32 BaseIndex = ContextAndExternalDataViews.Num();
+	const int32 NumDescs = ExternalDataDescs.Num();
+	FEzAbilityIndex16 Result(BaseIndex);
+
+	if (NumDescs > 0)
+	{
+		ContextAndExternalDataViews.AddDefaulted(NumDescs);
+		const TArrayView<FEzAbilityDataView> DataViews = MakeArrayView(ContextAndExternalDataViews.GetData() + BaseIndex, NumDescs);  
+
+		if (ensureMsgf(CollectExternalDataDelegate.IsBound(), TEXT("The EzAbility asset has external data, expecting CollectExternalData delegate to be provided.")))
+		{
+			if (!CollectExternalDataDelegate.Execute(*this, InAbility, InAbility->GetExternalDataDescs(), DataViews))
+			{
+				// The caller is responsible for error reporting. 
+				return FEzAbilityIndex16::Invalid;
+			}
+		}
+
+		// Check that the data is valid and present.
+		for (int32 Index = 0; Index < NumDescs; Index++)
+		{
+			const FEzAbilityExternalDataDesc& DataDesc = ExternalDataDescs[Index];
+			const FEzAbilityDataView& DataView = ContextAndExternalDataViews[BaseIndex + Index];
+
+			if (DataDesc.Requirement == EEzAbilityExternalDataRequirement::Required)
+			{
+				// Required items must have valid pointer of the expected type.  
+				if (!DataView.IsValid() || !DataDesc.IsCompatibleWith(DataView))
+				{
+					Result = FEzAbilityIndex16::Invalid;
+					break;
+				}
+			}
+			else
+			{
+				// Optional items must have same type if they are set.
+				if (DataView.IsValid() && !DataDesc.IsCompatibleWith(DataView))
+				{
+					Result = FEzAbilityIndex16::Invalid;
+					break;
+				}
+			}
+		}
+	}
+
+	if (!Result.IsValid())
+	{
+		// Rollback
+		ContextAndExternalDataViews.SetNum(BaseIndex);
+	}
+
+	// Cached both succeeded and failed attempts.
+	CollectedExternalCache.Add({ InAbility, Result });
+
+	return FEzAbilityIndex16(Result);
+}
+
+bool FEzAbilityContext::SelectState(const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityStateHandle NextState, TArray<FEzAbilityExecutionFrame, TFixedAllocator<MaxExecutionFrames>>& OutNextActiveFrames, const EEzAbilitySelectionFallback Fallback)
+{
+	const FEzAbilityExecutionState& Exec = GetExecState();
+
+	if (Exec.ActiveFrames.IsEmpty())
+	{
+		EZ_ABILITY_LOG(Error, TEXT("%hs: SelectState can only be called on initialized tree.  '%s' using EzAbility '%s'."),
+			__FUNCTION__, *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
+		return false;
+	}
+	
+	if (!NextState.IsValid())
+	{
+		return false;
+	}
+
+	// Walk towards the root from current state.
+	TStaticArray<FEzAbilityStateHandle, FEzAbilityActiveStates::MaxStates> ParentStates;
+	int32 NumParentStates = 0;
+	FEzAbilityStateHandle CurrState = NextState;
+	while (CurrState.IsValid())
+	{
+		if (NumParentStates == FEzAbilityActiveStates::MaxStates)
+		{
+			EZ_ABILITY_LOG(Error, TEXT("%hs: Reached max execution depth when trying to select state %s from '%s'.  '%s' using EzAbility '%s'."),
+				__FUNCTION__, *GetSafeStateName(CurrentFrame, NextState), *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
+			return false;
+		}
+		// Store the states that are in between the 'NextState' and common ancestor. 
+		ParentStates[NumParentStates++] = CurrState;
+		CurrState = CurrentFrame.Ability->States[CurrState.Index].Parent;
+	}
+
+	const UEzAbility* NextEzAbility = CurrentFrame.Ability;
+	const FEzAbilityStateHandle NextRootState = ParentStates[NumParentStates - 1]; 
+
+	// Find the frame that the next state belongs to.
+	int32 CurrentFrameIndex = INDEX_NONE;
+	int32 CurrentEzAbilityIndex = INDEX_NONE;
+
+	for (int32 FrameIndex = Exec.ActiveFrames.Num() - 1; FrameIndex >= 0; FrameIndex--)
+	{
+		const FEzAbilityExecutionFrame& Frame = Exec.ActiveFrames[FrameIndex]; 
+		if (Frame.Ability == NextEzAbility)
+		{
+			CurrentEzAbilityIndex = FrameIndex;
+			if (Frame.RootState == NextRootState)
+			{
+				CurrentFrameIndex = FrameIndex;
+				break;
+			}
+		}
+	}
+
+	// Copy common frames over.
+	// ReferenceCurrentFrame is the original of the last copied frame. It will be used to keep track if we are following the current active frames and states.
+	const FEzAbilityExecutionFrame* CurrentFrameInActiveFrames  = nullptr;
+	if (CurrentFrameIndex != INDEX_NONE)
+	{
+		const int32 NumCommonFrames = CurrentFrameIndex + 1;
+		OutNextActiveFrames = MakeArrayView(Exec.ActiveFrames.GetData(), NumCommonFrames);
+		CurrentFrameInActiveFrames  = &Exec.ActiveFrames[CurrentFrameIndex];
+	}
+	else if (CurrentEzAbilityIndex != INDEX_NONE)
+	{
+		// If we could not find a common frame, we assume that we jumped to different subtree in same asset.
+		const int32 NumCommonFrames = CurrentEzAbilityIndex + 1;
+		OutNextActiveFrames = MakeArrayView(Exec.ActiveFrames.GetData(), NumCommonFrames);
+		CurrentFrameInActiveFrames  = &Exec.ActiveFrames[CurrentEzAbilityIndex];
+	}
+	else
+	{
+		EZ_ABILITY_LOG(Error, TEXT("%hs: Encountered unrecognized state %s during state selection from '%s'.  '%s' using EzAbility '%s'."),
+			__FUNCTION__, *GetNameSafe(NextEzAbility), *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
+		return false;
+	}
+	
+	// Append in between state in reverse order, they were collected from leaf towards the root.
+	// Note: NextState will be added by SelectStateInternal() if conditions pass.
+	const int32 LastFrameIndex = OutNextActiveFrames.Num() - 1;
+	FEzAbilityExecutionFrame& LastFrame = OutNextActiveFrames[LastFrameIndex];
+
+	LastFrame.ActiveStates.Reset();
+	for (int32 Index = NumParentStates - 1; Index > 0; Index--)
+	{
+		LastFrame.ActiveStates.Push(ParentStates[Index]);
+	}
+	// Existing state's data is safe to access during select.
+	LastFrame.NumCurrentlyActiveStates = static_cast<uint8>(LastFrame.ActiveStates.Num());
+
+	TArray<FEzAbilityExecutionFrame, TFixedAllocator<MaxExecutionFrames>> InitialNextActiveFrames;
+	
+	if (Fallback == EEzAbilitySelectionFallback::NextSelectableSibling)
+	{
+		InitialNextActiveFrames = OutNextActiveFrames;
+	}
+	
+	// We take copy of the last frame and assign it later, as SelectStateInternal() might change the array and invalidate the pointer.
+	const FEzAbilityExecutionFrame* CurrentParentFrame = LastFrameIndex > 0 ? &OutNextActiveFrames[LastFrameIndex - 1] : nullptr; 
+	if (SelectStateInternal(CurrentParentFrame, OutNextActiveFrames[LastFrameIndex], CurrentFrameInActiveFrames , NextState, OutNextActiveFrames))
+	{
+		return true;
+	}
+
+	// Failed to Select Next State, handle fallback here
+	// Return true on the first next sibling that gets selected successfully
+	if (Fallback == EEzAbilitySelectionFallback::NextSelectableSibling && NumParentStates >= 2)
+	{
+		// InBetweenStates is in reversed order (i.e. from leaf to root)
+		const FEzAbilityStateHandle Parent = ParentStates[1];
+		if (Parent.IsValid())
+		{
+			const FCompactEzAbilityState& ParentState = CurrentFrame.Ability->States[Parent.Index];
+
+			uint16 ChildState = CurrentFrame.Ability->States[NextState.Index].GetNextIndex();
+			for (; ChildState < ParentState.ChildrenEnd; ChildState = CurrentFrame.Ability->States[ChildState].GetNextIndex())
+			{
+				FEzAbilityStateHandle ChildStateHandle = FEzAbilityStateHandle(ChildState);
+
+				// Start selection from blank slate.
+				OutNextActiveFrames = InitialNextActiveFrames;
+	
+				// We take copy of the last frame and assign it later, as SelectStateInternal() might change the array and invalidate the pointer.
+				CurrentParentFrame = LastFrameIndex > 0 ? &OutNextActiveFrames[LastFrameIndex - 1] : nullptr; 
+				if (SelectStateInternal(CurrentParentFrame, OutNextActiveFrames[LastFrameIndex], CurrentFrameInActiveFrames , ChildStateHandle, OutNextActiveFrames))
+				{
+					return true;
+				}
+			}
+		}
+	}
+	
+	return false;
+}
+
+bool FEzAbilityContext::SelectStateInternal(const FEzAbilityExecutionFrame* CurrentParentFrame, FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityExecutionFrame* CurrentFrameInActiveFrames, const FEzAbilityStateHandle NextStateHandle, TArray<FEzAbilityExecutionFrame, TFixedAllocator<MaxExecutionFrames>>& OutNextActiveFrames)
+{
+	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_SelectState);
+
+	const FEzAbilityExecutionState& Exec = GetExecState();
+
+	if (!NextStateHandle.IsValid())
+	{
+		// Trying to select non-existing state.
+		EZ_ABILITY_LOG(Error, TEXT("%hs: Trying to select invalid state from '%s'.  '%s' using EzAbility '%s'."),
+            __FUNCTION__, *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+		return false;
+	}
+
+	FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
+
+	const UEzAbility* CurrentEzAbility = CurrentFrame.Ability;
+	const FCompactEzAbilityState& NextState = CurrentEzAbility->States[NextStateHandle.Index];
+
+	if (NextState.bEnabled == false)
+	{
+		// Do not select disabled state
+		EZ_ABILITY_LOG(VeryVerbose, TEXT("%hs: Ignoring disabled state '%s'.  '%s' using EzAbility '%s'."),
+			__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+		return false;
+	}
+
+	EZ_ABILITY_TRACE_SCOPED_STATE_PHASE(NextStateHandle, EEzAbilityUpdatePhase::StateSelection);
+
+	// The state cannot be directly selected.
+	if (NextState.SelectionBehavior == EEzAbilityStateSelectionBehavior::None)
+	{
+		return false;
+	}
+
+	if (NextState.ParameterDataHandle.IsValid())
+	{
+		// Instantiate state parameters if not done yet.
+		FEzAbilityDataView NextStateParametersView = GetDataViewOrTemporary(CurrentParentFrame, CurrentFrame, NextState.ParameterDataHandle);
+		if (!NextStateParametersView.IsValid())
+		{
+			// Allocate temporary instance for parameters if the state has params.
+			const FConstStructView DefaultStateParamsInstanceData = CurrentFrame.Ability->DefaultInstanceData.GetStruct(NextState.ParameterTemplateIndex.Get());
+			const FCompactEzAbilityParameters& DefaultStateParams = DefaultStateParamsInstanceData.Get<const FCompactEzAbilityParameters>();
+			if (DefaultStateParams.Parameters.IsValid())
+			{
+				FEzAbilityDataView TempStateParametersView = AddTemporaryInstance(CurrentFrame, FEzAbilityIndex16::Invalid, NextState.ParameterDataHandle, DefaultStateParamsInstanceData);
+				check(TempStateParametersView.IsValid());
+				FCompactEzAbilityParameters& StateParams = TempStateParametersView.GetMutable<FCompactEzAbilityParameters>();
+				NextStateParametersView = FEzAbilityDataView(StateParams.Parameters.GetMutableValue());
+			}
+		}
+
+		// Copy parameters if needed
+		if (NextStateParametersView.IsValid()
+			&& NextState.ParameterDataHandle.IsValid()
+			&& NextState.ParameterBindingsBatch.IsValid())
+		{
+			// Note: the parameters are for the current (linked) state, stored in current frame.
+			CopyBatchWithValidation(CurrentParentFrame, CurrentFrame, NextStateParametersView, NextState.ParameterBindingsBatch);
+		}
+	}
+
+	// Check that the state can be entered
+	EZ_ABILITY_TRACE_PHASE_BEGIN(EEzAbilityUpdatePhase::EnterConditions);
+	const bool bEnterConditionsPassed = TestAllConditions(CurrentParentFrame, CurrentFrame, NextState.EnterConditionsBegin, NextState.EnterConditionsNum);
+	EZ_ABILITY_TRACE_PHASE_END(EEzAbilityUpdatePhase::EnterConditions);
+
+	if (bEnterConditionsPassed)
+	{
+		if (!CurrentFrame.ActiveStates.Push(NextStateHandle))
+		{
+			EZ_ABILITY_LOG(Error, TEXT("%hs: Reached max execution depth when trying to select state %s from '%s'.  '%s' using EzAbility '%s'."),
+				__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+			return false;
+		}
+
+		// Check if we're still tracking on the current active frame and state.
+		// If we are, update the NumCurrentlyActiveStates to indicate that this state's instance data can be accessed. 
+		const uint8 PrevNumCurrentlyActiveStates = CurrentFrame.NumCurrentlyActiveStates; 
+		if (CurrentFrame.ActiveInstanceIndexBase.IsValid()
+			&& CurrentFrameInActiveFrames)
+		{
+			const int32 CurrentStateIndex = CurrentFrame.ActiveStates.Num() - 1;
+			const FEzAbilityStateHandle MatchingActiveHandle = CurrentFrameInActiveFrames->ActiveStates.GetStateSafe(CurrentStateIndex);
+			if (MatchingActiveHandle == NextStateHandle)
+			{
+				CurrentFrame.NumCurrentlyActiveStates = static_cast<uint8>(CurrentFrame.ActiveStates.Num());
+			}
+		}
+		
+		if (NextState.Type == EEzAbilityStateType::Linked)
+		{
+			if (NextState.LinkedState.IsValid())
+			{
+				if (OutNextActiveFrames.Num() == MaxExecutionFrames)
+				{
+					EZ_ABILITY_LOG(Error, TEXT("%hs: Reached max execution depth when trying to select state %s from '%s'.  '%s' using EzAbility '%s'."),
+						__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+					return false;
+				}
+
+				FEzAbilityExecutionFrame NewFrame;
+				NewFrame.Ability = CurrentFrame.Ability;
+				NewFrame.RootState = NextState.LinkedState;
+				NewFrame.ExternalDataBaseIndex = CurrentFrame.ExternalDataBaseIndex;
+
+				// Check and prevent recursion.
+				const bool bNewFrameAlreadySelected = OutNextActiveFrames.ContainsByPredicate([&NewFrame](const FEzAbilityExecutionFrame& Frame) {
+					return Frame.IsSameFrame(NewFrame);
+				});
+				
+				if (bNewFrameAlreadySelected)
+				{
+					EZ_ABILITY_LOG(Error, TEXT("%hs: Trying to recursively enter subtree '%s' from '%s'.  '%s' using EzAbility '%s'."),
+						__FUNCTION__, *GetSafeStateName(NewFrame, NewFrame.RootState), *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+					return false;
+				}
+
+				// If the Frame already exists, copy instance indices so that conditions that rely on active states work correctly.
+				const FEzAbilityExecutionFrame* ExistingFrame = Exec.ActiveFrames.FindByPredicate(
+					[EzAbility = NewFrame.Ability, RootState = NewFrame.RootState](const FEzAbilityExecutionFrame& Frame)
+					{
+						return Frame.Ability == EzAbility && Frame.RootState == RootState;
+					});
+				if (ExistingFrame)
+				{
+					NewFrame.ActiveInstanceIndexBase = ExistingFrame->ActiveInstanceIndexBase;
+					NewFrame.GlobalInstanceIndexBase = ExistingFrame->GlobalInstanceIndexBase;
+					NewFrame.StateParameterDataHandle = ExistingFrame->StateParameterDataHandle;
+					NewFrame.GlobalParameterDataHandle = ExistingFrame->GlobalParameterDataHandle;
+				}
+				else
+				{
+					// Since the EzAbility is the same, we can access the global tasks of CurrentFrame, if they are initialized.   
+					NewFrame.GlobalParameterDataHandle = CurrentFrame.GlobalParameterDataHandle;
+					NewFrame.GlobalInstanceIndexBase = CurrentFrame.GlobalInstanceIndexBase;
+					NewFrame.StateParameterDataHandle = NextState.ParameterDataHandle; // Temporary allocated earlier if did not exists.
+				}
+
+				OutNextActiveFrames.Push(NewFrame);
+
+				// If State is linked, proceed to the linked state.
+				if (SelectStateInternal(&CurrentFrame, OutNextActiveFrames.Last(), ExistingFrame, NewFrame.RootState, OutNextActiveFrames))
+				{
+					return true;
+				}
+				
+				OutNextActiveFrames.Pop();
+			}
+			else
+			{
+				EZ_ABILITY_LOG(Warning, TEXT("%hs: Trying to enter invalid linked subtree from '%s'.  '%s' using EzAbility '%s'."),
+					__FUNCTION__, *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+			}
+		}
+		else if (NextState.Type == EEzAbilityStateType::LinkedAsset)
+		{
+			if (NextState.LinkedAsset)
+			{
+				if (OutNextActiveFrames.Num() == MaxExecutionFrames)
+				{
+					EZ_ABILITY_LOG(Error, TEXT("%hs: Reached max execution depth when trying to select state %s from '%s'.  '%s' using EzAbility '%s'."),
+						__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+					return false;
+				}
+
+				// The linked state tree should have compatible context requirements.
+				if (!NextState.LinkedAsset->HasCompatibleContextData(*GetAbility()))
+				{
+					EZ_ABILITY_LOG(Error, TEXT("%hs: The linked State Tree '%s' does not have compatible schema, trying to select state %s from '%s'.  '%s' using EzAbility '%s'."),
+						__FUNCTION__, *GetFullNameSafe(NextState.LinkedAsset), *GetSafeStateName(CurrentFrame, NextStateHandle), *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+					return false;
+				}
+				
+				FEzAbilityExecutionFrame NewFrame;
+				NewFrame.Ability = NextState.LinkedAsset;
+				NewFrame.RootState = FEzAbilityStateHandle::Root;
+				NewFrame.bIsGlobalFrame = true;
+
+				// Check and prevent recursion.
+				const bool bNewFrameAlreadySelected = OutNextActiveFrames.ContainsByPredicate([&NewFrame](const FEzAbilityExecutionFrame& Frame) {
+					return Frame.IsSameFrame(NewFrame);
+				});
+				
+				if (bNewFrameAlreadySelected)
+				{
+					EZ_ABILITY_LOG(Error, TEXT("%hs: Trying to recursively enter subtree '%s' from '%s'.  '%s' using EzAbility '%s'."),
+						__FUNCTION__, *GetSafeStateName(NewFrame, NewFrame.RootState), *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+					return false;
+				}
+
+				// If the Frame already exists, copy instance indices so that conditions that rely on active states work correctly.
+				const FEzAbilityExecutionFrame* ExistingFrame = Exec.ActiveFrames.FindByPredicate(
+					[EzAbility = NewFrame.Ability, RootState = NewFrame.RootState](const FEzAbilityExecutionFrame& Frame)
+					{
+						return Frame.Ability == EzAbility && Frame.RootState == RootState;
+					});
+				if (ExistingFrame)
+				{
+					NewFrame.ActiveInstanceIndexBase = ExistingFrame->ActiveInstanceIndexBase;
+					NewFrame.GlobalInstanceIndexBase = ExistingFrame->GlobalInstanceIndexBase;
+					NewFrame.StateParameterDataHandle = ExistingFrame->StateParameterDataHandle;
+					NewFrame.GlobalParameterDataHandle = ExistingFrame->GlobalParameterDataHandle;
+					NewFrame.ExternalDataBaseIndex = ExistingFrame->ExternalDataBaseIndex;
+				}
+				else
+				{
+					// Pass the linked state's parameters as global parameters to the linked asset.
+					NewFrame.GlobalParameterDataHandle = NextState.ParameterDataHandle;
+
+					// Collect external data if needed
+					NewFrame.ExternalDataBaseIndex = CollectExternalData(NewFrame.Ability);
+					if (!NewFrame.ExternalDataBaseIndex.IsValid())
+					{
+						EZ_ABILITY_LOG(VeryVerbose, TEXT("%hs: Cannot select state '%s' because failed to collect external data for nested tree '%s'.  '%s' using EzAbility '%s'."),
+							__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetFullNameSafe(NewFrame.Ability), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+						return false;
+					}
+					
+					// The state parameters will be from the root state.
+					const FCompactEzAbilityState& RootState = NewFrame.Ability->States[NewFrame.RootState.Index];
+					NewFrame.StateParameterDataHandle = RootState.ParameterDataHandle;
+
+					// Start global tasks and evaluators temporarily, so that their data is available already during select.
+					if (StartTemporaryEvaluatorsAndGlobalTasks(nullptr, NewFrame) != EAbilityRunStatus::Running)
+					{
+						EZ_ABILITY_LOG(VeryVerbose, TEXT("%hs: Cannot select state '%s' because cannot start nested tree's '%s' global tasks and evaluators.  '%s' using EzAbility '%s'."),
+							__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetFullNameSafe(NewFrame.Ability), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+						return false;
+					}
+				}
+				
+				OutNextActiveFrames.Push(NewFrame);
+
+				// If State is linked, proceed to the linked state.
+				if (SelectStateInternal(&CurrentFrame, OutNextActiveFrames.Last(), ExistingFrame, NewFrame.RootState, OutNextActiveFrames))
+				{
+					return true;
+				}
+				
+				OutNextActiveFrames.Pop();
+			}
+			else
+			{
+				EZ_ABILITY_LOG(Warning, TEXT("%hs: Trying to enter invalid linked asset from '%s'.  '%s' using EzAbility '%s'."),
+					__FUNCTION__, *GetStateStatusString(Exec), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+			}
+		}
+		else if (NextState.SelectionBehavior == EEzAbilityStateSelectionBehavior::TryEnterState)
+		{
+			// Select this state.
+			EZ_ABILITY_TRACE_STATE_EVENT(NextStateHandle, EEzAbilityTraceEventType::OnStateSelected);
+			return true;
+		}
+		else if (NextState.SelectionBehavior == EEzAbilityStateSelectionBehavior::TryFollowTransitions)
+		{
+			EZ_ABILITY_TRACE_SCOPED_STATE_PHASE(NextStateHandle, EEzAbilityUpdatePhase::TrySelectBehavior);
+
+			EEzAbilityTransitionPriority CurrentPriority = EEzAbilityTransitionPriority::None;
+
+			for (uint8 i = 0; i < NextState.TransitionsNum; i++)
+			{
+				const int16 TransitionIndex = NextState.TransitionsBegin + i;
+				const FCompactEzAbilityTransition& Transition = GetAbility()->Transitions[TransitionIndex];
+
+				// Skip disabled transitions
+				if (Transition.bTransitionEnabled == false)
+				{
+					continue;
+				}
+
+				// No need to test the transition if same or higher priority transition has already been processed.
+				if (Transition.Priority <= CurrentPriority)
+				{
+					continue;
+				}
+
+				// Skip completion transitions
+				if (EnumHasAnyFlags(Transition.Trigger, EEzAbilityTransitionTrigger::OnStateCompleted))
+				{
+					continue;
+				}
+
+				// Cannot follow transitions with delay.
+				if (Transition.HasDelay())
+				{
+					continue;
+				}
+
+				// Try to prevent (infinite) loops in the selection.
+				if (CurrentFrame.ActiveStates.Contains(Transition.State))
+				{
+					EZ_ABILITY_LOG(Error, TEXT("%hs: Loop detected when trying to select state %s from '%s'. Prior states: %s.  '%s' using EzAbility '%s'."),
+						__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetStateStatusString(Exec), *DebugGetStatePath(OutNextActiveFrames, &CurrentFrame), *GetNameSafe(Owner), *GetFullNameSafe(CurrentFrame.Ability));
+					continue;
+				}
+
+				const bool bShouldTrigger = Transition.Trigger == EEzAbilityTransitionTrigger::OnTick
+											|| (Transition.Trigger == EEzAbilityTransitionTrigger::OnEvent
+												&& HasEventToProcess(Transition.EventTag));
+
+				bool bTransitionConditionsPassed = false;
+				if (bShouldTrigger)
+				{
+					EZ_ABILITY_TRACE_TRANSITION_EVENT(FEzAbilityTransitionSource(FEzAbilityIndex16(TransitionIndex), Transition.State, Transition.Priority), EEzAbilityTraceEventType::OnEvaluating);
+					EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::TransitionConditions);
+					bTransitionConditionsPassed = bShouldTrigger && TestAllConditions(CurrentParentFrame, CurrentFrame, Transition.ConditionsBegin, Transition.ConditionsNum);
+				}
+
+				if (bTransitionConditionsPassed)
+				{
+					// Using SelectState() instead of SelectStateInternal to treat the transitions the same way as regular transitions,
+					// e.g. it may jump to a completely different branch.
+					TArray<FEzAbilityExecutionFrame, TFixedAllocator<MaxExecutionFrames>> NewActiveFrames;
+					if (SelectState(CurrentFrame, Transition.State, NewActiveFrames, Transition.Fallback))
+					{
+						// Selection succeeded.
+						// Cannot break yet because higher priority transitions may override the selection. 
+						OutNextActiveFrames = NewActiveFrames;
+						CurrentPriority = Transition.Priority;
+					}
+				}
+			}
+
+			if (CurrentPriority != EEzAbilityTransitionPriority::None)
+			{
+				return true;
+			}
+		}
+		else if (NextState.SelectionBehavior == EEzAbilityStateSelectionBehavior::TrySelectChildrenInOrder)
+		{
+			if (NextState.HasChildren())
+			{
+				EZ_ABILITY_TRACE_SCOPED_STATE_PHASE(NextStateHandle, EEzAbilityUpdatePhase::TrySelectBehavior);
+
+				// If the state has children, proceed to select children.
+				for (uint16 ChildState = NextState.ChildrenBegin; ChildState < NextState.ChildrenEnd; ChildState = CurrentEzAbility->States[ChildState].GetNextIndex())
+				{
+					if (SelectStateInternal(CurrentParentFrame, CurrentFrame, CurrentFrameInActiveFrames, FEzAbilityStateHandle(ChildState), OutNextActiveFrames))
+					{
+						// Selection succeeded
+						return true;
+					}
+				}
+			}
+			else
+			{
+				// Select this state (For backwards compatibility)
+				EZ_ABILITY_TRACE_STATE_EVENT(NextStateHandle, EEzAbilityTraceEventType::OnStateSelected);
+				return true;
+			}
+		}
+
+		// State could not be selected, restore.
+		CurrentFrame.NumCurrentlyActiveStates = PrevNumCurrentlyActiveStates;
+		CurrentFrame.ActiveStates.Pop();
+	}
+
+	// Nothing got selected.
+	return false;
+}
+
+bool FEzAbilityContext::TestAllConditions(const FEzAbilityExecutionFrame* CurrentParentFrame, const FEzAbilityExecutionFrame& CurrentFrame, const int32 ConditionsOffset, const int32 ConditionsNum)
+{
+	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_TestConditions);
+
+	if (ConditionsNum == 0)
+	{
+		return true;
+	}
+
+	TStaticArray<EEzAbilityConditionOperand, UE::EzAbility::MaxConditionIndent + 1> Operands(InPlace, EEzAbilityConditionOperand::Copy);
+	TStaticArray<bool, UE::EzAbility::MaxConditionIndent + 1> Values(InPlace, false);
+
+	int32 Level = 0;
+	
+	for (int32 Index = 0; Index < ConditionsNum; Index++)
+	{
+		const int32 ConditionIndex = ConditionsOffset + Index;
+		const FEzAbilityCondition& Cond = CurrentFrame.Ability->Nodes[ConditionIndex].Get<const FEzAbilityCondition>();
+		const FEzAbilityDataView ConditionInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Cond.InstanceDataHandle);
+		FNodeInstanceDataScope DataScope(*this, Cond.InstanceDataHandle, ConditionInstanceView);
+
+		bool bValue = false;
+		if (Cond.EvaluationMode == EEzAbilityConditionEvaluationMode::Evaluated)
+		{
+			// Copy bound properties.
+			if (Cond.BindingsBatch.IsValid())
+			{
+				// Use validated copy, since we test in situations where the sources are not always valid (e.g. enter conditions may try to access inactive parent state). 
+				if (!CopyBatchWithValidation(CurrentParentFrame, CurrentFrame, ConditionInstanceView, Cond.BindingsBatch))
+				{
+					// If the source data cannot be accessed, the whole expression evaluates to false.
+					EZ_ABILITY_TRACE_CONDITION_EVENT(ConditionIndex, ConditionInstanceView, EEzAbilityTraceEventType::InternalForcedFailure);
+					EZ_ABILITY_TRACE_LOG_EVENT(TEXT("Evaluation forced to false: source data cannot be accessed (e.g. enter conditions trying to access inactive parent state)"));
+					Values[0] = false;
+					break;
+				}
+			}
+			
+			bValue = Cond.TestCondition(*this);
+			EZ_ABILITY_TRACE_CONDITION_EVENT(ConditionIndex, ConditionInstanceView, bValue ? EEzAbilityTraceEventType::Passed : EEzAbilityTraceEventType::Failed);
+			
+			// Reset copied properties that might contain object references.
+			if (Cond.BindingsBatch.IsValid())
+			{
+				CurrentFrame.Ability->PropertyBindings.ResetObjects(Cond.BindingsBatch, ConditionInstanceView);
+			}
+		}
+		else
+		{
+			bValue = Cond.EvaluationMode == EEzAbilityConditionEvaluationMode::ForcedTrue;
+			EZ_ABILITY_TRACE_CONDITION_EVENT(ConditionIndex, FEzAbilityDataView{}, bValue ? EEzAbilityTraceEventType::ForcedSuccess : EEzAbilityTraceEventType::ForcedFailure);
+		}
+
+		const int32 DeltaIndent = Cond.DeltaIndent;
+		const int32 OpenParens = FMath::Max(0, DeltaIndent) + 1;	// +1 for the current value that is stored at the empty slot at the top of the value stack.
+		const int32 ClosedParens = FMath::Max(0, -DeltaIndent) + 1;
+
+		// Store the operand to apply when merging higher level down when returning to this level.
+		// @todo: remove this conditions in 5.1, needs resaving existing EzAbilitys.
+		const EEzAbilityConditionOperand Operand = Index == 0 ? EEzAbilityConditionOperand::Copy : Cond.Operand;
+		Operands[Level] = Operand;
+
+		// Store current value at the top of the stack.
+		Level += OpenParens;
+		Values[Level] = bValue;
+
+		// Evaluate and merge down values based on closed braces.
+		// The current value is placed in parens (see +1 above), which makes merging down and applying the new value consistent.
+		// The default operand is copy, so if the value is needed immediately, it is just copied down, or if we're on the same level,
+		// the operand storing above gives handles with the right logic.
+		for (int32 Paren = 0; Paren < ClosedParens; Paren++)
+		{
+			Level--;
+			switch (Operands[Level])
+			{
+			case EEzAbilityConditionOperand::Copy:
+				Values[Level] = Values[Level + 1];
+				break;
+			case EEzAbilityConditionOperand::And:
+				Values[Level] &= Values[Level + 1];
+				break;
+			case EEzAbilityConditionOperand::Or:
+				Values[Level] |= Values[Level + 1];
+				break;
+			}
+			Operands[Level] = EEzAbilityConditionOperand::Copy;
+		}
+	}
+	
+	return Values[0];
+}
+
+FString FEzAbilityContext::GetStateStatusString(const FEzAbilityExecutionState& ExecState) const
+{
+	if (ExecState.TreeRunStatus != EAbilityRunStatus::Running)
+	{
+		return TEXT("--:") + UEnum::GetDisplayValueAsText(ExecState.LastTickStatus).ToString();
+	}
+	return GetSafeStateName(ExecState.ActiveFrames.Last(), ExecState.ActiveFrames.Last().ActiveStates.Last()) + TEXT(":") + UEnum::GetDisplayValueAsText(ExecState.LastTickStatus).ToString();
+}
+
+FString FEzAbilityContext::GetSafeStateName(const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityStateHandle State) const
+{
+	if (State == FEzAbilityStateHandle::Invalid)
+	{
+		return TEXT("(State Invalid)");
+	}
+	else if (State == FEzAbilityStateHandle::Succeeded)
+	{
+		return TEXT("(State Succeeded)");
+	}
+	else if (State == FEzAbilityStateHandle::Failed)
+	{
+		return TEXT("(State Failed)");
+	}
+	else if (CurrentFrame.Ability && CurrentFrame.Ability->States.IsValidIndex(State.Index))
+	{
+		return *CurrentFrame.Ability->States[State.Index].Name.ToString();
+	}
+	return TEXT("(Unknown)");
+}
+
+FString FEzAbilityContext::DebugGetStatePath(TConstArrayView<FEzAbilityExecutionFrame> ActiveFrames, const FEzAbilityExecutionFrame* CurrentFrame, const int32 ActiveStateIndex) const
+{
+	FString StatePath;
+	const UEzAbility* LastEzAbility = GetAbility();
+		
+	for (const FEzAbilityExecutionFrame& Frame : ActiveFrames)
+	{
+		if (!ensure(Frame.Ability))
+		{
+			return StatePath;
+		}
+
+		// If requested up the active state, clamp count.
+		int32 Num = Frame.ActiveStates.Num();
+		if (CurrentFrame == &Frame && Frame.ActiveStates.IsValidIndex(ActiveStateIndex))
+		{
+			Num = ActiveStateIndex + 1;
+		}
+
+		if (Frame.Ability != LastEzAbility)
+		{
+			StatePath.Appendf(TEXT("[%s]"), *GetNameSafe(Frame.Ability));
+			LastEzAbility = Frame.Ability;
+		}
+		
+		for (int32 i = 0; i < Num; i++)
+		{
+			const FCompactEzAbilityState& State = Frame.Ability->States[Frame.ActiveStates[i].Index];
+			StatePath.Appendf(TEXT("%s%s"), i == 0 ? TEXT("") : TEXT("."), *State.Name.ToString());
+		}
+	}
+		
+	return StatePath;
+}
+
+FString FEzAbilityContext::DebugGetEventsAsString() const
+{
+	FString Result;
+	for (const FEzAbilityEvent& Event : EventsToProcess)
+	{
+		if (!Result.IsEmpty())
+		{
+			Result += TEXT(", ");
+		}
+		Result += Event.Tag.ToString();
+	}
 	return Result;
 }
 

+ 23 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbility_Replicate.cpp

@@ -79,6 +79,29 @@ FGuid UEzAbility::GetNodeIdFromIndex(const FEzAbilityIndex16 NodeIndex) const
 	return Entry != nullptr ? Entry->Id : FGuid();
 }
 
+bool UEzAbility::HasCompatibleContextData(const UEzAbility& Other) const
+{
+	if (ContextDataDescs.Num() != Other.ContextDataDescs.Num())
+	{
+		return false;
+	}
+
+	const int32 Num = ContextDataDescs.Num();
+	for (int32 Index = 0; Index < Num; Index++)
+	{
+		const FEzAbilityExternalDataDesc& Desc = ContextDataDescs[Index];
+		const FEzAbilityExternalDataDesc& OtherDesc = Other.ContextDataDescs[Index];
+		
+		if (!OtherDesc.Struct 
+			|| !OtherDesc.Struct->IsChildOf(Desc.Struct))
+		{
+			return false;
+		}
+	}
+	
+	return true;
+}
+
 bool UEzAbility::Link()
 {
 	// Initialize the instance data default value.

+ 4 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/Condition/EzAbilityCondition.h

@@ -7,6 +7,7 @@
 #include "UObject/Object.h"
 #include "EzAbilityCondition.generated.h"
 
+struct FEzAbilityContext;
 /**
  * 
  */
@@ -15,6 +16,9 @@ struct EZABILITY_API FEzAbilityCondition : public FEzAbilityNodeBase
 {
 	GENERATED_BODY()
 
+	/** @return True if the condition passes. */
+	virtual bool TestCondition(FEzAbilityContext& Context) const { return false; }
+	
 	UPROPERTY()
 	EEzAbilityConditionOperand Operand = EEzAbilityConditionOperand::And;
 

+ 4 - 1
Ability/Plugins/EzAbility/Source/EzAbility/Public/Evaluator/EzAbilityEvaluator.h

@@ -17,5 +17,8 @@ struct EZABILITY_API FEzAbilityEvaluator : public FEzAbilityNodeBase
 {
 	GENERATED_BODY()
 
-	virtual void Stop(FEzAbilityContext& Context) const {}
+	virtual void Start	(FEzAbilityContext& Context)						const {}
+	virtual void Stop	(FEzAbilityContext& Context)						const {}
+	virtual void Tick	(FEzAbilityContext& Context, const float DeltaTime) const {}
 };
+

+ 9 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbility.h

@@ -25,6 +25,12 @@ public:
 	virtual bool CanActivateAbility(FEzAbilityContext& Context, FText& OutText) const;
 	virtual bool ShouldReplicate(const FEzAbilityInstance& Instance) const;
 
+	/** @return List of external data required by the state tree */
+	TConstArrayView<FEzAbilityExternalDataDesc> GetExternalDataDescs() const { return ExternalDataDescs; }
+
+	/** @return List of context data enforced by the schema that must be provided through the execution context. */
+	TConstArrayView<FEzAbilityExternalDataDesc> GetContextDataDescs() const { return ContextDataDescs; }
+	
 	const FCompactEzAbilityState* GetStateFromHandle(const FEzAbilityStateHandle StateHandle) const;
 	const FCompactEzAbilityTransition* GetTransitionFromIndex(const FEzAbilityIndex16 TransitionIndex) const;
 	
@@ -38,6 +44,9 @@ public:
 	FEzAbilityIndex16 GetNodeIndexFromId(const FGuid Id) const;
 	FGuid GetNodeIdFromIndex(const FEzAbilityIndex16 NodeIndex) const;
 
+	/** @return true if the other EzAbility has compatible context data. */
+	bool HasCompatibleContextData(const UEzAbility& Other) const;
+	
 	/**
 	 * Resolves references between data in the EzAbility.
 	 * @return true if all references to internal and external data are resolved properly, false otherwise.

+ 173 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityContext.h

@@ -47,6 +47,10 @@ USTRUCT(BlueprintType)
 struct EZABILITY_API FEzAbilityContext
 {
 	GENERATED_BODY()
+
+	/** Max number of execution frames handled during state selection. */
+	static constexpr int32 MaxExecutionFrames = 8;
+	
 public:
 	FEzAbilityContext() = default;
 	FEzAbilityContext(UObject& InOwner, const UEzAbility& InAbility, FEzAbilityInstanceData& InInstanceData, const FOnCollectEzAbilityExternalData& CollectExternalDataCallback = {});
@@ -86,13 +90,145 @@ public:
 	
 	TArray<FName>		GetActiveStateNames() const;
 	const UEzAbility*	GetAbility() const { return Ability; }
+	
 protected:
+	
 	void Reset();
 	FString GetInstanceDescription() const;
 	void UpdateInstanceData(TConstArrayView<FEzAbilityExecutionFrame> CurrentActiveFrames, TArrayView<FEzAbilityExecutionFrame> NextActiveFrames);
+	
+	/** Starts temporary instances of global evaluators and tasks for a given frame. */
+	EAbilityRunStatus StartTemporaryEvaluatorsAndGlobalTasks(const FEzAbilityExecutionFrame* CurrentParentFrame, const FEzAbilityExecutionFrame& CurrentFrame);
+
+	/** Stops leftover global evaluators and tasks in the provided temporary instance data. */
 	void StopTemporaryEvaluatorsAndGlobalTasks(TArrayView<FEzAbilityTemporaryInstanceData> TempInstances);
 
+	/**
+	 * Handles logic for entering State. EnterState is called on new active Evaluators and Tasks that are part of the re-planned tree.
+	 * Re-planned tree is from the transition target up to the leaf state. States that are parent to the transition target state
+	 * and still active after the transition will remain intact.
+	 * @return Run status returned by the tasks.
+	 */
+	EAbilityRunStatus EnterState(FEzAbilityTransitionResult& Transition);
+
+	/**
+	 * Handles logic for exiting State. ExitState is called on current active Evaluators and Tasks that are part of the re-planned tree.
+	 * Re-planned tree is from the transition target up to the leaf state. States that are parent to the transition target state
+	 * and still active after the transition will remain intact.
+	 */
+	void ExitState(const FEzAbilityTransitionResult& Transition);
+
+	/**
+	 * Handles logic for signalling State completed. StateCompleted is called on current active Evaluators and Tasks in reverse order (from leaf to root).
+	 */
+	void StateCompleted();
+	
+	/**
+	 * Tick evaluators and global tasks by delta time.
+	 */
+	EAbilityRunStatus TickEvaluatorsAndGlobalTasks(const float DeltaTime, bool bTickGlobalTasks = true);
+	
+	/**
+	 * Starts evaluators and global tasks.
+	 * @return run status returned by the global tasks.
+	 */
 	EAbilityRunStatus StartEvaluatorsAndGlobalTasks(FEzAbilityIndex16& OutLastInitializedTaskIndex);
+
+	/**
+	 * Stops evaluators and global tasks.
+	 */
+	void StopEvaluatorsAndGlobalTasks(const EAbilityRunStatus CompletionStatus, const FEzAbilityIndex16 LastInitializedTaskIndex = FEzAbilityIndex16());
+	
+	/** @return true if handle source is valid cified handle relative to given frame. */
+	bool IsHandleSourceValid(const FEzAbilityExecutionFrame* ParentFrame, const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataHandle Handle) const;
+	
+	/** @return data view of the specified handle relative to the given frame, or tries to find a matching temporary instance. */
+	FEzAbilityDataView GetDataViewOrTemporary(const FEzAbilityExecutionFrame* ParentFrame, const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataHandle Handle) const;
+
+	/**
+	 * Adds a temporary instance that can be located using frame and data handle later.
+	 * @returns view to the newly added instance. If NewInstanceData is Object wrapper, the new object is returned.
+	 */
+	FEzAbilityDataView AddTemporaryInstance(const FEzAbilityExecutionFrame& Frame, const FEzAbilityIndex16 OwnerNodeIndex, const FEzAbilityDataHandle DataHandle, FConstStructView NewInstanceData);
+	
+	/** Copies a batch of properties to the data in TargetView. Should be used only on active instances, assumes valid handles and does not consider temporary instances. */
+	bool CopyBatchOnActiveInstances(const FEzAbilityExecutionFrame* ParentFrame, const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataView TargetView, const FEzAbilityIndex16 BindingsBatch) const;
+
+	/** Copies a batch of properties to the data in TargetView. This version validates the data handles and looks up temporary instances. */
+	bool CopyBatchWithValidation(const FEzAbilityExecutionFrame* ParentFrame, const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataView TargetView, const FEzAbilityIndex16 BindingsBatch) const;
+
+	/**
+	 * Collects external data for all EzAbilitys in active frames.
+	 * @returns true if all external data are set successfully. */
+	bool CollectActiveExternalData();
+
+	/**
+	 * Collects external data for specific State Tree asset. If the data is already collected, cached index is returned.
+	 * @returns index in ContextAndExternalDataViews for the first external data.
+	 */
+	FEzAbilityIndex16 CollectExternalData(const UEzAbility* Ability);
+	
+	/**
+	 * Runs state selection logic starting at the specified state, walking towards the leaf states.
+	 * If a state cannot be selected, false is returned. 
+	 * If NextState is a selector state, SelectStateInternal is called recursively (depth-first) to all child states (where NextState will be one of child states).
+	 * If NextState is a leaf state, the active states leading from root to the leaf are returned.
+	 * @param CurrentFrame The frame where the NextState is valid. 
+	 * @param NextState The state which we try to select next.
+	 * @param OutNextActiveFrames Active frames and states that got selected.
+	 * @param Fallback selection behavior to execute if it fails to select the desired state
+	 * @return True if succeeded to select new active states.
+	 */
+	bool SelectState(
+		const FEzAbilityExecutionFrame& CurrentFrame,
+		const FEzAbilityStateHandle NextState,
+		TArray<FEzAbilityExecutionFrame, TFixedAllocator<MaxExecutionFrames>>& OutNextActiveFrames,
+		const EEzAbilitySelectionFallback Fallback = EEzAbilitySelectionFallback::None);
+
+	/**
+	 * Used internally to do the recursive part of the SelectState().
+	 */
+	bool SelectStateInternal(
+		const FEzAbilityExecutionFrame* CurrentParentFrame,
+		FEzAbilityExecutionFrame& CurrentFrame,
+		const FEzAbilityExecutionFrame* CurrentFrameInActiveFrames,
+		const FEzAbilityStateHandle NextStateHandle,
+		TArray<FEzAbilityExecutionFrame, TFixedAllocator<MaxExecutionFrames>>& OutNextActiveFrames);
+
+	/**
+	 * Checks all conditions at given range
+	 * @return True if all conditions pass.
+	 */
+	bool TestAllConditions(const FEzAbilityExecutionFrame* CurrentParentFrame, const FEzAbilityExecutionFrame& CurrentFrame, const int32 ConditionsOffset, const int32 ConditionsNum);
+
+	/** @return events to process this tick. */
+	TConstArrayView<FEzAbilityEvent> GetEventsToProcess() const { return EventsToProcess; }
+
+	/** @return true if there is a pending event with specified tag. */
+	bool HasEventToProcess(const FGameplayTag Tag) const
+	{
+		if (EventsToProcess.IsEmpty())
+		{
+			return false;
+		}
+		
+		return EventsToProcess.ContainsByPredicate([Tag](const FEzAbilityEvent& Event)
+		{
+			return Event.Tag.MatchesTag(Tag);
+		});
+	}
+	
+	/** @return String describing state status for logging and debug. */
+	FString GetStateStatusString(const FEzAbilityExecutionState& ExecState) const;
+
+	/** @return String describing state name for logging and debug. */
+	FString GetSafeStateName(const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityStateHandle State) const;
+
+	/** @return String describing full path of an activate state for logging and debug. */
+	FString DebugGetStatePath(TConstArrayView<FEzAbilityExecutionFrame> ActiveFrames, const FEzAbilityExecutionFrame* CurrentFrame = nullptr, const int32 ActiveStateIndex = INDEX_NONE) const;
+
+	/** @return String describing all events that are currently being processed  for logging and debug. */
+	FString DebugGetEventsAsString() const;
 protected:
 	UPROPERTY(Transient)
 	FEzAbilityInstanceData			InstanceData;
@@ -124,7 +260,44 @@ protected:
 
 	/** Data view of the context data. */
 	TArray<FEzAbilityDataView, TConcurrentLinearArrayAllocator<FDefaultBlockAllocationTag>> ContextAndExternalDataViews;
+
+	/** Events to process in current tick. */
+	TArray<FEzAbilityEvent, TConcurrentLinearArrayAllocator<FDefaultBlockAllocationTag>> EventsToProcess;
+
+	struct FCollectedExternalDataCache
+	{
+		const UEzAbility* Ability = nullptr;
+		FEzAbilityIndex16 BaseIndex;
+	};
+	TArray<FCollectedExternalDataCache, TConcurrentLinearArrayAllocator<FDefaultBlockAllocationTag>> CollectedExternalCache;
+
+	bool bActiveExternalDataCollected = false;
+	
 public:
+
+	/** Current state we're processing, or invalid if not applicable. */
+	FEzAbilityStateHandle CurrentlyProcessedState;
+	
+	/** Helper struct to track currently processed state. */
+	struct FCurrentlyProcessedStateScope
+	{
+		FCurrentlyProcessedStateScope(FEzAbilityContext& InContext, const FEzAbilityStateHandle State)
+			: Context(InContext)
+		{
+			SavedState = Context.CurrentlyProcessedState;
+			Context.CurrentlyProcessedState = State;
+		}
+
+		~FCurrentlyProcessedStateScope()
+		{
+			Context.CurrentlyProcessedState = SavedState;
+		}
+
+	private:
+		FEzAbilityContext& Context;
+		FEzAbilityStateHandle SavedState = FEzAbilityStateHandle::Invalid; 
+	};
+	
 	/** Helper struct to track currently processed frame. */
 	struct FCurrentlyProcessedFrameScope
 	{

+ 47 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/Task/EzAbilityTask.h

@@ -17,12 +17,59 @@ struct EZABILITY_API FEzAbilityTask : public FEzAbilityNodeBase
 {
 	GENERATED_BODY()
 
+	/**
+	 * Called when a new state is entered and task is part of active states.
+	 * @param Context Reference to current execution context.
+	 * @param Transition Describes the states involved in the transition
+	 * @return Succeed/Failed will end the state immediately and trigger to select new state, Running will carry on to tick the state.
+	 */
+	virtual EAbilityRunStatus EnterState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const { return EAbilityRunStatus::Running; }
+
+	/**
+	* Called when a current state is exited and task is part of active states.
+	* @param Context Reference to current execution context.
+	* @param Transition Describes the states involved in the transition
+	*/
 	virtual void ExitState(FEzAbilityContext& Context, const FEzAbilityTransitionResult& Transition) const {}
 
+	/**
+	 * Called during state tree tick when the task is on active state.
+	 * Note: The method is called only if bShouldCallTick or bShouldCallTickOnlyOnEvents is set.
+	 * @param Context Reference to current execution context.
+	 * @param DeltaTime Time since last EzAbility tick.
+	 * @return Running status of the state: Running if still in progress, Succeeded if execution is done and succeeded, Failed if execution is done and failed.
+	 */
+	virtual EAbilityRunStatus Tick(FEzAbilityContext& Context, const float DeltaTime) const { return EAbilityRunStatus::Running; };
 
+	/**
+	 * Called right after a state has been completed, but before new state has been selected. StateCompleted is called in reverse order to allow to propagate state to other Tasks that
+	 * are executed earlier in the tree. Note that StateCompleted is not called if conditional transition changes the state.
+	 * @param Context Reference to current execution context.
+	 * @param CompletionStatus Describes the running status of the completed state (Succeeded/Failed).
+	 * @param CompletedActiveStates Active states at the time of completion.
+	 */
+	virtual void StateCompleted(FEzAbilityContext& Context, const EAbilityRunStatus CompletionStatus, const FEzAbilityActiveStates& CompletedActiveStates) const {}
+	
+	/**
+	 * If set to true, the task will receive EnterState/ExitState even if the state was previously active.
+	 * Generally this should be true for action type tasks, like playing animation,
+	 * and false on state like tasks like claiming a resource that is expected to be acquired on child states.
+	 * Default value is true. */
+	uint8 bShouldStateChangeOnReselect : 1;
+	
+	/** If set to true, copy the values of bound properties before calling Tick(). Default true. */
+	uint8 bShouldCopyBoundPropertiesOnTick : 1;
+	/** If set to true, copy the values of bound properties before calling ExitState(). Default true. */
+	uint8 bShouldCopyBoundPropertiesOnExitState : 1;
+	
 	/** If set to true, TriggerTransitions() is called during transition handling. Default false. */
 	uint8 bShouldAffectTransitions : 1;
 
+	/** If set to true, Tick() is called. Not ticking implies no property copy. Default true. */
+	uint8 bShouldCallTick : 1;
+	/** If set to true, Tick() is called only when there are events. No effect if bShouldCallTickState is true. Not ticking implies no property copy. Default false. */
+	uint8 bShouldCallTickOnlyOnEvents : 1;
+	
 	/** True if the node is Enabled (i.e. not explicitly disabled in the asset). */
 	UPROPERTY()
 	uint8 bTaskEnabled : 1;