|
@@ -9,6 +9,7 @@
|
|
#include "Evaluator/EzAbilityEvaluator.h"
|
|
#include "Evaluator/EzAbilityEvaluator.h"
|
|
#include "Task/EzAbilityTask.h"
|
|
#include "Task/EzAbilityTask.h"
|
|
#include "EzAbilityLog.h"
|
|
#include "EzAbilityLog.h"
|
|
|
|
+#include "Condition/EzAbilityCondition.h"
|
|
#include "Debugger/EzAbilityTrace.h"
|
|
#include "Debugger/EzAbilityTrace.h"
|
|
#include "Debugger/EzAbilityTraceTypes.h"
|
|
#include "Debugger/EzAbilityTraceTypes.h"
|
|
|
|
|
|
@@ -207,6 +208,13 @@ EAbilityRunStatus FEzAbilityContext::Start(UEzAbility* InAbility, const FEzAbili
|
|
|
|
|
|
UpdateInstanceData({}, State.ActiveFrames);
|
|
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
|
|
// Must sent instance creation event first
|
|
EZ_ABILITY_TRACE_INSTANCE_EVENT(EEzAbilityTraceEventType::Push);
|
|
EZ_ABILITY_TRACE_INSTANCE_EVENT(EEzAbilityTraceEventType::Push);
|
|
|
|
|
|
@@ -223,11 +231,57 @@ EAbilityRunStatus FEzAbilityContext::Start(UEzAbility* InAbility, const FEzAbili
|
|
|
|
|
|
if (GlobalTasksRunStatus == EAbilityRunStatus::Running)
|
|
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
|
|
else
|
|
{
|
|
{
|
|
- //StopEvaluatorsAndGlobalTasks(GlobalTasksRunStatus, LastInitializedTaskIndex);
|
|
|
|
|
|
+ StopEvaluatorsAndGlobalTasks(GlobalTasksRunStatus, LastInitializedTaskIndex);
|
|
|
|
|
|
EZ_ABILITY_LOG(VeryVerbose, TEXT("%hs: Global tasks completed the EzAbility %s on start in status '%s'."),
|
|
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());
|
|
__FUNCTION__, *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()), *UEnum::GetDisplayValueAsText(GlobalTasksRunStatus).ToString());
|
|
@@ -611,6 +665,106 @@ void FEzAbilityContext::UpdateInstanceData(TConstArrayView<FEzAbilityExecutionFr
|
|
InstanceData.ResetTemporaryInstances();
|
|
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)
|
|
void FEzAbilityContext::StopTemporaryEvaluatorsAndGlobalTasks(TArrayView<FEzAbilityTemporaryInstanceData> TempInstances)
|
|
{
|
|
{
|
|
EZ_ABILITY_CLOG(!TempInstances.IsEmpty(), Verbose, TEXT("Stop Temporary Evaluators & Global tasks left over from previous state selection"));
|
|
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;
|
|
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);
|
|
const FEzAbilityDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
|
|
|
|
+
|
|
FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
|
|
FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
|
|
-
|
|
|
|
|
|
+
|
|
// Copy bound properties.
|
|
// Copy bound properties.
|
|
if (Task.BindingsBatch.IsValid())
|
|
if (Task.BindingsBatch.IsValid())
|
|
{
|
|
{
|
|
- //CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
|
|
|
|
|
|
+ CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
// Ignore disabled task
|
|
// Ignore disabled task
|
|
if (Task.bTaskEnabled == false)
|
|
if (Task.bTaskEnabled == false)
|
|
{
|
|
{
|
|
EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'EnterState' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
|
|
EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'EnterState' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
|
|
continue;
|
|
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;
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
|