|
@@ -327,7 +327,153 @@ EAbilityRunStatus FEzAbilityContext::GetAbilityRunStatus() const
|
|
|
|
|
|
EAbilityRunStatus FEzAbilityContext::Tick(float DeltaTime)
|
|
|
{
|
|
|
- return EAbilityRunStatus::Failed;
|
|
|
+ CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_Tick);
|
|
|
+ EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::Tick);
|
|
|
+
|
|
|
+ if (!IsValid())
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG(Warning, TEXT("%hs: EzAbility context is not initialized properly ('%s' using EzAbility '%s')"),
|
|
|
+ __FUNCTION__, *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
|
|
|
+ return EAbilityRunStatus::Failed;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!CollectActiveExternalData())
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG(Warning, TEXT("%hs: Failed to collect external data ('%s' using EzAbility '%s')"),
|
|
|
+ __FUNCTION__, *GetNameSafe(Owner), *GetFullNameSafe(GetAbility()));
|
|
|
+ return EAbilityRunStatus::Failed;
|
|
|
+ }
|
|
|
+
|
|
|
+ FEzAbilityEventQueue& EventQueue = InstanceData.GetMutableEventQueue();
|
|
|
+ FEzAbilityExecutionState& Exec = GetExecState();
|
|
|
+
|
|
|
+
|
|
|
+ if (Exec.TreeRunStatus != EAbilityRunStatus::Running)
|
|
|
+ {
|
|
|
+ return Exec.TreeRunStatus;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!ensureMsgf(Exec.CurrentPhase == EEzAbilityUpdatePhase::Unset, TEXT("%hs can't be called while already in %s ('%s' using EzAbility '%s')."),
|
|
|
+ __FUNCTION__, *UEnum::GetDisplayValueAsText(Exec.CurrentPhase).ToString(), *GetNameSafe(Owner), *GetFullNameSafe(GetAbility())))
|
|
|
+ {
|
|
|
+ return EAbilityRunStatus::Failed;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ Exec.CurrentPhase = EEzAbilityUpdatePhase::Tick;
|
|
|
+
|
|
|
+
|
|
|
+ EventsToProcess = EventQueue.GetEvents();
|
|
|
+ EventQueue.Reset();
|
|
|
+
|
|
|
+
|
|
|
+ for (FEzAbilityTransitionDelayedState& DelayedState : Exec.DelayedTransitions)
|
|
|
+ {
|
|
|
+ DelayedState.TimeLeft -= DeltaTime;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ const EAbilityRunStatus EvalAndGlobalTaskStatus = TickEvaluatorsAndGlobalTasks(DeltaTime);
|
|
|
+ if (EvalAndGlobalTaskStatus == EAbilityRunStatus::Running)
|
|
|
+ {
|
|
|
+ if (Exec.LastTickStatus == EAbilityRunStatus::Running)
|
|
|
+ {
|
|
|
+
|
|
|
+ Exec.LastTickStatus = TickTasks(DeltaTime);
|
|
|
+
|
|
|
+
|
|
|
+ if (Exec.LastTickStatus != EAbilityRunStatus::Running)
|
|
|
+ {
|
|
|
+ StateCompleted();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ static constexpr int32 MaxIterations = 5;
|
|
|
+ for (int32 Iter = 0; Iter < MaxIterations; Iter++)
|
|
|
+ {
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ EventsToProcess.Append(EventQueue.GetEvents());
|
|
|
+
|
|
|
+
|
|
|
+ if (TriggerTransitions())
|
|
|
+ {
|
|
|
+ EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::ApplyTransitions);
|
|
|
+ EZ_ABILITY_TRACE_TRANSITION_EVENT(NextTransitionSource, EEzAbilityTraceEventType::OnTransition);
|
|
|
+ NextTransitionSource.Reset();
|
|
|
+
|
|
|
+
|
|
|
+ EventQueue.Reset();
|
|
|
+
|
|
|
+ ExitState(NextTransition);
|
|
|
+
|
|
|
+
|
|
|
+ if (NextTransition.TargetState.IsCompletionState())
|
|
|
+ {
|
|
|
+
|
|
|
+ Exec.TreeRunStatus = NextTransition.TargetState.ToCompletionStatus();
|
|
|
+
|
|
|
+
|
|
|
+ StopEvaluatorsAndGlobalTasks(Exec.TreeRunStatus);
|
|
|
+
|
|
|
+
|
|
|
+ Exec.ActiveFrames.Reset();
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ EventsToProcess.Append(EventQueue.GetEvents());
|
|
|
+ EventQueue.Reset();
|
|
|
+
|
|
|
+
|
|
|
+ const EAbilityRunStatus LastTickStatus = EnterState(NextTransition);
|
|
|
+
|
|
|
+ NextTransition.Reset();
|
|
|
+
|
|
|
+ Exec.LastTickStatus = LastTickStatus;
|
|
|
+
|
|
|
+
|
|
|
+ EventsToProcess.Reset();
|
|
|
+
|
|
|
+
|
|
|
+ if (Exec.LastTickStatus != EAbilityRunStatus::Running)
|
|
|
+ {
|
|
|
+ StateCompleted();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (Exec.LastTickStatus == EAbilityRunStatus::Running)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ EZ_ABILITY_TRACE_LOG_EVENT(TEXT("Global tasks completed (%s), stopping the tree"), *UEnum::GetDisplayValueAsText(EvalAndGlobalTaskStatus).ToString());
|
|
|
+ Exec.RequestedStop = EvalAndGlobalTaskStatus;
|
|
|
+ }
|
|
|
+
|
|
|
+ EventsToProcess.Reset();
|
|
|
+
|
|
|
+
|
|
|
+ Exec.CurrentPhase = EEzAbilityUpdatePhase::Unset;
|
|
|
+
|
|
|
+
|
|
|
+ EAbilityRunStatus Result = Exec.TreeRunStatus;
|
|
|
+
|
|
|
+ if (Exec.RequestedStop != EAbilityRunStatus::Unset)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG_AND_TRACE(VeryVerbose, TEXT("Processing Deferred Stop"));
|
|
|
+ Result = Stop(Exec.RequestedStop);
|
|
|
+ }
|
|
|
+
|
|
|
+ return Result;
|
|
|
}
|
|
|
|
|
|
FEzAbilityDataView FEzAbilityContext::GetDataView(FEzAbilityInstanceStorage& InstanceDataStorage,
|
|
@@ -1823,6 +1969,376 @@ FEzAbilityIndex16 FEzAbilityContext::CollectExternalData(const UEzAbility* InAbi
|
|
|
return FEzAbilityIndex16(Result);
|
|
|
}
|
|
|
|
|
|
+bool FEzAbilityContext::TriggerTransitions()
|
|
|
+{
|
|
|
+ CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_TriggerTransition);
|
|
|
+ EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::TriggerTransitions);
|
|
|
+
|
|
|
+ FAllowDirectTransitionsScope AllowDirectTransitionsScope(*this);
|
|
|
+ FEzAbilityExecutionState& Exec = GetExecState();
|
|
|
+
|
|
|
+ if (EventsToProcess.Num() > 0)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG_AND_TRACE(Verbose, TEXT("Trigger transitions with events [%s]"), *DebugGetEventsAsString());
|
|
|
+ }
|
|
|
+
|
|
|
+ NextTransition.Reset();
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ for (const FEzAbilityTransitionRequest& Request : InstanceData.GetTransitionRequests())
|
|
|
+ {
|
|
|
+
|
|
|
+ const int32 FrameIndex = Exec.ActiveFrames.IndexOfByPredicate([&Request](const FEzAbilityExecutionFrame& Frame)
|
|
|
+ {
|
|
|
+ return Frame.Ability == Request.SourceAbility && Frame.RootState == Request.SourceRootState;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (FrameIndex != INDEX_NONE)
|
|
|
+ {
|
|
|
+ const FEzAbilityExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
|
|
|
+ if (RequestTransition(CurrentFrame, Request.TargetState, Request.Priority))
|
|
|
+ {
|
|
|
+ NextTransitionSource = FEzAbilityTransitionSource(EEzAbilityTransitionSourceType::ExternalRequest, Request.TargetState, Request.Priority);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ InstanceData.ResetTransitionRequests();
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if (Exec.ActiveFrames.Num() > 0)
|
|
|
+ {
|
|
|
+ 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);
|
|
|
+
|
|
|
+ for (int32 StateIndex = CurrentFrame.ActiveStates.Num() - 1; StateIndex >= 0; StateIndex--)
|
|
|
+ {
|
|
|
+ const FEzAbilityStateHandle StateHandle = CurrentFrame.ActiveStates[StateIndex];
|
|
|
+ const FCompactEzAbilityState& State = CurrentEzAbility->States[StateHandle.Index];
|
|
|
+
|
|
|
+
|
|
|
+ if (!State.bEnabled)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ FCurrentlyProcessedStateScope StateScope(*this, StateHandle);
|
|
|
+ EZ_ABILITY_TRACE_SCOPED_STATE(StateHandle);
|
|
|
+
|
|
|
+ if (State.bHasTransitionTasks)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_CLOG(State.TasksNum > 0, VeryVerbose, TEXT("%*sTrigger task transitions in state '%s'"), StateIndex*UE::EzAbility::DebugIndentSize, TEXT(""), *DebugGetStatePath(Exec.ActiveFrames, &CurrentFrame, StateIndex));
|
|
|
+
|
|
|
+ for (int32 TaskIndex = (State.TasksBegin + State.TasksNum) - 1; TaskIndex >= State.TasksBegin; TaskIndex--)
|
|
|
+ {
|
|
|
+ const FEzAbilityTask& Task = CurrentEzAbility->Nodes[TaskIndex].Get<const FEzAbilityTask>();
|
|
|
+ const FEzAbilityDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
|
|
|
+ FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
|
|
|
+
|
|
|
+
|
|
|
+ if (Task.bTaskEnabled == false)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'TriggerTransitions' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Task.bShouldAffectTransitions)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sTriggerTransitions: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
|
|
|
+ EZ_ABILITY_TRACE_TASK_EVENT(TaskIndex, TaskInstanceView, EEzAbilityTraceEventType::OnEvaluating, EAbilityRunStatus::Running);
|
|
|
+ check(TaskInstanceView.IsValid());
|
|
|
+ Task.TriggerTransitions(*this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (uint8 i = 0; i < State.TransitionsNum; i++)
|
|
|
+ {
|
|
|
+
|
|
|
+ const int16 TransitionIndex = State.TransitionsBegin + i;
|
|
|
+ const FCompactEzAbilityTransition& Transition = CurrentEzAbility->Transitions[TransitionIndex];
|
|
|
+
|
|
|
+
|
|
|
+ if (Transition.bTransitionEnabled == false)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (Transition.Priority <= NextTransition.Priority)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (EnumHasAnyFlags(Transition.Trigger, EEzAbilityTransitionTrigger::OnStateCompleted))
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ FEzAbilityTransitionDelayedState* DelayedState = nullptr;
|
|
|
+ if (Transition.HasDelay())
|
|
|
+ {
|
|
|
+ DelayedState = Exec.FindDelayedTransition(CurrentFrame.Ability, FEzAbilityIndex16(TransitionIndex));
|
|
|
+ if (DelayedState != nullptr && DelayedState->TimeLeft <= 0.0f)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG(Verbose, TEXT("Passed delayed transition from '%s' (%s) -> '%s'"),
|
|
|
+ *GetSafeStateName(CurrentFrame, CurrentFrame.ActiveStates.Last()), *State.Name.ToString(), *GetSafeStateName(CurrentFrame, Transition.State));
|
|
|
+
|
|
|
+ Exec.DelayedTransitions.RemoveAllSwap([EzAbility = CurrentFrame.Ability, TransitionIndex](const FEzAbilityTransitionDelayedState& DelayedState)
|
|
|
+ {
|
|
|
+ return DelayedState.Ability == EzAbility && DelayedState.TransitionIndex.Get() == TransitionIndex;
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ if (RequestTransition(CurrentFrame, Transition.State, Transition.Priority, Transition.Fallback))
|
|
|
+ {
|
|
|
+ NextTransitionSource = FEzAbilityTransitionSource(FEzAbilityIndex16(TransitionIndex), Transition.State, Transition.Priority);
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const bool bShouldTrigger = Transition.Trigger == EEzAbilityTransitionTrigger::OnTick
|
|
|
+ || (Transition.Trigger == EEzAbilityTransitionTrigger::OnEvent
|
|
|
+ && HasEventToProcess(Transition.EventTag));
|
|
|
+
|
|
|
+ bool bPassed = false;
|
|
|
+ if (bShouldTrigger)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_TRACE_TRANSITION_EVENT(FEzAbilityTransitionSource(FEzAbilityIndex16(TransitionIndex), Transition.State, Transition.Priority), EEzAbilityTraceEventType::OnEvaluating);
|
|
|
+ EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::TransitionConditions);
|
|
|
+ bPassed = TestAllConditions(CurrentParentFrame, CurrentFrame, Transition.ConditionsBegin, Transition.ConditionsNum);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bPassed)
|
|
|
+ {
|
|
|
+
|
|
|
+ if (Transition.HasDelay())
|
|
|
+ {
|
|
|
+ if (DelayedState == nullptr)
|
|
|
+ {
|
|
|
+
|
|
|
+ const float DelayDuration = Transition.Delay.GetRandomDuration();
|
|
|
+ if (DelayDuration > 0.0f)
|
|
|
+ {
|
|
|
+ DelayedState = &Exec.DelayedTransitions.AddDefaulted_GetRef();
|
|
|
+ DelayedState->Ability = CurrentFrame.Ability;
|
|
|
+ DelayedState->TransitionIndex = FEzAbilityIndex16(TransitionIndex);
|
|
|
+ DelayedState->TimeLeft = DelayDuration;
|
|
|
+ BeginDelayedTransition(*DelayedState);
|
|
|
+ EZ_ABILITY_LOG(Verbose, TEXT("Delayed transition triggered from '%s' (%s) -> '%s' %.1fs"),
|
|
|
+ *GetSafeStateName(CurrentFrame, CurrentFrame.ActiveStates.Last()), *State.Name.ToString(), *GetSafeStateName(CurrentFrame, Transition.State), DelayedState->TimeLeft);
|
|
|
+
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (RequestTransition(CurrentFrame, Transition.State, Transition.Priority, Transition.Fallback))
|
|
|
+ {
|
|
|
+ NextTransitionSource = FEzAbilityTransitionSource(FEzAbilityIndex16(TransitionIndex), Transition.State, Transition.Priority);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (CurrentFrame.bIsGlobalFrame)
|
|
|
+ {
|
|
|
+
|
|
|
+ if (CurrentFrame.Ability->bHasGlobalTransitionTasks)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG(VeryVerbose, TEXT("Trigger global task transitions"));
|
|
|
+ for (int32 TaskIndex = (CurrentEzAbility->GlobalTasksBegin + CurrentEzAbility->GlobalTasksNum) - 1; TaskIndex >= CurrentFrame.Ability->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);
|
|
|
+
|
|
|
+
|
|
|
+ if (Task.bTaskEnabled == false)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sSkipped 'TriggerTransitions' for disabled Task: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Task.bShouldAffectTransitions)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG(VeryVerbose, TEXT("%*sTriggerTransitions: '%s'"), UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString());
|
|
|
+ EZ_ABILITY_TRACE_TASK_EVENT(TaskIndex, TaskInstanceView, EEzAbilityTraceEventType::OnEvaluating, EAbilityRunStatus::Running);
|
|
|
+ check(TaskInstanceView.IsValid());
|
|
|
+ Task.TriggerTransitions(*this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ bool bProcessSubTreeCompletion = true;
|
|
|
+
|
|
|
+ if (NextTransition.Priority == EEzAbilityTransitionPriority::None
|
|
|
+ && Exec.LastTickStatus != EAbilityRunStatus::Running)
|
|
|
+ {
|
|
|
+
|
|
|
+ const int32 FrameStartIndex = Exec.CompletedFrameIndex.IsValid() ? Exec.CompletedFrameIndex.AsInt32() : (Exec.ActiveFrames.Num() - 1);
|
|
|
+ check(FrameStartIndex >= 0 && FrameStartIndex < Exec.ActiveFrames.Num());
|
|
|
+
|
|
|
+ for (int32 FrameIndex = FrameStartIndex; 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);
|
|
|
+
|
|
|
+ int32 StateStartIndex = CurrentFrame.ActiveStates.Num() - 1;
|
|
|
+ if (FrameIndex == FrameStartIndex
|
|
|
+ && Exec.CompletedStateHandle.IsValid())
|
|
|
+ {
|
|
|
+ StateStartIndex = CurrentFrame.ActiveStates.IndexOfReverse(Exec.CompletedStateHandle);
|
|
|
+
|
|
|
+ ensureMsgf(StateStartIndex != INDEX_NONE, TEXT("If CompletedFrameIndex and CompletedStateHandle are specified, we expect that the state is found"));
|
|
|
+ }
|
|
|
+
|
|
|
+ const EEzAbilityTransitionTrigger CompletionTrigger = Exec.LastTickStatus == EAbilityRunStatus::Succeeded ? EEzAbilityTransitionTrigger::OnStateSucceeded : EEzAbilityTransitionTrigger::OnStateFailed;
|
|
|
+
|
|
|
+
|
|
|
+ for (int32 StateIndex = StateStartIndex; StateIndex >= 0; StateIndex--)
|
|
|
+ {
|
|
|
+ const FEzAbilityStateHandle StateHandle = CurrentFrame.ActiveStates[StateIndex];
|
|
|
+ const FCompactEzAbilityState& State = CurrentEzAbility->States[StateHandle.Index];
|
|
|
+
|
|
|
+ FCurrentlyProcessedStateScope StateScope(*this, StateHandle);
|
|
|
+ EZ_ABILITY_TRACE_SCOPED_STATE_PHASE(StateHandle, EEzAbilityUpdatePhase::TriggerTransitions);
|
|
|
+
|
|
|
+ for (uint8 i = 0; i < State.TransitionsNum; i++)
|
|
|
+ {
|
|
|
+
|
|
|
+ const int16 TransitionIndex = State.TransitionsBegin + i;
|
|
|
+ const FCompactEzAbilityTransition& Transition = CurrentEzAbility->Transitions[TransitionIndex];
|
|
|
+
|
|
|
+
|
|
|
+ if (Transition.bTransitionEnabled == false)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (EnumHasAnyFlags(Transition.Trigger, CompletionTrigger))
|
|
|
+ {
|
|
|
+ bool bPassed = false;
|
|
|
+ {
|
|
|
+ EZ_ABILITY_TRACE_TRANSITION_EVENT(FEzAbilityTransitionSource(FEzAbilityIndex16(TransitionIndex), Transition.State, Transition.Priority), EEzAbilityTraceEventType::OnEvaluating);
|
|
|
+ EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::TransitionConditions);
|
|
|
+ bPassed = TestAllConditions(CurrentParentFrame, CurrentFrame, Transition.ConditionsBegin, Transition.ConditionsNum);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bPassed)
|
|
|
+ {
|
|
|
+
|
|
|
+
|
|
|
+ if (RequestTransition(CurrentFrame, Transition.State, EEzAbilityTransitionPriority::Normal, Transition.Fallback))
|
|
|
+ {
|
|
|
+ NextTransitionSource = FEzAbilityTransitionSource(FEzAbilityIndex16(TransitionIndex), Transition.State, Transition.Priority);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (NextTransition.Priority != EEzAbilityTransitionPriority::None)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (NextTransition.Priority == EEzAbilityTransitionPriority::None)
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG_AND_TRACE(Verbose, TEXT("Could not trigger completion transition, jump back to root state."));
|
|
|
+
|
|
|
+ check(!Exec.ActiveFrames.IsEmpty());
|
|
|
+ FEzAbilityExecutionFrame& RootFrame = Exec.ActiveFrames[0];
|
|
|
+ FCurrentlyProcessedFrameScope RootFrameScope(*this, nullptr, RootFrame);
|
|
|
+ FCurrentlyProcessedStateScope RootStateScope(*this, FEzAbilityStateHandle::Root);
|
|
|
+
|
|
|
+ if (RequestTransition(RootFrame, FEzAbilityStateHandle::Root, EEzAbilityTransitionPriority::Normal))
|
|
|
+ {
|
|
|
+ NextTransitionSource = FEzAbilityTransitionSource(EEzAbilityTransitionSourceType::Internal, FEzAbilityStateHandle::Root, EEzAbilityTransitionPriority::Normal);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ EZ_ABILITY_LOG_AND_TRACE(Warning, TEXT("Failed to select root state. Stopping the tree with failure."));
|
|
|
+
|
|
|
+ SetupNextTransition(RootFrame, FEzAbilityStateHandle::Failed, EEzAbilityTransitionPriority::Critical);
|
|
|
+
|
|
|
+
|
|
|
+ bProcessSubTreeCompletion = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (NextTransition.TargetState.IsCompletionState() && bProcessSubTreeCompletion)
|
|
|
+ {
|
|
|
+ const int32 SourceFrameIndex = Exec.ActiveFrames.IndexOfByPredicate([&NextTransition = NextTransition](const FEzAbilityExecutionFrame& Frame)
|
|
|
+ {
|
|
|
+ return Frame.Ability == NextTransition.SourceAbility && Frame.RootState == NextTransition.SourceRootState;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (SourceFrameIndex > 0)
|
|
|
+ {
|
|
|
+ const FEzAbilityExecutionFrame& SourceFrame = Exec.ActiveFrames[SourceFrameIndex];
|
|
|
+ const int32 ParentFrameIndex = SourceFrameIndex - 1;
|
|
|
+ const FEzAbilityExecutionFrame& ParentFrame = Exec.ActiveFrames[ParentFrameIndex];
|
|
|
+ const FEzAbilityStateHandle ParentLinkedState = ParentFrame.ActiveStates.Last();
|
|
|
+
|
|
|
+ if (ParentLinkedState.IsValid())
|
|
|
+ {
|
|
|
+ const EAbilityRunStatus RunStatus = NextTransition.TargetState.ToCompletionStatus();
|
|
|
+ EZ_ABILITY_LOG(Verbose, TEXT("Completed subtree '%s' from state '%s': %s"),
|
|
|
+ *GetSafeStateName(ParentFrame, ParentLinkedState), *GetSafeStateName(SourceFrame, NextTransition.SourceState), *UEnum::GetDisplayValueAsText(RunStatus).ToString());
|
|
|
+
|
|
|
+
|
|
|
+ Exec.CompletedFrameIndex = FEzAbilityIndex16(ParentFrameIndex);
|
|
|
+ Exec.CompletedStateHandle = ParentLinkedState;
|
|
|
+ Exec.LastTickStatus = RunStatus;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ NextTransition.Reset();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return NextTransition.TargetState.IsValid();
|
|
|
+}
|
|
|
+
|
|
|
bool FEzAbilityContext::SelectState(const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityStateHandle NextState, TArray<FEzAbilityExecutionFrame, TFixedAllocator<MaxExecutionFrames>>& OutNextActiveFrames, const EEzAbilitySelectionFallback Fallback)
|
|
|
{
|
|
|
const FEzAbilityExecutionState& Exec = GetExecState();
|
|
@@ -2321,6 +2837,133 @@ bool FEzAbilityContext::SelectStateInternal(const FEzAbilityExecutionFrame* Curr
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+EAbilityRunStatus FEzAbilityContext::TickTasks(const float DeltaTime)
|
|
|
+{
|
|
|
+ CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_TickTasks);
|
|
|
+ EZ_ABILITY_TRACE_SCOPED_PHASE(EEzAbilityUpdatePhase::TickingTasks);
|
|
|
+
|
|
|
+ FEzAbilityExecutionState& Exec = GetExecState();
|
|
|
+
|
|
|
+ if (Exec.ActiveFrames.IsEmpty())
|
|
|
+ {
|
|
|
+ return EAbilityRunStatus::Failed;
|
|
|
+ }
|
|
|
+
|
|
|
+ EAbilityRunStatus Result = EAbilityRunStatus::Running;
|
|
|
+ int32 NumTotalTasks = 0;
|
|
|
+
|
|
|
+ const bool bHasEvents = !EventsToProcess.IsEmpty();
|
|
|
+
|
|
|
+ Exec.CompletedFrameIndex = FEzAbilityIndex16::Invalid;
|
|
|
+ Exec.CompletedStateHandle = FEzAbilityStateHandle::Invalid;
|
|
|
+
|
|
|
+
|
|
|
+ bool bShouldTickTasks = true;
|
|
|
+
|
|
|
+ EZ_ABILITY_CLOG(Exec.ActiveFrames.Num() > 0, VeryVerbose, TEXT("Ticking Tasks"));
|
|
|
+
|
|
|
+ for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); 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);
|
|
|
+
|
|
|
+ for (int32 Index = 0; Index < CurrentFrame.ActiveStates.Num(); Index++)
|
|
|
+ {
|
|
|
+ const FEzAbilityStateHandle CurrentHandle = CurrentFrame.ActiveStates[Index];
|
|
|
+ const FCompactEzAbilityState& State = CurrentEzAbility->States[CurrentHandle.Index];
|
|
|
+
|
|
|
+ FCurrentlyProcessedStateScope StateScope(*this, CurrentHandle);
|
|
|
+ EZ_ABILITY_TRACE_SCOPED_STATE(CurrentHandle);
|
|
|
+
|
|
|
+ EZ_ABILITY_CLOG(State.TasksNum > 0, VeryVerbose, TEXT("%*sState '%s'"), Index*UE::EzAbility::DebugIndentSize, TEXT(""), *DebugGetStatePath(Exec.ActiveFrames, &CurrentFrame, Index));
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ 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);
|
|
|
+ FNodeInstanceDataScope DataScope(*this, Task.InstanceDataHandle, TaskInstanceView);
|
|
|
+
|
|
|
+
|
|
|
+ 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("%*s Tick: '%s' %s"), Index*UE::EzAbility::DebugIndentSize, TEXT(""), *Task.Name.ToString(), !bNeedsTick ? TEXT("[not ticked]") : TEXT(""));
|
|
|
+ if (!bNeedsTick)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if (Task.BindingsBatch.IsValid() && Task.bShouldCopyBoundPropertiesOnTick)
|
|
|
+ {
|
|
|
+ CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ 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 (TaskResult != EAbilityRunStatus::Running)
|
|
|
+ {
|
|
|
+
|
|
|
+ if (!Exec.CompletedStateHandle.IsValid())
|
|
|
+ {
|
|
|
+ Exec.CompletedFrameIndex = FEzAbilityIndex16(FrameIndex);
|
|
|
+ Exec.CompletedStateHandle = CurrentHandle;
|
|
|
+ }
|
|
|
+ Result = TaskResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (TaskResult == EAbilityRunStatus::Failed)
|
|
|
+ {
|
|
|
+ bShouldTickTasks = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ NumTotalTasks += State.TasksNum;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (NumTotalTasks == 0)
|
|
|
+ {
|
|
|
+
|
|
|
+ Result = EAbilityRunStatus::Succeeded;
|
|
|
+ Exec.CompletedFrameIndex = FEzAbilityIndex16(0);
|
|
|
+ Exec.CompletedStateHandle = Exec.ActiveFrames[0].ActiveStates.GetStateSafe(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ return Result;
|
|
|
+}
|
|
|
+
|
|
|
bool FEzAbilityContext::TestAllConditions(const FEzAbilityExecutionFrame* CurrentParentFrame, const FEzAbilityExecutionFrame& CurrentFrame, const int32 ConditionsOffset, const int32 ConditionsNum)
|
|
|
{
|
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EzAbility_TestConditions);
|
|
@@ -2413,6 +3056,73 @@ bool FEzAbilityContext::TestAllConditions(const FEzAbilityExecutionFrame* Curren
|
|
|
return Values[0];
|
|
|
}
|
|
|
|
|
|
+bool FEzAbilityContext::RequestTransition(const FEzAbilityExecutionFrame& CurrentFrame,
|
|
|
+ const FEzAbilityStateHandle NextState, const EEzAbilityTransitionPriority Priority,
|
|
|
+ const EEzAbilitySelectionFallback Fallback)
|
|
|
+{
|
|
|
+
|
|
|
+ if (NextTransition.Priority >= Priority)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (NextState.IsCompletionState())
|
|
|
+ {
|
|
|
+ SetupNextTransition(CurrentFrame, NextState, Priority);
|
|
|
+ EZ_ABILITY_LOG(Verbose, TEXT("Transition on state '%s' -> state '%s'"),
|
|
|
+ *GetSafeStateName(CurrentFrame, CurrentFrame.ActiveStates.Last()), *NextState.Describe());
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (!NextState.IsValid())
|
|
|
+ {
|
|
|
+
|
|
|
+ SetupNextTransition(CurrentFrame, FEzAbilityStateHandle::Invalid, Priority);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ TArray<FEzAbilityExecutionFrame, TFixedAllocator<MaxExecutionFrames>> NewNextActiveFrames;
|
|
|
+ if (SelectState(CurrentFrame, NextState, NewNextActiveFrames, Fallback))
|
|
|
+ {
|
|
|
+ SetupNextTransition(CurrentFrame, NextState, Priority);
|
|
|
+ NextTransition.NextActiveFrames = NewNextActiveFrames;
|
|
|
+
|
|
|
+ EZ_ABILITY_LOG(Verbose, TEXT("Transition on state '%s' -[%s]-> state '%s'"),
|
|
|
+ *GetSafeStateName(CurrentFrame, CurrentFrame.ActiveStates.Last()),
|
|
|
+ *GetSafeStateName(CurrentFrame, NextState),
|
|
|
+ *GetSafeStateName(NextTransition.NextActiveFrames.Last(), NextTransition.NextActiveFrames.Last().ActiveStates.Last()));
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+void FEzAbilityContext::SetupNextTransition(const FEzAbilityExecutionFrame& CurrentFrame,
|
|
|
+ const FEzAbilityStateHandle NextState, const EEzAbilityTransitionPriority Priority)
|
|
|
+{
|
|
|
+ const FEzAbilityExecutionState& Exec = GetExecState();
|
|
|
+
|
|
|
+ NextTransition.CurrentRunStatus = Exec.LastTickStatus;
|
|
|
+ NextTransition.SourceState = CurrentlyProcessedState;
|
|
|
+ NextTransition.SourceAbility = CurrentFrame.Ability;
|
|
|
+ NextTransition.SourceRootState = CurrentFrame.ActiveStates.GetStateSafe(0);
|
|
|
+ NextTransition.TargetState = NextState;
|
|
|
+ NextTransition.Priority = Priority;
|
|
|
+
|
|
|
+ FEzAbilityExecutionFrame& NewFrame = NextTransition.NextActiveFrames.AddDefaulted_GetRef();
|
|
|
+ NewFrame.Ability = CurrentFrame.Ability;
|
|
|
+ NewFrame.RootState = CurrentFrame.RootState;
|
|
|
+
|
|
|
+ if (NextState == FEzAbilityStateHandle::Invalid)
|
|
|
+ {
|
|
|
+ NewFrame.ActiveStates = {};
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ NewFrame.ActiveStates = FEzAbilityActiveStates(NextState);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
FString FEzAbilityContext::GetStateStatusString(const FEzAbilityExecutionState& ExecState) const
|
|
|
{
|
|
|
if (ExecState.TreeRunStatus != EAbilityRunStatus::Running)
|