Files
UnrealEngine/Engine/Plugins/Runtime/StateTree/Source/StateTreeModule/Private/StateTreeExecutionContext.cpp
2025-05-18 13:04:45 +08:00

6090 lines
245 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "StateTreeExecutionContext.h"
#include "StateTreeAsyncExecutionContext.h"
#include "StateTreeTaskBase.h"
#include "StateTreeEvaluatorBase.h"
#include "StateTreeConditionBase.h"
#include "StateTreeConsiderationBase.h"
#include "StateTreeDelegate.h"
#include "StateTreeInstanceDataHelpers.h"
#include "StateTreePropertyFunctionBase.h"
#include "StateTreeReference.h"
#include "AutoRTFM.h"
#include "Containers/StaticArray.h"
#include "Debugger/StateTreeDebug.h"
#include "Debugger/StateTreeTrace.h"
#include "Debugger/StateTreeTraceTypes.h"
#include "Misc/ScopeExit.h"
#include "VisualLogger/VisualLogger.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include "Logging/LogScopedVerbosityOverride.h"
#define STATETREE_LOG(Verbosity, Format, ...) UE_VLOG_UELOG(GetOwner(), LogStateTree, Verbosity, TEXT("%s: ") Format, *GetInstanceDescriptionInternal(), ##__VA_ARGS__)
#define STATETREE_CLOG(Condition, Verbosity, Format, ...) UE_CVLOG_UELOG((Condition), GetOwner(), LogStateTree, Verbosity, TEXT("%s: ") Format, *GetInstanceDescriptionInternal(), ##__VA_ARGS__)
namespace UE::StateTree::Debug
{
constexpr int32 IndentSize = 2; // Debug printing indent for hierarchical data.
static std::atomic<uint32> InstanceSerialNumber = 0;
}; // namespace
namespace UE::StateTree::ExecutionContext
{
namespace Private
{
bool bCopyBoundPropertiesOnNonTickedTask = false;
FAutoConsoleVariableRef CVarCopyBoundPropertiesOnNonTickedTask(
TEXT("StateTree.CopyBoundPropertiesOnNonTickedTask"),
bCopyBoundPropertiesOnNonTickedTask,
TEXT("When ticking the tasks, copy the bindings when the tasks are not ticked because it disabled ticking or completed. The bindings are not updated on failure.")
);
bool bTickGlobalNodesFollowingTreeHierarchy = true;
FAutoConsoleVariableRef CVarTickGlobalNodesFollowingTreeHierarchy(
TEXT("StateTree.TickGlobalNodesFollowingTreeHierarchy"),
bTickGlobalNodesFollowingTreeHierarchy,
TEXT("If true, then the global evaluators and global tasks are ticked following the asset hierarchy.\n")
TEXT("The order is (1)root evaluators, (2)root global tasks, (3)state tasks, (4)linked asset evaluators, (5)linked asset global tasks, (6)linked asset state tasks.\n")
TEXT("If false (5.5 behavior), then all the global nodes, from all linked assets, are evaluated, then the state tasks are ticked.\n")
TEXT("You should upgrade your asset. This option is to help migrate to the new behavior.")
);
bool bGlobalTasksCompleteOwningFrame = true;
FAutoConsoleVariableRef CVarGlobalTasksCompleteFrame(
TEXT("StateTree.GlobalTasksCompleteOwningFrame"),
bGlobalTasksCompleteOwningFrame,
TEXT("If true, the global tasks complete the tree they are part of. The root or the linked state. 5.6 behavior.\n")
TEXT("If false, the global tasks complete the root tree execution (even for linked assets). 5.5 behavior.")
TEXT("You should upgrade your asset. This option is to help migrate to the new behavior.")
);
constexpr uint32 NumEStateTreeRunStatus()
{
#ifdef UE_STATETREE_ESTATETREERUNSTATUS_PRIVATE
#error UE_STATETREE_ESTATETREERUNSTATUS_PRIVATE_ALREADY_DEFINED
#endif
#define UE_STATETREE_ESTATETREERUNSTATUS_PRIVATE(EnumValue) ++Number;
int32 Number = 0;
FOREACH_ENUM_ESTATETREERUNSTATUS(UE_STATETREE_ESTATETREERUNSTATUS_PRIVATE)
#undef UE_STATETREE_ESTATETREERUNSTATUS_PRIVATE
return Number;
}
constexpr uint32 NumEStateTreeFinishTaskType()
{
#ifdef UE_STATETREE_ESTATETREEFINISHTASKTYPE_PRIVATE
#error UE_STATETREE_ESTATETREEFINISHTASKTYPE_PRIVATE_ALREADY_DEFINED
#endif
#define UE_STATETREE_ESTATETREEFINISHTASKTYPE_PRIVATE(EnumValue) ++Number;
int32 Number = 0;
FOREACH_ENUM_ESTATETREEFINISHTASKTYPE(UE_STATETREE_ESTATETREEFINISHTASKTYPE_PRIVATE)
#undef UE_STATETREE_ESTATETREEFINISHTASKTYPE_PRIVATE
return Number;
}
}
bool MarkDelegateAsBroadcasted(FStateTreeDelegateDispatcher Dispatcher, const FStateTreeExecutionFrame& CurrentFrame, FStateTreeInstanceStorage& Storage)
{
const UStateTree* StateTree = CurrentFrame.StateTree;
check(StateTree);
for (FStateTreeStateHandle ActiveState : CurrentFrame.ActiveStates)
{
const FCompactStateTreeState* State = StateTree->GetStateFromHandle(ActiveState);
check(State);
if (!State->bHasDelegateTriggerTransitions)
{
continue;
}
const int32 TransitionEnd = State->TransitionsBegin + State->TransitionsNum;
for (int32 TransitionIndex = State->TransitionsBegin; TransitionIndex <TransitionEnd; ++TransitionIndex)
{
const FCompactStateTransition* Transition = StateTree->GetTransitionFromIndex(FStateTreeIndex16(TransitionIndex));
check(Transition);
if (Transition->RequiredDelegateDispatcher == Dispatcher)
{
ensureMsgf(EnumHasAnyFlags(Transition->Trigger, EStateTreeTransitionTrigger::OnDelegate), TEXT("The transition should have both (a valid dispatcher and the OnDelegate flag) or none."));
Storage.MarkDelegateAsBroadcasted(Dispatcher);
return true;
}
}
}
return false;
}
/** @return in order {Failed, Succeeded, Stopped, Running, Unset} */
EStateTreeRunStatus GetPriorityRunStatus(EStateTreeRunStatus A, EStateTreeRunStatus B)
{
static_assert((int32)EStateTreeRunStatus::Running == 0);
static_assert((int32)EStateTreeRunStatus::Stopped == 1);
static_assert((int32)EStateTreeRunStatus::Succeeded == 2);
static_assert((int32)EStateTreeRunStatus::Failed == 3);
static_assert((int32)EStateTreeRunStatus::Unset == 4);
static_assert(Private::NumEStateTreeRunStatus() == 5, "The number of entries in EStateTreeRunStatus changed. Update GetPriorityRunStatus.");
static constexpr int32 PriorityMatrix[] = { 1, 2, 3, 4, 0 };
return PriorityMatrix[(uint8)A] > PriorityMatrix[(uint8)B] ? A : B;
}
UE::StateTree::ETaskCompletionStatus CastToTaskStatus(EStateTreeFinishTaskType FinishTask)
{
static_assert(Private::NumEStateTreeFinishTaskType() == 2, "The number of entries in EStateTreeFinishTaskType changed. Update CastToTaskStatus.");
return FinishTask == EStateTreeFinishTaskType::Succeeded ? UE::StateTree::ETaskCompletionStatus::Succeeded : UE::StateTree::ETaskCompletionStatus::Failed;
}
EStateTreeRunStatus CastToRunStatus(EStateTreeFinishTaskType FinishTask)
{
static_assert(Private::NumEStateTreeFinishTaskType() == 2, "The number of entries in EStateTreeFinishTaskType changed. Update CastToRunStatus.");
return FinishTask == EStateTreeFinishTaskType::Succeeded ? EStateTreeRunStatus::Succeeded : EStateTreeRunStatus::Failed;
}
UE::StateTree::ETaskCompletionStatus CastToTaskStatus(EStateTreeRunStatus InStatus)
{
static_assert((int32)EStateTreeRunStatus::Running == (int32)UE::StateTree::ETaskCompletionStatus::Running);
static_assert((int32)EStateTreeRunStatus::Stopped == (int32)UE::StateTree::ETaskCompletionStatus::Stopped);
static_assert((int32)EStateTreeRunStatus::Succeeded == (int32)UE::StateTree::ETaskCompletionStatus::Succeeded);
static_assert((int32)EStateTreeRunStatus::Failed == (int32)UE::StateTree::ETaskCompletionStatus::Failed);
static_assert(Private::NumEStateTreeRunStatus() == 5, "The number of entries in EStateTreeRunStatus changed. Update CastToTaskStatus.");
return InStatus != EStateTreeRunStatus::Unset ? (UE::StateTree::ETaskCompletionStatus)InStatus : UE::StateTree::ETaskCompletionStatus::Running;
}
EStateTreeRunStatus CastToRunStatus(UE::StateTree::ETaskCompletionStatus InStatus)
{
static_assert((int32)EStateTreeRunStatus::Running == (int32)UE::StateTree::ETaskCompletionStatus::Running);
static_assert((int32)EStateTreeRunStatus::Stopped == (int32)UE::StateTree::ETaskCompletionStatus::Stopped);
static_assert((int32)EStateTreeRunStatus::Succeeded == (int32)UE::StateTree::ETaskCompletionStatus::Succeeded);
static_assert((int32)EStateTreeRunStatus::Failed == (int32)UE::StateTree::ETaskCompletionStatus::Failed);
static_assert(UE::StateTree::NumberOfTaskStatus == 4, "The number of entries in EStateTreeRunStatus changed. Update CastToRunStatus.");
return (EStateTreeRunStatus)InStatus;
}
}
/**
* FStateTreeReadOnlyExecutionContext implementation
*/
FStateTreeReadOnlyExecutionContext::FStateTreeReadOnlyExecutionContext(TNotNull<UObject*> InOwner, TNotNull<const UStateTree*> InStateTree, FStateTreeInstanceData& InInstanceData)
: FStateTreeReadOnlyExecutionContext(InOwner, InStateTree, InInstanceData.GetMutableStorage())
{
}
FStateTreeReadOnlyExecutionContext::FStateTreeReadOnlyExecutionContext(TNotNull<UObject*> InOwner, TNotNull<const UStateTree*> InStateTree, FStateTreeInstanceStorage& InStorage)
: Owner(*InOwner)
, RootStateTree(*InStateTree)
, Storage(InStorage)
{
Storage.AcquireReadAccess();
if (IsValid())
{
Storage.GetRuntimeValidation().SetContext(&Owner, &RootStateTree);
}
}
FStateTreeReadOnlyExecutionContext::~FStateTreeReadOnlyExecutionContext()
{
Storage.ReleaseReadAccess();
}
FStateTreeScheduledTick FStateTreeReadOnlyExecutionContext::GetNextScheduledTick() const
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return FStateTreeScheduledTick::MakeSleep();
}
const FStateTreeExecutionState& Exec = Storage.GetExecutionState();
if (Exec.TreeRunStatus != EStateTreeRunStatus::Running)
{
return FStateTreeScheduledTick::MakeSleep();
}
// USchema::IsScheduleTickAllowed.
//Used the state tree cached value to prevent runtime changes that could affect the behavior.
for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); ++FrameIndex)
{
const FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
if (!CurrentFrame.StateTree->IsScheduledTickAllowed())
{
return FStateTreeScheduledTick::MakeEveryFrames();
}
}
const FStateTreeEventQueue& EventQueue = Storage.GetEventQueue();
const bool bHasEvents = EventQueue.HasEvents();
const bool bHasBroadcastedDelegates = Storage.HasBroadcastedDelegates();
// We wish to return in order: EveryFrames, then NextFrame, then CustomTickRate, then Sleep.
// Do we have a state that requires a tick or is waiting for an event.
TOptional<float> CustomTickRate;
{
bool bHasTaskWithEveryFramesTick = false;
for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); ++FrameIndex)
{
const FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
// Test global tasks.
if (CurrentStateTree->DoesRequestTickGlobalTasks(bHasEvents))
{
bHasTaskWithEveryFramesTick = true;
}
// Test active states tasks.
for (int32 StateIndex = 0; StateIndex < CurrentFrame.ActiveStates.Num(); ++StateIndex)
{
const FStateTreeStateHandle CurrentHandle = CurrentFrame.ActiveStates[StateIndex];
const FCompactStateTreeState& State = CurrentStateTree->States[CurrentHandle.Index];
if (State.bEnabled)
{
if (State.bHasCustomTickRate)
{
CustomTickRate = CustomTickRate.IsSet() ? FMath::Min(CustomTickRate.GetValue(), State.CustomTickRate) : State.CustomTickRate;
}
else if (!CustomTickRate.IsSet())
{
if (State.DoesRequestTickTasks(bHasEvents)
|| State.ShouldTickTransitions(bHasEvents, bHasBroadcastedDelegates)
)
{
// todo: ShouldTickTransitions has onevent or ontick. both can be already triggered and we are waiting for the delay
bHasTaskWithEveryFramesTick = true;
}
}
}
}
}
if (!CustomTickRate.IsSet() && bHasTaskWithEveryFramesTick)
{
return FStateTreeScheduledTick::MakeEveryFrames();
}
// If one state has a custom tick rate, then it overrides the tick rate for all states.
//Only return the CustomTickRate if it's > than NextFrame, the custom tick rate will be processed at the end.
if (CustomTickRate.IsSet() && CustomTickRate.GetValue() <= 0.0f)
{
// A state might override the custom tick rate with > 0, then another state overrides it again with 0 to tick back every frame.
return FStateTreeScheduledTick::MakeEveryFrames();
}
}
// Requests
if (Exec.HasScheduledTickRequests())
{
// The ScheduledTickRequests loop value is cached. Returns every frame or next frame. CustomTime needs to wait after the other tests.
const FStateTreeScheduledTick ScheduledTickRequest = Exec.GetScheduledTickRequest();
if (ScheduledTickRequest.ShouldTickEveryFrames() || ScheduledTickRequest.ShouldTickOnceNextFrame())
{
return ScheduledTickRequest;
}
const float CachedTickRate = ScheduledTickRequest.GetTickRate();
CustomTickRate = CustomTickRate.IsSet() ? FMath::Min(CustomTickRate.GetValue(), CachedTickRate) : CachedTickRate;
}
// Transitions
if (Storage.GetTransitionRequests().Num() > 0)
{
return FStateTreeScheduledTick::MakeNextFrame();
}
// Events are cleared every tick.
if (bHasEvents && Storage.IsOwningEventQueue())
{
return FStateTreeScheduledTick::MakeNextFrame();
}
// Completed task. For EnterState or for user that only called TickTasks and not TickTransitions.
if (Exec.bHasPendingCompletedState)
{
return FStateTreeScheduledTick::MakeNextFrame();
}
// Min of all delayed transitions.
if (Exec.DelayedTransitions.Num() > 0)
{
for (const FStateTreeTransitionDelayedState& Transition : Exec.DelayedTransitions)
{
CustomTickRate = CustomTickRate.IsSet() ? FMath::Min(CustomTickRate.GetValue(), Transition.TimeLeft) : Transition.TimeLeft;
}
}
// Custom tick rate for tasks and transitions.
if (CustomTickRate.IsSet())
{
return FStateTreeScheduledTick::MakeCustomTickRate(CustomTickRate.GetValue());
}
return FStateTreeScheduledTick::MakeSleep();
}
EStateTreeRunStatus FStateTreeReadOnlyExecutionContext::GetStateTreeRunStatus() const
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return EStateTreeRunStatus::Failed;
}
return Storage.GetExecutionState().TreeRunStatus;
}
EStateTreeRunStatus FStateTreeReadOnlyExecutionContext::GetLastTickStatus() const
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return EStateTreeRunStatus::Failed;
}
const FStateTreeExecutionState& Exec = Storage.GetExecutionState();
return Exec.LastTickStatus;
}
TConstArrayView<FStateTreeExecutionFrame> FStateTreeReadOnlyExecutionContext::GetActiveFrames() const
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return TConstArrayView<FStateTreeExecutionFrame>();
}
const FStateTreeExecutionState& Exec = Storage.GetExecutionState();
return Exec.ActiveFrames;
}
FString FStateTreeReadOnlyExecutionContext::GetActiveStateName() const
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return FString();
}
const FStateTreeExecutionState& Exec = Storage.GetExecutionState();
TStringBuilder<1024> FullStateName;
const UStateTree* LastStateTree = &RootStateTree;
int32 Indent = 0;
for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); FrameIndex++)
{
const FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
// Append linked state marker at the end of the previous line.
if (Indent > 0)
{
FullStateName << TEXT(" >");
}
// If tree has changed, append that too.
if (CurrentFrame.StateTree != LastStateTree)
{
FullStateName << TEXT(" [");
FullStateName << CurrentFrame.StateTree.GetFName();
FullStateName << TEXT(']');
LastStateTree = CurrentFrame.StateTree;
}
for (int32 Index = 0; Index < CurrentFrame.ActiveStates.Num(); Index++)
{
const FStateTreeStateHandle Handle = CurrentFrame.ActiveStates[Index];
if (Handle.IsValid())
{
const FCompactStateTreeState& State = CurrentStateTree->States[Handle.Index];
if (Indent > 0)
{
FullStateName += TEXT("\n");
}
FullStateName.Appendf(TEXT("%*s-"), Indent * 3, TEXT("")); // Indent
FullStateName << State.Name;
Indent++;
}
}
}
switch (Exec.TreeRunStatus)
{
case EStateTreeRunStatus::Failed:
FullStateName << TEXT(" FAILED\n");
break;
case EStateTreeRunStatus::Succeeded:
FullStateName << TEXT(" SUCCEEDED\n");
break;
case EStateTreeRunStatus::Running:
// Empty
break;
default:
FullStateName << TEXT("--\n");
}
return FullStateName.ToString();
}
TArray<FName> FStateTreeReadOnlyExecutionContext::GetActiveStateNames() const
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return TArray<FName>();
}
TArray<FName> Result;
const FStateTreeExecutionState& Exec = Storage.GetExecutionState();
// Active States
for (const FStateTreeExecutionFrame& CurrentFrame : Exec.ActiveFrames)
{
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
for (int32 Index = 0; Index < CurrentFrame.ActiveStates.Num(); Index++)
{
const FStateTreeStateHandle Handle = CurrentFrame.ActiveStates[Index];
if (Handle.IsValid())
{
const FCompactStateTreeState& State = CurrentStateTree->States[Handle.Index];
Result.Add(State.Name);
}
}
}
return Result;
}
#if WITH_GAMEPLAY_DEBUGGER
FString FStateTreeReadOnlyExecutionContext::GetDebugInfoString() const
{
TStringBuilder<2048> DebugString;
DebugString << TEXT("StateTree (asset: '");
RootStateTree.GetFullName(DebugString);
DebugString << TEXT("')");
if (IsValid())
{
const FStateTreeExecutionState& Exec = Storage.GetExecutionState();
DebugString << TEXT("Status: ");
DebugString << UEnum::GetDisplayValueAsText(Exec.TreeRunStatus).ToString();
DebugString << TEXT("\n");
// Active States
DebugString << TEXT("Current State:\n");
for (const FStateTreeExecutionFrame& CurrentFrame : Exec.ActiveFrames)
{
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
if (CurrentFrame.bIsGlobalFrame)
{
DebugString.Appendf(TEXT("\nEvaluators\n [ %-30s | %8s | %15s ]\n"),
TEXT("Name"), TEXT("Bindings"), TEXT("Data Handle"));
for (int32 EvalIndex = CurrentStateTree->EvaluatorsBegin; EvalIndex < (CurrentStateTree->EvaluatorsBegin + CurrentStateTree->EvaluatorsNum); EvalIndex++)
{
const FStateTreeEvaluatorBase& Eval = CurrentStateTree->Nodes[EvalIndex].Get<const FStateTreeEvaluatorBase>();
DebugString.Appendf(TEXT("| %-30s | %8d | %15s |\n"),
*Eval.Name.ToString(), Eval.BindingsBatch.Get(), *Eval.InstanceDataHandle.Describe());
}
DebugString << TEXT("\nGlobal Tasks\n");
for (int32 TaskIndex = CurrentStateTree->GlobalTasksBegin; TaskIndex < (CurrentStateTree->GlobalTasksBegin + CurrentStateTree->GlobalTasksNum); TaskIndex++)
{
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
if (Task.bTaskEnabled)
{
DebugString << Task.GetDebugInfo(*this);
}
}
}
for (int32 Index = 0; Index < CurrentFrame.ActiveStates.Num(); Index++)
{
FStateTreeStateHandle Handle = CurrentFrame.ActiveStates[Index];
if (Handle.IsValid())
{
const FCompactStateTreeState& State = CurrentStateTree->States[Handle.Index];
DebugString << TEXT('[');
DebugString << State.Name;
DebugString << TEXT("]\n");
if (State.TasksNum > 0)
{
DebugString += TEXT("\nTasks:\n");
for (int32 TaskIndex = State.TasksBegin; TaskIndex < (State.TasksBegin + State.TasksNum); TaskIndex++)
{
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
if (Task.bTaskEnabled)
{
DebugString << Task.GetDebugInfo(*this);
}
}
}
}
}
}
}
else
{
DebugString << TEXT("StateTree context is not initialized properly.");
}
return DebugString.ToString();
}
#endif // WITH_GAMEPLAY_DEBUGGER
#if WITH_STATETREE_DEBUG
int32 FStateTreeReadOnlyExecutionContext::GetStateChangeCount() const
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return 0;
}
const FStateTreeExecutionState& Exec = Storage.GetExecutionState();
return Exec.StateChangeCount;
}
void FStateTreeReadOnlyExecutionContext::DebugPrintInternalLayout()
{
LOG_SCOPE_VERBOSITY_OVERRIDE(LogStateTree, ELogVerbosity::Log);
UE_LOG(LogStateTree, Log, TEXT("%s"), *RootStateTree.DebugInternalLayoutAsString());
}
#endif // WITH_STATETREE_DEBUG
FString FStateTreeReadOnlyExecutionContext::GetInstanceDescriptionInternal() const
{
const TInstancedStruct<FStateTreeExecutionExtension>& ExecutionExtension = Storage.GetExecutionState().ExecutionExtension;
return ExecutionExtension.IsValid()
? ExecutionExtension.Get().GetInstanceDescription(FStateTreeExecutionExtension::FContextParameters(Owner, RootStateTree, Storage))
: Owner.GetName();
}
#if WITH_STATETREE_TRACE
UE_AUTORTFM_ALWAYS_OPEN
static uint32 GetNextInstanceSerialNumber()
{
// The instance serial number is only used to synthesize unique instance debug IDs; rollback isn't needed.
return ++UE::StateTree::Debug::InstanceSerialNumber;
}
FStateTreeInstanceDebugId FStateTreeReadOnlyExecutionContext::GetInstanceDebugId() const
{
FStateTreeInstanceDebugId& InstanceDebugId = Storage.GetMutableExecutionState().InstanceDebugId;
if (!InstanceDebugId.IsValid())
{
InstanceDebugId = FStateTreeInstanceDebugId(GetTypeHash(GetInstanceDescriptionInternal()), GetNextInstanceSerialNumber());
}
return InstanceDebugId;
}
#endif // WITH_STATETREE_TRACE
/**
* FStateTreeMinimalExecutionContext implementation
*/
// Deprecated
FStateTreeMinimalExecutionContext::FStateTreeMinimalExecutionContext(UObject& InOwner, const UStateTree& InStateTree, FStateTreeInstanceData& InInstanceData)
: FStateTreeMinimalExecutionContext(&InOwner, &InStateTree, InInstanceData.GetMutableStorage())
{
}
// Deprecated
FStateTreeMinimalExecutionContext::FStateTreeMinimalExecutionContext(UObject& InOwner, const UStateTree& InStateTree, FStateTreeInstanceStorage& InStorage)
: FStateTreeMinimalExecutionContext(&InOwner, &InStateTree, InStorage)
{
}
FStateTreeMinimalExecutionContext::FStateTreeMinimalExecutionContext(TNotNull<UObject*> InOwner, TNotNull<const UStateTree*> InStateTree, FStateTreeInstanceData& InInstanceData)
: FStateTreeMinimalExecutionContext(InOwner, InStateTree, InInstanceData.GetMutableStorage())
{
}
FStateTreeMinimalExecutionContext::FStateTreeMinimalExecutionContext(TNotNull<UObject*> InOwner, TNotNull<const UStateTree*> InStateTree, FStateTreeInstanceStorage& InStorage)
: FStateTreeReadOnlyExecutionContext(InOwner, InStateTree, InStorage)
{
Storage.AcquireWriteAccess();
}
FStateTreeMinimalExecutionContext::~FStateTreeMinimalExecutionContext()
{
Storage.ReleaseWriteAccess();
}
UE::StateTree::FScheduledTickHandle FStateTreeMinimalExecutionContext::AddScheduledTickRequest(FStateTreeScheduledTick ScheduledTick)
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return UE::StateTree::FScheduledTickHandle();
}
UE::StateTree::FScheduledTickHandle Result = Storage.GetMutableExecutionState().AddScheduledTickRequest(ScheduledTick);
ScheduleNextTick();
return Result;
}
void FStateTreeMinimalExecutionContext::UpdateScheduledTickRequest(UE::StateTree::FScheduledTickHandle Handle, FStateTreeScheduledTick ScheduledTick)
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
if (Storage.GetMutableExecutionState().UpdateScheduledTickRequest(Handle, ScheduledTick))
{
ScheduleNextTick();
}
}
void FStateTreeMinimalExecutionContext::RemoveScheduledTickRequest(UE::StateTree::FScheduledTickHandle Handle)
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
if (Storage.GetMutableExecutionState().RemoveScheduledTickRequest(Handle))
{
ScheduleNextTick();
}
}
void FStateTreeMinimalExecutionContext::SendEvent(const FGameplayTag Tag, const FConstStructView Payload, const FName Origin)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_SendEvent);
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
STATETREE_LOG(VeryVerbose, TEXT("Send Event '%s'"), *Tag.ToString());
UE_STATETREE_DEBUG_LOG_EVENT(this, Log, TEXT("Send Event '%s'"), *Tag.ToString());
FStateTreeEventQueue& LocalEventQueue = Storage.GetMutableEventQueue();
LocalEventQueue.SendEvent(&Owner, Tag, Payload, Origin);
ScheduleNextTick();
UE_STATETREE_DEBUG_SEND_EVENT(this, &RootStateTree, Tag, Payload, Origin);
}
void FStateTreeMinimalExecutionContext::ScheduleNextTick()
{
TInstancedStruct<FStateTreeExecutionExtension>& ExecutionExtension = Storage.GetMutableExecutionState().ExecutionExtension;
if (bAllowedToScheduleNextTick && RootStateTree.IsScheduledTickAllowed() && ExecutionExtension.IsValid())
{
ExecutionExtension.GetMutable().ScheduleNextTick(FStateTreeExecutionExtension::FContextParameters(Owner, RootStateTree, Storage));
}
}
/**
* FStateTreeExecutionContext::FCurrentlyProcessedFrameScope implementation
*/
FStateTreeExecutionContext::FCurrentlyProcessedFrameScope::FCurrentlyProcessedFrameScope(FStateTreeExecutionContext& InContext, const FStateTreeExecutionFrame* CurrentParentFrame, const FStateTreeExecutionFrame& CurrentFrame): Context(InContext)
{
check(CurrentFrame.StateTree);
FStateTreeInstanceStorage* SharedInstanceDataStorage = &CurrentFrame.StateTree->GetSharedInstanceData()->GetMutableStorage();
SavedFrame = Context.CurrentlyProcessedFrame;
SavedParentFrame = Context.CurrentlyProcessedParentFrame;
SavedSharedInstanceDataStorage = Context.CurrentlyProcessedSharedInstanceStorage;
Context.CurrentlyProcessedFrame = &CurrentFrame;
Context.CurrentlyProcessedParentFrame = CurrentParentFrame;
Context.CurrentlyProcessedSharedInstanceStorage = SharedInstanceDataStorage;
UE_STATETREE_DEBUG_INSTANCE_FRAME_EVENT(&Context, Context.CurrentlyProcessedFrame);
}
FStateTreeExecutionContext::FCurrentlyProcessedFrameScope::~FCurrentlyProcessedFrameScope()
{
Context.CurrentlyProcessedFrame = SavedFrame;
Context.CurrentlyProcessedParentFrame = SavedParentFrame;
Context.CurrentlyProcessedSharedInstanceStorage = SavedSharedInstanceDataStorage;
if (Context.CurrentlyProcessedFrame)
{
UE_STATETREE_DEBUG_INSTANCE_FRAME_EVENT(&Context, Context.CurrentlyProcessedFrame);
}
}
/**
* FStateTreeExecutionContext::FNodeInstanceDataScope implementation
*/
FStateTreeExecutionContext::FNodeInstanceDataScope::FNodeInstanceDataScope(FStateTreeExecutionContext& InContext, const FStateTreeNodeBase* InNode, const int32 InNodeIndex, const FStateTreeDataHandle InNodeDataHandle, const FStateTreeDataView InNodeInstanceData)
: Context(InContext)
{
SavedNode = Context.CurrentNode;
SavedNodeIndex = Context.CurrentNodeIndex;
SavedNodeDataHandle = Context.CurrentNodeDataHandle;
SavedNodeInstanceData = Context.CurrentNodeInstanceData;
Context.CurrentNode = InNode;
Context.CurrentNodeIndex = InNodeIndex;
Context.CurrentNodeDataHandle = InNodeDataHandle;
Context.CurrentNodeInstanceData = InNodeInstanceData;
}
FStateTreeExecutionContext::FNodeInstanceDataScope::~FNodeInstanceDataScope()
{
Context.CurrentNodeDataHandle = SavedNodeDataHandle;
Context.CurrentNodeInstanceData = SavedNodeInstanceData;
Context.CurrentNodeIndex = SavedNodeIndex;
Context.CurrentNode = SavedNode;
}
/**
* FStateTreeExecutionContext implementation
*/
FStateTreeExecutionContext::FStateTreeExecutionContext(UObject& InOwner, const UStateTree& InStateTree, FStateTreeInstanceData& InInstanceData, const FOnCollectStateTreeExternalData& InCollectExternalDataDelegate, const EStateTreeRecordTransitions RecordTransitions)
: FStateTreeExecutionContext(&InOwner, &InStateTree, InInstanceData, InCollectExternalDataDelegate, RecordTransitions)
{
}
FStateTreeExecutionContext::FStateTreeExecutionContext(TNotNull<UObject*> InOwner, TNotNull<const UStateTree*> InStateTree, FStateTreeInstanceData& InInstanceData, const FOnCollectStateTreeExternalData& InCollectExternalDataDelegate, const EStateTreeRecordTransitions RecordTransitions)
: FStateTreeMinimalExecutionContext(InOwner, InStateTree, InInstanceData)
, InstanceData(InInstanceData)
, CollectExternalDataDelegate(InCollectExternalDataDelegate)
{
if (IsValid())
{
// Initialize data views for all possible items.
ContextAndExternalDataViews.SetNum(RootStateTree.GetNumContextDataViews());
EventQueue = InstanceData.GetSharedMutableEventQueue();
bRecordTransitions = RecordTransitions == EStateTreeRecordTransitions::Yes;
}
else
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree asset is not valid ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
}
}
FStateTreeExecutionContext::FStateTreeExecutionContext(const FStateTreeExecutionContext & InContextToCopy, const UStateTree & InStateTree, FStateTreeInstanceData & InInstanceData)
: FStateTreeExecutionContext(InContextToCopy.Owner, InStateTree, InInstanceData, InContextToCopy.CollectExternalDataDelegate)
{
}
FStateTreeExecutionContext::FStateTreeExecutionContext(const FStateTreeExecutionContext& InContextToCopy, TNotNull<const UStateTree*> InStateTree, FStateTreeInstanceData& InInstanceData)
: FStateTreeExecutionContext(&InContextToCopy.Owner, InStateTree, InInstanceData, InContextToCopy.CollectExternalDataDelegate)
{
SetLinkedStateTreeOverrides(InContextToCopy.LinkedAssetStateTreeOverrides);
const bool bIsSameSchema = RootStateTree.GetSchema()->GetClass() == InContextToCopy.GetStateTree()->GetSchema()->GetClass();
if (bIsSameSchema)
{
for (const FStateTreeExternalDataDesc& TargetDataDesc : GetContextDataDescs())
{
const int32 TargetIndex = TargetDataDesc.Handle.DataHandle.GetIndex();
ContextAndExternalDataViews[TargetIndex] = InContextToCopy.ContextAndExternalDataViews[TargetIndex];
}
}
else
{
STATETREE_LOG(Error, TEXT("%hs: '%s' using StateTree '%s' trying to run subtree '%s' but their schemas don't match"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(InContextToCopy.GetStateTree()), *GetFullNameSafe(&RootStateTree));
}
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FStateTreeExecutionContext::~FStateTreeExecutionContext()
{
// Mark external data indices as invalid
FStateTreeExecutionState& Exec = InstanceData.GetMutableStorage().GetMutableExecutionState();
for (FStateTreeExecutionFrame& Frame : Exec.ActiveFrames)
{
Frame.ExternalDataBaseIndex = {};
}
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
void FStateTreeExecutionContext::SetCollectExternalDataCallback(const FOnCollectStateTreeExternalData& Callback)
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
FStateTreeExecutionState& Exec = GetExecState();
if (!ensureMsgf(Exec.CurrentPhase == EStateTreeUpdatePhase::Unset, TEXT("%hs can't be called while already in %s ('%s' using StateTree '%s')."),
__FUNCTION__, *UEnum::GetDisplayValueAsText(Exec.CurrentPhase).ToString(), *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree)))
{
return;
}
CollectExternalDataDelegate = Callback;
}
void FStateTreeExecutionContext::SetLinkedStateTreeOverrides(const FStateTreeReferenceOverrides* InLinkedStateTreeOverrides)
{
if (InLinkedStateTreeOverrides)
{
SetLinkedStateTreeOverrides(*InLinkedStateTreeOverrides);
}
else
{
SetLinkedStateTreeOverrides(FStateTreeReferenceOverrides());
}
}
void FStateTreeExecutionContext::SetLinkedStateTreeOverrides(FStateTreeReferenceOverrides InLinkedStateTreeOverrides)
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
FStateTreeExecutionState& Exec = GetExecState();
if (!ensureMsgf(Exec.CurrentPhase == EStateTreeUpdatePhase::Unset, TEXT("%hs can't be called while already in %s ('%s' using StateTree '%s')."),
__FUNCTION__, *UEnum::GetDisplayValueAsText(Exec.CurrentPhase).ToString(), *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree)))
{
return;
}
bool bValid = true;
// Confirms that the overrides schema matches.
const TConstArrayView<FStateTreeReferenceOverrideItem> InOverrideItems = InLinkedStateTreeOverrides.GetOverrideItems();
for (const FStateTreeReferenceOverrideItem& Item : InOverrideItems)
{
if (const UStateTree* ItemStateTree = Item.GetStateTreeReference().GetStateTree())
{
if (!ItemStateTree->IsReadyToRun())
{
STATETREE_LOG(Error, TEXT("%hs: '%s' using StateTree '%s' trying to set override '%s' but the tree is not initialized properly."),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(GetStateTree()), *GetFullNameSafe(ItemStateTree));
bValid = false;
break;
}
if (!RootStateTree.HasCompatibleContextData(*ItemStateTree))
{
STATETREE_LOG(Error, TEXT("%hs: '%s' using StateTree '%s' trying to set override '%s' but the tree context data is not compatible."),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(GetStateTree()), *GetFullNameSafe(ItemStateTree));
bValid = false;
break;
}
const UStateTreeSchema* OverrideSchema = ItemStateTree->GetSchema();
if (ItemStateTree->GetSchema() == nullptr)
{
STATETREE_LOG(Error, TEXT("%hs: '%s' using StateTree '%s' trying to set override '%s' but the tree does not have a schema."),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(GetStateTree()), *GetFullNameSafe(ItemStateTree));
bValid = false;
break;
}
const bool bIsSameSchema = RootStateTree.GetSchema()->GetClass() == OverrideSchema->GetClass();
if (!bIsSameSchema)
{
STATETREE_LOG(Error, TEXT("%hs: '%s' using StateTree '%s' trying to set override '%s' but their schemas don't match."),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(GetStateTree()), *GetFullNameSafe(Item.GetStateTreeReference().GetStateTree()));
bValid = false;
break;
}
}
}
bool bChanged = false;
if (bValid)
{
LinkedAssetStateTreeOverrides = MoveTemp(InLinkedStateTreeOverrides);
bChanged = LinkedAssetStateTreeOverrides.GetOverrideItems().Num() > 0;
}
else if (LinkedAssetStateTreeOverrides.GetOverrideItems().Num() > 0)
{
LinkedAssetStateTreeOverrides.Reset();
bChanged = true;
}
if (bChanged)
{
TInstancedStruct<FStateTreeExecutionExtension>& ExecutionExtension = Storage.GetMutableExecutionState().ExecutionExtension;
if (ExecutionExtension.IsValid())
{
ExecutionExtension.GetMutable().OnLinkedStateTreeOverridesSet(FStateTreeExecutionExtension::FContextParameters(Owner, RootStateTree, Storage), LinkedAssetStateTreeOverrides);
}
}
}
const FStateTreeReference* FStateTreeExecutionContext::GetLinkedStateTreeOverrideForTag(const FGameplayTag StateTag) const
{
for (const FStateTreeReferenceOverrideItem& Item : LinkedAssetStateTreeOverrides.GetOverrideItems())
{
if (StateTag.MatchesTag(Item.GetStateTag()))
{
return &Item.GetStateTreeReference();
}
}
return nullptr;
}
bool FStateTreeExecutionContext::FExternalGlobalParameters::Add(const FPropertyBindingCopyInfo& Copy, uint8* InParameterMemory)
{
const int32 TypeHash = HashCombine(GetTypeHash(Copy.SourceLeafProperty), GetTypeHash(Copy.SourceIndirection));
const int32 NumMappings = Mappings.Num();
Mappings.Add(TypeHash, InParameterMemory);
return Mappings.Num() > NumMappings;
}
uint8* FStateTreeExecutionContext::FExternalGlobalParameters::Find(const FPropertyBindingCopyInfo& Copy) const
{
const int32 TypeHash = HashCombine(GetTypeHash(Copy.SourceLeafProperty), GetTypeHash(Copy.SourceIndirection));
if(uint8* const* MappingPtr = Mappings.Find(TypeHash))
{
return *MappingPtr;
}
checkf(false, TEXT("Missing external parameter data"));
return nullptr;
}
void FStateTreeExecutionContext::FExternalGlobalParameters::Reset()
{
Mappings.Reset();
}
void FStateTreeExecutionContext::SetExternalGlobalParameters(const FExternalGlobalParameters* Parameters)
{
ExternalGlobalParameters = Parameters;
}
bool FStateTreeExecutionContext::AreContextDataViewsValid() const
{
if (!IsValid())
{
return false;
}
bool bResult = true;
for (const FStateTreeExternalDataDesc& DataDesc : RootStateTree.GetContextDataDescs())
{
const FStateTreeDataView& DataView = ContextAndExternalDataViews[DataDesc.Handle.DataHandle.GetIndex()];
// Required items must have valid pointer of the expected type.
if (DataDesc.Requirement == EStateTreeExternalDataRequirement::Required)
{
if (!DataView.IsValid() || !DataDesc.IsCompatibleWith(DataView))
{
bResult = false;
break;
}
}
else // Optional items must have the expected type if they are set.
{
if (DataView.IsValid() && !DataDesc.IsCompatibleWith(DataView))
{
bResult = false;
break;
}
}
}
return bResult;
}
bool FStateTreeExecutionContext::SetContextDataByName(const FName Name, FStateTreeDataView DataView)
{
const FStateTreeExternalDataDesc* Desc = RootStateTree.GetContextDataDescs().FindByPredicate([&Name](const FStateTreeExternalDataDesc& Desc)
{
return Desc.Name == Name;
});
if (Desc)
{
ContextAndExternalDataViews[Desc->Handle.DataHandle.GetIndex()] = DataView;
return true;
}
return false;
}
FStateTreeDataView FStateTreeExecutionContext::GetContextDataByName(const FName Name) const
{
const FStateTreeExternalDataDesc* Desc = RootStateTree.GetContextDataDescs().FindByPredicate([&Name](const FStateTreeExternalDataDesc& Desc)
{
return Desc.Name == Name;
});
if (Desc)
{
return ContextAndExternalDataViews[Desc->Handle.DataHandle.GetIndex()];
}
return FStateTreeDataView();
}
FStateTreeWeakExecutionContext FStateTreeExecutionContext::MakeWeakExecutionContext() const
{
return FStateTreeWeakExecutionContext(*this);
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FStateTreeWeakTaskRef FStateTreeExecutionContext::MakeWeakTaskRef(const FStateTreeTaskBase& Node) const
{
// This function has been deprecated
check(CurrentNode == &Node);
return MakeWeakTaskRefInternal();
}
FStateTreeWeakTaskRef FStateTreeExecutionContext::MakeWeakTaskRefInternal() const
{
// This function has been deprecated
FStateTreeWeakTaskRef Result;
if (const FStateTreeExecutionFrame* Frame = GetCurrentlyProcessedFrame())
{
if (Frame->StateTree->Nodes.IsValidIndex(CurrentNodeIndex)
&& Frame->StateTree->Nodes[CurrentNodeIndex].GetPtr<const FStateTreeTaskBase>() != nullptr)
{
Result = FStateTreeWeakTaskRef(Frame->StateTree, FStateTreeIndex16(CurrentNodeIndex));
}
}
return Result;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
EStateTreeRunStatus FStateTreeExecutionContext::Start(const FInstancedPropertyBag* InitialParameters, int32 RandomSeed)
{
const TOptional<int32> ParamRandomSeed = RandomSeed == -1 ? TOptional<int32>() : RandomSeed;
return Start(FStartParameters{.GlobalParameters = InitialParameters, .RandomSeed = ParamRandomSeed });
}
void FStateTreeExecutionContext::SetUpdatePhaseInExecutionState(FStateTreeExecutionState& ExecutionState, const EStateTreeUpdatePhase UpdatePhase) const
{
if (ExecutionState.CurrentPhase == UpdatePhase)
{
return;
}
if (ExecutionState.CurrentPhase != EStateTreeUpdatePhase::Unset)
{
UE_STATETREE_DEBUG_EXIT_PHASE(this, ExecutionState.CurrentPhase);
}
ExecutionState.CurrentPhase = UpdatePhase;
if (ExecutionState.CurrentPhase != EStateTreeUpdatePhase::Unset)
{
UE_STATETREE_DEBUG_ENTER_PHASE(this, ExecutionState.CurrentPhase);
}
}
EStateTreeRunStatus FStateTreeExecutionContext::Start(FStartParameters Parameters)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Start);
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return EStateTreeRunStatus::Failed;
}
FStateTreeExecutionState& Exec = GetExecState();
if (!ensureMsgf(Exec.CurrentPhase == EStateTreeUpdatePhase::Unset, TEXT("%hs can't be called while already in %s ('%s' using StateTree '%s')."),
__FUNCTION__, *UEnum::GetDisplayValueAsText(Exec.CurrentPhase).ToString(), *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree)))
{
return EStateTreeRunStatus::Failed;
}
// Stop if still running previous state.
if (Exec.TreeRunStatus == EStateTreeRunStatus::Running)
{
Stop();
}
// Initialize instance data. No active states yet, so we'll initialize the evals and global tasks.
InstanceData.Reset();
Storage.GetRuntimeValidation().SetContext(&Owner, &RootStateTree);
Exec.ExecutionExtension = MoveTemp(Parameters.ExecutionExtension);
if (Parameters.SharedEventQueue)
{
InstanceData.SetSharedEventQueue(Parameters.SharedEventQueue.ToSharedRef());
}
#if WITH_STATETREE_TRACE
// Make sure the debug id is valid. We want to construct it with the current GetInstanceDescriptionInternal
GetInstanceDebugId();
#endif
if (!Parameters.GlobalParameters || !SetGlobalParameters(*Parameters.GlobalParameters))
{
SetGlobalParameters(RootStateTree.GetDefaultParameters());
}
TGuardValue<bool> ScheduledNextTickScope(bAllowedToScheduleNextTick, false);
// Initialize for the init frame.
ensure(Exec.ActiveFrames.Num() == 0);
FStateTreeExecutionFrame& InitFrame = Exec.ActiveFrames.AddDefaulted_GetRef();
InitFrame.FrameID = UE::StateTree::FActiveFrameID(Storage.GenerateUniqueId());
InitFrame.StateTree = &RootStateTree;
InitFrame.RootState = FStateTreeStateHandle::Root;
InitFrame.ActiveStates = {};
InitFrame.bIsGlobalFrame = true;
const FCompactStateTreeFrame* FrameInfo = RootStateTree.GetFrameFromHandle(FStateTreeStateHandle::Root);
ensureAlwaysMsgf(FrameInfo, TEXT("The compiled data is invalid. It should contains the information for the root frame."));
InitFrame.ActiveTasksStatus = FrameInfo ? FStateTreeTasksCompletionStatus(*FrameInfo) : FStateTreeTasksCompletionStatus();
UpdateInstanceData({}, Exec.ActiveFrames);
Exec.RandomStream.Initialize(Parameters.RandomSeed.IsSet() ? Parameters.RandomSeed.GetValue() : FPlatformTime::Cycles());
if (!CollectActiveExternalData())
{
STATETREE_LOG(Warning, TEXT("%hs: Failed to collect external data ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return EStateTreeRunStatus::Failed;
}
// Must sent instance creation event first
UE_STATETREE_DEBUG_INSTANCE_EVENT(this, EStateTreeTraceEventType::Push);
STATETREE_LOG(VeryVerbose, TEXT("%hs: Starting State Tree %s on owner '%s'."),
__FUNCTION__, *GetFullNameSafe(&RootStateTree), *GetNameSafe(&Owner));
// From this point any calls to Stop should be deferred.
SetUpdatePhaseInExecutionState(Exec, EStateTreeUpdatePhase::StartTree);
// Start evaluators and global tasks. Fail the execution if any global task fails.
FStateTreeIndex16 LastInitializedTaskIndex;
const EStateTreeRunStatus GlobalTasksRunStatus = StartEvaluatorsAndGlobalTasks(LastInitializedTaskIndex);
if (GlobalTasksRunStatus == EStateTreeRunStatus::Running)
{
// First tick.
// Tasks are not ticked here, since their behavior is that EnterState() (called above) is treated as a tick.
//@todo Check the result of TickEvaluatorsAndGlobalTasks and early exit if it is not running
constexpr bool bTickGlobalTasks = false;
TickEvaluatorsAndGlobalTasks(0.0f, bTickGlobalTasks);
// Initialize to unset running state.
Exec.TreeRunStatus = EStateTreeRunStatus::Running;
Exec.LastTickStatus = EStateTreeRunStatus::Unset;
static const FStateTreeStateHandle RootState = FStateTreeStateHandle::Root;
FStateSelectionResult StateSelectionResult;
if (SelectState(InitFrame, RootState, StateSelectionResult))
{
check(StateSelectionResult.ContainsFrames());
if (StateSelectionResult.GetSelectedFrames().Last().ActiveStates.Last().IsCompletionState())
{
// Transition to a terminal state (succeeded/failed).
STATETREE_LOG(Warning, TEXT("%hs: Tree %s at StateTree start on '%s' using StateTree '%s'."),
__FUNCTION__, StateSelectionResult.GetSelectedFrames().Last().ActiveStates.Last() == FStateTreeStateHandle::Succeeded ? TEXT("succeeded") : TEXT("failed"), *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
Exec.TreeRunStatus = StateSelectionResult.GetSelectedFrames().Last().ActiveStates.Last().ToCompletionStatus();
}
else
{
// Enter state tasks can fail/succeed, treat it same as tick.
FStateTreeTransitionResult Transition;
Transition.TargetState = RootState;
Transition.CurrentRunStatus = Exec.LastTickStatus;
Transition.NextActiveFrames = StateSelectionResult.GetSelectedFrames(); // Enter state will update Exec.ActiveFrames.
Transition.NextActiveFrameEvents = StateSelectionResult.GetFramesStateSelectionEvents();
const EStateTreeRunStatus LastTickStatus = EnterState(Transition);
Exec.LastTickStatus = LastTickStatus;
// Report state completed immediately.
if (Exec.LastTickStatus != EStateTreeRunStatus::Running)
{
StateCompleted();
}
}
}
InstanceData.ResetTemporaryInstances();
if (Exec.LastTickStatus == EStateTreeRunStatus::Unset)
{
// Should not happen. This may happen if initial state could not be selected.
STATETREE_LOG(Error, TEXT("%hs: Failed to select initial state on '%s' using StateTree '%s'. This should not happen, check that the StateTree logic can always select a state at start."),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
Exec.TreeRunStatus = EStateTreeRunStatus::Failed;
}
}
else
{
StopEvaluatorsAndGlobalTasks(GlobalTasksRunStatus, LastInitializedTaskIndex);
STATETREE_LOG(VeryVerbose, TEXT("%hs: Global tasks completed the StateTree %s on start in status '%s'."),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree), *UEnum::GetDisplayValueAsText(GlobalTasksRunStatus).ToString());
// No active states or global tasks anymore, reset frames.
Exec.ActiveFrames.Reset();
RemoveAllDelegateListeners();
// We are not considered as running yet so we only set the status without requiring a stop.
Exec.TreeRunStatus = GlobalTasksRunStatus;
}
// Reset phase since we are now safe to stop and before potentially stopping the instance.
SetUpdatePhaseInExecutionState(Exec, EStateTreeUpdatePhase::Unset);
// Use local for resulting run state since Stop will reset the instance data.
EStateTreeRunStatus Result = Exec.TreeRunStatus;
if (Exec.RequestedStop != EStateTreeRunStatus::Unset)
{
STATETREE_LOG(VeryVerbose, TEXT("Processing Deferred Stop"));
UE_STATETREE_DEBUG_LOG_EVENT(this, Log, TEXT("Processing Deferred Stop"));
Result = Stop(Exec.RequestedStop);
}
return Result;
}
EStateTreeRunStatus FStateTreeExecutionContext::Stop(EStateTreeRunStatus CompletionStatus)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Stop);
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return EStateTreeRunStatus::Failed;
}
if (!CollectActiveExternalData())
{
STATETREE_LOG(Warning, TEXT("%hs: Failed to collect external data ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return EStateTreeRunStatus::Failed;
}
TGuardValue<bool> ScheduledNextTickScope(bAllowedToScheduleNextTick, false);
// Make sure that we return a valid completion status (i.e. Succeeded, Failed or Stopped)
if (CompletionStatus == EStateTreeRunStatus::Unset
|| CompletionStatus == EStateTreeRunStatus::Running)
{
CompletionStatus = EStateTreeRunStatus::Stopped;
}
FStateTreeExecutionState& Exec = GetExecState();
// A reentrant call to Stop or a call from Start or Tick must be deferred.
if (Exec.CurrentPhase != EStateTreeUpdatePhase::Unset)
{
STATETREE_LOG(VeryVerbose, TEXT("Deferring Stop at end of %s"), *UEnum::GetDisplayValueAsText(Exec.CurrentPhase).ToString());
UE_STATETREE_DEBUG_LOG_EVENT(this, Log, TEXT("Deferring Stop at end of %s"), *UEnum::GetDisplayValueAsText(Exec.CurrentPhase).ToString());
Exec.RequestedStop = CompletionStatus;
return EStateTreeRunStatus::Running;
}
// No need to clear on exit since we reset all the instance data before leaving the function.
SetUpdatePhaseInExecutionState(Exec, EStateTreeUpdatePhase::StopTree);
EStateTreeRunStatus Result = Exec.TreeRunStatus;
// Exit states if still in some valid state.
if (Exec.TreeRunStatus == EStateTreeRunStatus::Running)
{
// Transition to Succeeded state.
FStateTreeTransitionResult Transition;
Transition.TargetState = FStateTreeStateHandle::FromCompletionStatus(CompletionStatus);
Transition.CurrentRunStatus = CompletionStatus;
ExitState(Transition);
// No active states or global tasks anymore, reset frames.
Exec.ActiveFrames.Reset();
Result = CompletionStatus;
}
// Trace before resetting the instance data since it is required to provide all the event information
UE_STATETREE_DEBUG_ACTIVE_STATES_EVENT(this, {});
UE_STATETREE_DEBUG_EXIT_PHASE(this, EStateTreeUpdatePhase::StopTree);
UE_STATETREE_DEBUG_INSTANCE_EVENT(this, EStateTreeTraceEventType::Pop);
// Destruct all allocated instance data (does not shrink the buffer). This will invalidate Exec too.
InstanceData.Reset();
// External data needs to be recollected if this exec context is reused.
bActiveExternalDataCollected = false;
return Result;
}
EStateTreeRunStatus FStateTreeExecutionContext::TickPrelude()
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return EStateTreeRunStatus::Failed;
}
if (!CollectActiveExternalData())
{
STATETREE_LOG(Warning, TEXT("%hs: Failed to collect external data ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return EStateTreeRunStatus::Failed;
}
FStateTreeExecutionState& Exec = GetExecState();
// No ticking if the tree is done or stopped.
if (Exec.TreeRunStatus != EStateTreeRunStatus::Running)
{
return Exec.TreeRunStatus;
}
if (!ensureMsgf(Exec.CurrentPhase == EStateTreeUpdatePhase::Unset, TEXT("%hs can't be called while already in %s ('%s' using StateTree '%s')."),
__FUNCTION__, *UEnum::GetDisplayValueAsText(Exec.CurrentPhase).ToString(), *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree)))
{
return EStateTreeRunStatus::Failed;
}
// From this point any calls to Stop should be deferred.
SetUpdatePhaseInExecutionState(Exec, EStateTreeUpdatePhase::TickStateTree);
return EStateTreeRunStatus::Running;
}
EStateTreeRunStatus FStateTreeExecutionContext::TickPostlude()
{
FStateTreeExecutionState& Exec = GetExecState();
// Reset phase since we are now safe to stop.
SetUpdatePhaseInExecutionState(Exec, EStateTreeUpdatePhase::Unset);
// Use local for resulting run state since Stop will reset the instance data.
EStateTreeRunStatus Result = Exec.TreeRunStatus;
if (Exec.RequestedStop != EStateTreeRunStatus::Unset)
{
STATETREE_LOG(VeryVerbose, TEXT("Processing Deferred Stop"));
UE_STATETREE_DEBUG_LOG_EVENT(this, Log, TEXT("Processing Deferred Stop"));
Result = Stop(Exec.RequestedStop);
}
return Result;
}
EStateTreeRunStatus FStateTreeExecutionContext::Tick(const float DeltaTime)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Tick);
TGuardValue<bool> ScheduledNextTickScope(bAllowedToScheduleNextTick, false);
const EStateTreeRunStatus PreludeResult = TickPrelude();
if (PreludeResult != EStateTreeRunStatus::Running)
{
return PreludeResult;
}
TickUpdateTasksInternal(DeltaTime);
TickTriggerTransitionsInternal();
return TickPostlude();
}
EStateTreeRunStatus FStateTreeExecutionContext::TickUpdateTasks(const float DeltaTime)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Tick);
TGuardValue<bool> ScheduledNextTickScope(bAllowedToScheduleNextTick, false);
const EStateTreeRunStatus PreludeResult = TickPrelude();
if (PreludeResult != EStateTreeRunStatus::Running)
{
return PreludeResult;
}
TickUpdateTasksInternal(DeltaTime);
return TickPostlude();
}
EStateTreeRunStatus FStateTreeExecutionContext::TickTriggerTransitions()
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Tick);
TGuardValue<bool> ScheduledNextTickScope(bAllowedToScheduleNextTick, false);
const EStateTreeRunStatus PreludeResult = TickPrelude();
if (PreludeResult != EStateTreeRunStatus::Running)
{
return PreludeResult;
}
TickTriggerTransitionsInternal();
return TickPostlude();
}
void FStateTreeExecutionContext::TickUpdateTasksInternal(float DeltaTime)
{
FStateTreeExecutionState& Exec = GetExecState();
// If stop is requested, do not try to tick tasks.
if (Exec.RequestedStop != EStateTreeRunStatus::Unset)
{
return;
}
// Prevent wrong user input.
DeltaTime = FMath::Max(0.f, DeltaTime);
// Update the delayed transitions.
for (FStateTreeTransitionDelayedState& DelayedState : Exec.DelayedTransitions)
{
DelayedState.TimeLeft -= DeltaTime;
}
const EStateTreeRunStatus PreviousTickStatus = Exec.LastTickStatus;
auto LogRequestStop = [&Exec, this]()
{
if (Exec.RequestedStop != EStateTreeRunStatus::Unset) // -V547
{
UE_STATETREE_DEBUG_LOG_EVENT(this, Log, TEXT("Global tasks completed (%s), stopping the tree"), *UEnum::GetDisplayValueAsText(Exec.RequestedStop).ToString());
STATETREE_LOG(Log, TEXT("Global tasks completed (%s), stopping the tree"), *UEnum::GetDisplayValueAsText(Exec.RequestedStop).ToString());
}
};
auto TickTaskLogic = [&Exec, &LogRequestStop, PreviousTickStatus, this](float DeltaTime)
{
// Tick tasks on active states.
Exec.LastTickStatus = TickTasks(DeltaTime);
// Report state completed immediately (and no global task completes)
if (Exec.LastTickStatus != EStateTreeRunStatus::Running && Exec.RequestedStop == EStateTreeRunStatus::Unset && PreviousTickStatus == EStateTreeRunStatus::Running)
{
StateCompleted();
}
LogRequestStop();
};
if (UE::StateTree::ExecutionContext::Private::bTickGlobalNodesFollowingTreeHierarchy)
{
TickTaskLogic(DeltaTime);
}
else
{
// Tick global evaluators and tasks.
const bool bTickGlobalTasks = true;
const EStateTreeRunStatus EvalAndGlobalTaskStatus = TickEvaluatorsAndGlobalTasks(DeltaTime, bTickGlobalTasks);
if (EvalAndGlobalTaskStatus == EStateTreeRunStatus::Running)
{
if (Exec.LastTickStatus == EStateTreeRunStatus::Running)
{
TickTaskLogic(DeltaTime);
}
}
else
{
using namespace UE::StateTree;
if (ExecutionContext::Private::bGlobalTasksCompleteOwningFrame)
{
// Only set RequestStop if it's the first frame (root)
check(Exec.ActiveFrames.Num() > 0);
const UStateTree* StateTree = Exec.ActiveFrames[0].StateTree;
check(StateTree == &RootStateTree);
const ETaskCompletionStatus GlobalTaskStatus = Exec.ActiveFrames[0].ActiveTasksStatus.GetStatus(StateTree).GetCompletionStatus();
const EStateTreeRunStatus GlobalRunStatus = ExecutionContext::CastToRunStatus(GlobalTaskStatus);
if (GlobalRunStatus != EStateTreeRunStatus::Running)
{
// Note. Exec.RequestedStop default value is Unset.
Exec.RequestedStop = ExecutionContext::GetPriorityRunStatus(Exec.RequestedStop, GlobalRunStatus);
LogRequestStop();
}
}
else
{
// Any completion stops the tree execution.
Exec.RequestedStop = ExecutionContext::GetPriorityRunStatus(Exec.RequestedStop, EvalAndGlobalTaskStatus);
LogRequestStop();
}
}
}
}
void FStateTreeExecutionContext::TickTriggerTransitionsInternal()
{
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::TickTransitions);
FStateTreeExecutionState& Exec = GetExecState();
// If stop is requested, do not try to trigger transitions.
if (Exec.RequestedStop != EStateTreeRunStatus::Unset)
{
return;
}
// Reset the completed subframe counter (for unit-test that do not recreate an execution context between each tick)
TriggerTransitionsFromFrameIndex.Reset();
// The state selection is repeated up to MaxIteration time. This allows failed EnterState() to potentially find a new state immediately.
// This helps event driven StateTrees to not require another event/tick to find a suitable state.
static constexpr int32 MaxIterations = 5;
for (int32 Iter = 0; Iter < MaxIterations; Iter++)
{
ON_SCOPE_EXIT{ InstanceData.ResetTemporaryInstances(); };
// Trigger conditional transitions or state succeed/failed transitions. First tick transition is handled here too.
if (TriggerTransitions())
{
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::ApplyTransitions);
UE_STATETREE_DEBUG_TRANSITION_EVENT(this, NextTransitionSource, EStateTreeTraceEventType::OnTransition);
NextTransitionSource.Reset();
ExitState(NextTransition);
// Tree succeeded or failed.
if (NextTransition.TargetState.IsCompletionState())
{
// Transition to a terminal state (succeeded/failed), or default transition failed.
Exec.TreeRunStatus = NextTransition.TargetState.ToCompletionStatus();
// Stop evaluators and global tasks.
StopEvaluatorsAndGlobalTasks(Exec.TreeRunStatus);
// No active states or global tasks anymore, reset frames.
Exec.ActiveFrames.Reset();
RemoveAllDelegateListeners();
break;
}
// Enter state tasks can fail/succeed, treat it same as tick.
const EStateTreeRunStatus LastTickStatus = EnterState(NextTransition);
NextTransition = FStateTreeTransitionResult();
Exec.LastTickStatus = LastTickStatus;
// Report state completed immediately.
if (Exec.LastTickStatus != EStateTreeRunStatus::Running)
{
StateCompleted();
}
}
// Stop as soon as have found a running state.
if (Exec.LastTickStatus == EStateTreeRunStatus::Running)
{
break;
}
}
}
void FStateTreeExecutionContext::BroadcastDelegate(const FStateTreeDelegateDispatcher& Dispatcher)
{
if (!Dispatcher.IsValid())
{
return;
}
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
const FStateTreeExecutionFrame* CurrentFrame = GetCurrentlyProcessedFrame();
check(CurrentFrame);
GetExecState().DelegateActiveListeners.BroadcastDelegate(Dispatcher, GetExecState());
if (UE::StateTree::ExecutionContext::MarkDelegateAsBroadcasted(Dispatcher, *CurrentFrame, GetMutableInstanceData()->GetMutableStorage()))
{
ScheduleNextTick();
}
}
// Deprecated
bool FStateTreeExecutionContext::AddDelegateListener(const FStateTreeDelegateListener& Listener, FSimpleDelegate Delegate)
{
BindDelegate(Listener, MoveTemp(Delegate));
return true;
}
void FStateTreeExecutionContext::BindDelegate(const FStateTreeDelegateListener& Listener, FSimpleDelegate Delegate)
{
if (!Listener.IsValid())
{
// The listener is not bound to a dispatcher. It will never trigger the delegate.
return;
}
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
const FStateTreeExecutionFrame* CurrentFrame = GetCurrentlyProcessedFrame();
if (CurrentFrame == nullptr)
{
return;
}
const int32 ActiveStateIndex = CurrentFrame->ActiveStates.IndexOfReverse(CurrentlyProcessedState);
const UE::StateTree::FActiveStateID CurrentlyStateID = ActiveStateIndex != INDEX_NONE ? CurrentFrame->ActiveStates.StateIDs[ActiveStateIndex] : UE::StateTree::FActiveStateID::Invalid;
GetExecState().DelegateActiveListeners.Add(Listener, MoveTemp(Delegate), CurrentFrame->FrameID, CurrentlyStateID, FStateTreeIndex16(CurrentNodeDataHandle.GetIndex()));
}
// Deprecated
void FStateTreeExecutionContext::RemoveDelegateListener(const FStateTreeDelegateListener& Listener)
{
UnbindDelegate(Listener);
}
void FStateTreeExecutionContext::UnbindDelegate(const FStateTreeDelegateListener& Listener)
{
if (!Listener.IsValid())
{
return;
}
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
GetExecState().DelegateActiveListeners.Remove(Listener);
}
void FStateTreeExecutionContext::RequestTransition(const FStateTreeTransitionRequest& Request)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_RequestTransition);
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
FStateTreeExecutionState& Exec = GetExecState();
if (bAllowDirectTransitions)
{
checkf(CurrentlyProcessedFrame, TEXT("Expecting CurrentlyProcessedFrame to be valid when called during TriggerTransitions()."));
STATETREE_LOG(Verbose, TEXT("Request transition to '%s' at priority %s"), *GetSafeStateName(*CurrentlyProcessedFrame, Request.TargetState), *UEnum::GetDisplayValueAsText(Request.Priority).ToString());
if (RequestTransition(*CurrentlyProcessedFrame, Request.TargetState, Request.Priority, /*TransitionEvent*/nullptr, Request.Fallback))
{
NextTransitionSource = FStateTreeTransitionSource(CurrentlyProcessedFrame->StateTree, EStateTreeTransitionSourceType::ExternalRequest, Request.TargetState, Request.Priority);
}
}
else
{
const FStateTreeExecutionFrame* RootFrame = &Exec.ActiveFrames[0];
if (CurrentlyProcessedFrame)
{
RootFrame = CurrentlyProcessedFrame;
}
if (!RootFrame)
{
STATETREE_LOG(Warning, TEXT("%hs: RequestTransition called on %s using StateTree %s without active state. Start() must be called before requesting transition."),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
STATETREE_LOG(Verbose, TEXT("Request transition to '%s' at priority %s"), *GetSafeStateName(*RootFrame, Request.TargetState), *UEnum::GetDisplayValueAsText(Request.Priority).ToString());
FStateTreeTransitionRequest RequestWithSource = Request;
RequestWithSource.SourceFrameID = RootFrame->FrameID;
const int32 ActiveStateIndex = RootFrame->ActiveStates.IndexOfReverse(CurrentlyProcessedState);
RequestWithSource.SourceStateID = ActiveStateIndex != INDEX_NONE ? RootFrame->ActiveStates.StateIDs[ActiveStateIndex] : UE::StateTree::FActiveStateID::Invalid;
InstanceData.AddTransitionRequest(&Owner, RequestWithSource);
}
ScheduleNextTick();
}
void FStateTreeExecutionContext::RequestTransition(FStateTreeStateHandle InTargetState, EStateTreeTransitionPriority InPriority, EStateTreeSelectionFallback InFallback)
{
RequestTransition(FStateTreeTransitionRequest(InTargetState, InPriority, InFallback));
}
void FStateTreeExecutionContext::FinishTask(const FStateTreeTaskBase& Task, EStateTreeFinishTaskType FinishType)
{
using namespace UE::StateTree;
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return;
}
// Like GetInstanceData, only accept task if we are currently processing.
if (!ensure(CurrentNode == &Task))
{
return;
}
check(CurrentlyProcessedFrame);
check(CurrentNodeIndex >= 0);
FStateTreeExecutionState& Exec = GetExecState();
const UStateTree* CurrentStateTree = CurrentlyProcessedFrame->StateTree;
const ETaskCompletionStatus TaskStatus = ExecutionContext::CastToTaskStatus(FinishType);
if (CurrentlyProcessedState.IsValid())
{
check(CurrentStateTree->States.IsValidIndex(CurrentlyProcessedState.Index));
const FCompactStateTreeState& State = CurrentStateTree->States[CurrentlyProcessedState.Index];
const int32 ActiveStateIndex = CurrentlyProcessedFrame->ActiveStates.IndexOfReverse(CurrentlyProcessedState);
check(ActiveStateIndex != INDEX_NONE);
const int32 StateTaskIndex = CurrentNodeIndex - State.TasksBegin;
check(StateTaskIndex >= 0);
FTasksCompletionStatus StateTasksStatus = const_cast<FStateTreeExecutionFrame*>(CurrentlyProcessedFrame)->ActiveTasksStatus.GetStatus(State);
StateTasksStatus.SetStatusWithPriority(StateTaskIndex, TaskStatus);
Exec.bHasPendingCompletedState = Exec.bHasPendingCompletedState || StateTasksStatus.IsCompleted();
}
else
{
// global frame
const int32 FrameTaskIndex = CurrentNodeIndex - CurrentStateTree->GlobalTasksBegin;
check(FrameTaskIndex >= 0);
FTasksCompletionStatus GlobalTasksStatus = const_cast<FStateTreeExecutionFrame*>(CurrentlyProcessedFrame)->ActiveTasksStatus.GetStatus(CurrentStateTree);
GlobalTasksStatus.SetStatusWithPriority(FrameTaskIndex, TaskStatus);
Exec.bHasPendingCompletedState = Exec.bHasPendingCompletedState || GlobalTasksStatus.IsCompleted();
}
}
// Deprecated
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void FStateTreeExecutionContext::FinishTask(const UE::StateTree::FFinishedTask& Task, EStateTreeFinishTaskType FinishType)
{
FStateTreeExecutionState& Exec = GetExecState();
FStateTreeExecutionFrame* Frame = Exec.FindActiveFrame(Task.FrameID);
if (Frame == nullptr)
{
return;
}
using namespace UE::StateTree;
const UE::StateTree::ETaskCompletionStatus Status = ExecutionContext::CastToTaskStatus(Task.RunStatus);
if (Task.Reason == FFinishedTask::EReasonType::GlobalTask)
{
if (Frame->bIsGlobalFrame)
{
Frame->ActiveTasksStatus.GetStatus(Frame->StateTree).SetStatusWithPriority(Task.TaskIndex.AsInt32(), Status);
}
}
else
{
const int32 FoundIndex = Frame->ActiveStates.IndexOfReverse(Task.StateID);
if (FoundIndex != INDEX_NONE)
{
const FStateTreeStateHandle StateHandle = Frame->ActiveStates[FoundIndex];
const FCompactStateTreeState* State = Frame->StateTree->GetStateFromHandle(StateHandle);
if (State != nullptr)
{
if (Task.Reason == FFinishedTask::EReasonType::InternalTransition)
{
Frame->ActiveTasksStatus.GetStatus(*State).SetCompletionStatus(Status);
}
else
{
check(Task.Reason == FFinishedTask::EReasonType::StateTask);
Frame->ActiveTasksStatus.GetStatus(*State).SetStatusWithPriority(Task.TaskIndex.AsInt32(), Status);
}
}
}
}
}
// Deprecated
bool FStateTreeExecutionContext::IsFinishedTaskValid(const UE::StateTree::FFinishedTask& Task) const
{
return false;
}
// Deprecated
void FStateTreeExecutionContext::UpdateCompletedStateList()
{
}
// Deprecated
void FStateTreeExecutionContext::MarkStateCompleted(UE::StateTree::FFinishedTask& NewFinishedTask)
{
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
void FStateTreeExecutionContext::UpdateInstanceData(TConstArrayView<FStateTreeExecutionFrame> CurrentActiveFrames, TArrayView<FStateTreeExecutionFrame> NextActiveFrames)
{
// Estimate how many new instance data items we might have.
int32 EstimatedNumStructs = 0;
for (int32 FrameIndex = 0; FrameIndex < NextActiveFrames.Num(); FrameIndex++)
{
const FStateTreeExecutionFrame& NextFrame = NextActiveFrames[FrameIndex];
if (NextFrame.bIsGlobalFrame)
{
EstimatedNumStructs += NextFrame.StateTree->NumGlobalInstanceData;
}
// States
for (int32 StateIndex = 0; StateIndex < NextFrame.ActiveStates.Num(); StateIndex++)
{
const FStateTreeStateHandle StateHandle = NextFrame.ActiveStates[StateIndex];
const FCompactStateTreeState& State = NextFrame.StateTree->States[StateHandle.Index];
EstimatedNumStructs += State.InstanceDataNum;
}
}
TArray<FConstStructView, FNonconcurrentLinearArrayAllocator> InstanceStructs;
InstanceStructs.Reserve(EstimatedNumStructs);
TArray<FInstancedStruct*, FNonconcurrentLinearArrayAllocator> TempInstanceStructs;
TempInstanceStructs.Reserve(EstimatedNumStructs);
TArray<FCompactStateTreeParameters, TFixedAllocator<FStateSelectionResult::MaxExecutionFrames>> TempParams;
TArrayView<FStateTreeTemporaryInstanceData> TempInstances = Storage.GetMutableTemporaryInstances();
auto FindInstanceTempData = [&TempInstances](const FStateTreeExecutionFrame& Frame, FStateTreeDataHandle DataHandle)
{
FStateTreeTemporaryInstanceData* TempData = TempInstances.FindByPredicate([&Frame, &DataHandle](const FStateTreeTemporaryInstanceData& Data)
{
return Data.FrameID == Frame.FrameID && Data.DataHandle == DataHandle;
});
return TempData ? &TempData->Instance : nullptr;
};
// Find next instance data sources and find common/existing section of instance data at start.
int32 CurrentGlobalInstanceIndexBase = 0;
int32 NumCommonInstanceData = 0;
const UStruct* NextStateParameterDataStruct = nullptr;
FStateTreeDataHandle NextStateParameterDataHandle = FStateTreeDataHandle::Invalid;
FStateTreeDataHandle CurrentGlobalParameterDataHandle = FStateTreeDataHandle(EStateTreeDataSourceType::GlobalParameterData);
bool bAreCommon = true;
for (int32 FrameIndex = 0; FrameIndex < NextActiveFrames.Num(); FrameIndex++)
{
const bool bIsCurrentFrameValid = CurrentActiveFrames.IsValidIndex(FrameIndex)
&& CurrentActiveFrames[FrameIndex].IsSameFrame(NextActiveFrames[FrameIndex]);
bAreCommon &= bIsCurrentFrameValid;
const FStateTreeExecutionFrame* CurrentFrame = bIsCurrentFrameValid ? &CurrentActiveFrames[FrameIndex] : nullptr;
FStateTreeExecutionFrame& NextFrame = NextActiveFrames[FrameIndex];
check(NextFrame.StateTree);
if (NextFrame.bIsGlobalFrame)
{
// Handle global tree parameters
if (NextStateParameterDataHandle.IsValid())
{
// Point to the parameter block set by linked state.
check(NextStateParameterDataStruct == NextFrame.StateTree->GetDefaultParameters().GetPropertyBagStruct());
CurrentGlobalParameterDataHandle = NextStateParameterDataHandle;
NextStateParameterDataHandle = FStateTreeDataHandle::Invalid; // Mark as used.
}
// Global Evals
const int32 BaseIndex = InstanceStructs.Num();
CurrentGlobalInstanceIndexBase = BaseIndex;
InstanceStructs.AddDefaulted(NextFrame.StateTree->NumGlobalInstanceData);
TempInstanceStructs.AddZeroed(NextFrame.StateTree->NumGlobalInstanceData);
for (int32 EvalIndex = NextFrame.StateTree->EvaluatorsBegin; EvalIndex < (NextFrame.StateTree->EvaluatorsBegin + NextFrame.StateTree->EvaluatorsNum); EvalIndex++)
{
const FStateTreeEvaluatorBase& Eval = NextFrame.StateTree->Nodes[EvalIndex].Get<const FStateTreeEvaluatorBase>();
const FConstStructView EvalInstanceData = NextFrame.StateTree->DefaultInstanceData.GetStruct(Eval.InstanceTemplateIndex.Get());
InstanceStructs[BaseIndex + Eval.InstanceDataHandle.GetIndex()] = EvalInstanceData;
if (!bAreCommon)
{
TempInstanceStructs[BaseIndex + Eval.InstanceDataHandle.GetIndex()] = FindInstanceTempData(NextFrame, Eval.InstanceDataHandle);
}
}
// Global tasks
for (int32 TaskIndex = NextFrame.StateTree->GlobalTasksBegin; TaskIndex < (NextFrame.StateTree->GlobalTasksBegin + NextFrame.StateTree->GlobalTasksNum); TaskIndex++)
{
const FStateTreeTaskBase& Task = NextFrame.StateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
const FConstStructView TaskInstanceData = NextFrame.StateTree->DefaultInstanceData.GetStruct(Task.InstanceTemplateIndex.Get());
InstanceStructs[BaseIndex + Task.InstanceDataHandle.GetIndex()] = TaskInstanceData;
if (!bAreCommon)
{
TempInstanceStructs[BaseIndex + Task.InstanceDataHandle.GetIndex()] = FindInstanceTempData(NextFrame, Task.InstanceDataHandle);
}
}
if (bAreCommon)
{
NumCommonInstanceData = InstanceStructs.Num();
}
}
// States
const int32 BaseIndex = InstanceStructs.Num();
NextFrame.GlobalParameterDataHandle = CurrentGlobalParameterDataHandle;
NextFrame.GlobalInstanceIndexBase = FStateTreeIndex16(CurrentGlobalInstanceIndexBase);
NextFrame.ActiveInstanceIndexBase = FStateTreeIndex16(BaseIndex);
for (int32 StateIndex = 0; StateIndex < NextFrame.ActiveStates.Num(); StateIndex++)
{
// Check if the next state is still same as current state, GetStateSafe() will return invalid state if passed out of bounds index.
bAreCommon = bAreCommon && (CurrentFrame && CurrentFrame->ActiveStates.GetStateSafe(StateIndex) == NextFrame.ActiveStates[StateIndex]);
const FStateTreeStateHandle StateHandle = NextFrame.ActiveStates[StateIndex];
const FCompactStateTreeState& State = NextFrame.StateTree->States[StateHandle.Index];
InstanceStructs.AddDefaulted(State.InstanceDataNum);
TempInstanceStructs.AddZeroed(State.InstanceDataNum);
bool bCanHaveTempData = false;
if (State.Type == EStateTreeStateType::Subtree)
{
check(State.ParameterDataHandle.IsValid());
check(State.ParameterTemplateIndex.IsValid());
const FConstStructView ParamsInstanceData = NextFrame.StateTree->DefaultInstanceData.GetStruct(State.ParameterTemplateIndex.Get());
if (!NextStateParameterDataHandle.IsValid())
{
// Parameters are not set by a linked state, create instance data.
InstanceStructs[BaseIndex + State.ParameterDataHandle.GetIndex()] = ParamsInstanceData;
NextFrame.StateParameterDataHandle = State.ParameterDataHandle;
bCanHaveTempData = true;
}
else
{
// Point to the parameter block set by linked state.
const FCompactStateTreeParameters* Params = ParamsInstanceData.GetPtr<const FCompactStateTreeParameters>();
const UStruct* StateParameterDataStruct = Params ? Params->Parameters.GetPropertyBagStruct() : nullptr;
check(NextStateParameterDataStruct == StateParameterDataStruct);
NextFrame.StateParameterDataHandle = NextStateParameterDataHandle;
NextStateParameterDataHandle = FStateTreeDataHandle::Invalid; // Mark as used.
// This state will not instantiate parameter data, so we don't care about the temp data either.
bCanHaveTempData = false;
}
}
else
{
if (State.ParameterTemplateIndex.IsValid())
{
// Linked state's instance data is the parameters.
check(State.ParameterDataHandle.IsValid());
const FCompactStateTreeParameters* Params = nullptr;
if (FInstancedStruct* TempParamsInstanceData = FindInstanceTempData(NextFrame, State.ParameterDataHandle))
{
// If we have temp data for the parameters, then setup the instance data with just a type, so that we can steal the temp data below (TempInstanceStructs).
// We expect overridden linked assets to hit this code path.
InstanceStructs[BaseIndex + State.ParameterDataHandle.GetIndex()] = FConstStructView(TempParamsInstanceData->GetScriptStruct());
Params = TempParamsInstanceData->GetPtr<const FCompactStateTreeParameters>();
bCanHaveTempData = true;
}
else
{
// If not temp data, use the states or linked assets default values.
FConstStructView ParamsInstanceData;
if (State.Type == EStateTreeStateType::LinkedAsset)
{
// This state is a container for the linked state tree.Its instance data matches the linked state tree parameters.The linked state tree asset is the next frame.
const bool bIsLastFrame = FrameIndex == NextActiveFrames.Num() - 1;
if (!bIsLastFrame)
{
FStateTreeExecutionFrame& FollowingNextFrame = NextActiveFrames[FrameIndex + 1];
ParamsInstanceData = FConstStructView::Make(TempParams.Emplace_GetRef(FollowingNextFrame.StateTree->GetDefaultParameters()));
}
}
if (!ParamsInstanceData.IsValid())
{
ParamsInstanceData = NextFrame.StateTree->DefaultInstanceData.GetStruct(State.ParameterTemplateIndex.Get());
}
InstanceStructs[BaseIndex + State.ParameterDataHandle.GetIndex()] = ParamsInstanceData;
Params = ParamsInstanceData.GetPtr<const FCompactStateTreeParameters>();
bCanHaveTempData = true;
}
if (State.Type == EStateTreeStateType::Linked
|| State.Type == EStateTreeStateType::LinkedAsset)
{
// Store the index of the parameter data, so that we can point the linked state to it.
check(State.ParameterDataHandle.GetSource() == EStateTreeDataSourceType::StateParameterData);
checkf(!NextStateParameterDataHandle.IsValid(), TEXT("NextStateParameterDataIndex not should be set yet when we encounter a linked state."));
NextStateParameterDataHandle = State.ParameterDataHandle;
NextStateParameterDataStruct = Params ? Params->Parameters.GetPropertyBagStruct() : nullptr;
}
}
}
if (!bAreCommon && bCanHaveTempData)
{
TempInstanceStructs[BaseIndex + State.ParameterDataHandle.GetIndex()] = FindInstanceTempData(NextFrame, State.ParameterDataHandle);
}
if (State.EventDataIndex.IsValid())
{
InstanceStructs[BaseIndex + State.EventDataIndex.Get()] = FConstStructView(FStateTreeSharedEvent::StaticStruct());
}
for (int32 TaskIndex = State.TasksBegin; TaskIndex < (State.TasksBegin + State.TasksNum); TaskIndex++)
{
const FStateTreeTaskBase& Task = NextFrame.StateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
const FConstStructView TaskInstanceData = NextFrame.StateTree->DefaultInstanceData.GetStruct(Task.InstanceTemplateIndex.Get());
InstanceStructs[BaseIndex + Task.InstanceDataHandle.GetIndex()] = TaskInstanceData;
if (!bAreCommon)
{
TempInstanceStructs[BaseIndex + Task.InstanceDataHandle.GetIndex()] = FindInstanceTempData(NextFrame, Task.InstanceDataHandle);
}
}
if (bAreCommon)
{
NumCommonInstanceData = InstanceStructs.Num();
}
}
}
// Common section should match.
#if WITH_STATETREE_DEBUG
for (int32 Index = 0; Index < NumCommonInstanceData; Index++)
{
check(Index < InstanceData.Num());
FConstStructView ExistingInstanceDataView = InstanceData.GetStruct(Index);
FConstStructView NewInstanceDataView = InstanceStructs[Index];
check(NewInstanceDataView.GetScriptStruct() == ExistingInstanceDataView.GetScriptStruct());
const FStateTreeInstanceObjectWrapper* ExistingWrapper = ExistingInstanceDataView.GetPtr<const FStateTreeInstanceObjectWrapper>();
const FStateTreeInstanceObjectWrapper* NewWrapper = ExistingInstanceDataView.GetPtr<const FStateTreeInstanceObjectWrapper>();
if (ExistingWrapper && NewWrapper)
{
check(ExistingWrapper->InstanceObject && NewWrapper->InstanceObject);
check(ExistingWrapper->InstanceObject->GetClass() == NewWrapper->InstanceObject->GetClass());
}
}
#endif
// Remove instance data that was not common.
InstanceData.ShrinkTo(NumCommonInstanceData);
// Add new instance data.
InstanceData.Append(Owner,
MakeArrayView(InstanceStructs.GetData() + NumCommonInstanceData, InstanceStructs.Num() - NumCommonInstanceData),
MakeArrayView(TempInstanceStructs.GetData() + NumCommonInstanceData, TempInstanceStructs.Num() - NumCommonInstanceData));
InstanceData.ResetTemporaryInstances();
}
FStateTreeDataView FStateTreeExecutionContext::GetDataView(const FStateTreeExecutionFrame* ParentFrame, const FStateTreeExecutionFrame& CurrentFrame, const FStateTreeDataHandle Handle)
{
switch (Handle.GetSource())
{
case EStateTreeDataSourceType::ContextData:
check(!ContextAndExternalDataViews.IsEmpty())
return ContextAndExternalDataViews[Handle.GetIndex()];
case EStateTreeDataSourceType::ExternalData:
check(!ContextAndExternalDataViews.IsEmpty())
return ContextAndExternalDataViews[CurrentFrame.ExternalDataBaseIndex.Get() + Handle.GetIndex()];
case EStateTreeDataSourceType::TransitionEvent:
{
if (CurrentlyProcessedTransitionEvent)
{
// const_cast because events are read only, but we cannot express that in FStateTreeDataView.
return FStateTreeDataView(FStructView::Make(*const_cast<FStateTreeEvent*>(CurrentlyProcessedTransitionEvent)));
}
return nullptr;
}
case EStateTreeDataSourceType::StateEvent:
{
// If state selection is going, return FStateTreeEvent of the event currently captured by the state selection.
if (CurrentlyProcessedStateSelectionEvents)
{
if (const FCompactStateTreeState* State = CurrentFrame.StateTree->GetStateFromHandle(Handle.GetState()))
{
// Events are read only, but we cannot express that in FStateTreeDataView.
if (FStateTreeEvent* Event = CurrentlyProcessedStateSelectionEvents->Events[State->Depth].GetMutable())
{
return FStateTreeDataView(FStructView::Make(*Event));
}
}
return {};
}
return UE::StateTree::InstanceData::GetDataView(Storage, CurrentlyProcessedSharedInstanceStorage, ParentFrame, CurrentFrame, Handle);
}
case EStateTreeDataSourceType::ExternalGlobalParameterData:
{
checkf(false, TEXT("External global parameter data currently not supported for linked state-trees"));
break;
}
default:
return UE::StateTree::InstanceData::GetDataView(Storage, CurrentlyProcessedSharedInstanceStorage, ParentFrame, CurrentFrame, Handle);
}
return {};
}
FStateTreeDataView FStateTreeExecutionContext::GetDataView(const FStateTreeExecutionFrame* ParentFrame, const FStateTreeExecutionFrame& CurrentFrame, const FPropertyBindingCopyInfo& CopyInfo)
{
const FStateTreeDataHandle Handle = CopyInfo.SourceDataHandle.Get<FStateTreeDataHandle>();
if (Handle.GetSource() == EStateTreeDataSourceType::ExternalGlobalParameterData)
{
return GetDataViewOrTemporary(ParentFrame, CurrentFrame, CopyInfo);
}
return GetDataView(ParentFrame, CurrentFrame, Handle);
}
EStateTreeRunStatus FStateTreeExecutionContext::ForceTransition(const FRecordedStateTreeTransitionResult& Transition)
{
if (!IsValid())
{
STATETREE_LOG(Warning, TEXT("%hs: StateTree context is not initialized properly ('%s' using StateTree '%s')"),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return EStateTreeRunStatus::Failed;
}
// A reentrant call to ForceTransition or a call from Start, Tick or Stop must be deferred.
if (GetExecState().CurrentPhase != EStateTreeUpdatePhase::Unset)
{
UE_STATETREE_DEBUG_LOG_EVENT(this, Warning, TEXT("Can't force a transition while %s"), *UEnum::GetDisplayValueAsText(GetExecState().CurrentPhase).ToString());
return EStateTreeRunStatus::Unset;
}
TOptional<FStateTreeTransitionResult> TransitionResult = MakeTransitionResult(Transition);
if (!TransitionResult.IsSet())
{
return EStateTreeRunStatus::Unset;
}
ExitState(TransitionResult.GetValue());
return EnterState(TransitionResult.GetValue());
}
const FStateTreeExecutionFrame* FStateTreeExecutionContext::FindFrame(const UStateTree* StateTree, FStateTreeStateHandle RootState, TConstArrayView<FStateTreeExecutionFrame> Frames, const FStateTreeExecutionFrame*& OutParentFrame)
{
const int32 FrameIndex = Frames.IndexOfByPredicate([&StateTree, RootState](const FStateTreeExecutionFrame& Frame)
{
return Frame.StateTree == StateTree && Frame.RootState == RootState;
});
if (FrameIndex == INDEX_NONE)
{
OutParentFrame = nullptr;
return nullptr;
}
if (FrameIndex > 0)
{
OutParentFrame = &Frames[FrameIndex - 1];
}
return &Frames[FrameIndex];
}
bool FStateTreeExecutionContext::IsHandleSourceValid(const FStateTreeExecutionFrame* ParentFrame, const FStateTreeExecutionFrame& CurrentFrame, const FStateTreeDataHandle Handle) const
{
switch (Handle.GetSource())
{
case EStateTreeDataSourceType::None:
return true;
case EStateTreeDataSourceType::ContextData:
return true;
case EStateTreeDataSourceType::ExternalData:
return CurrentFrame.ExternalDataBaseIndex.IsValid()
&& ContextAndExternalDataViews.IsValidIndex(CurrentFrame.ExternalDataBaseIndex.Get() + Handle.GetIndex());
case EStateTreeDataSourceType::TransitionEvent:
return CurrentlyProcessedTransitionEvent != nullptr;
case EStateTreeDataSourceType::StateEvent:
return CurrentlyProcessedStateSelectionEvents != nullptr
|| (CurrentFrame.ActiveInstanceIndexBase.IsValid()
&& CurrentFrame.ActiveStates.Contains(Handle.GetState())
&& Storage.IsValidIndex(CurrentFrame.ActiveInstanceIndexBase.Get() + Handle.GetIndex()));
case EStateTreeDataSourceType::ExternalGlobalParameterData:
{
checkf(false, TEXT("External global parameter data currently not supported for linked state-trees"));
break;
}
default:
return UE::StateTree::InstanceData::Private::IsHandleSourceValid(Storage, ParentFrame, CurrentFrame, Handle);
}
return false;
}
bool FStateTreeExecutionContext::IsHandleSourceValid(const FStateTreeExecutionFrame* ParentFrame, const FStateTreeExecutionFrame& CurrentFrame, const FPropertyBindingCopyInfo& CopyInfo) const
{
const FStateTreeDataHandle Handle = CopyInfo.SourceDataHandle.Get<FStateTreeDataHandle>();
if (Handle.GetSource() == EStateTreeDataSourceType::ExternalGlobalParameterData)
{
return ExternalGlobalParameters ? ExternalGlobalParameters->Find(CopyInfo) != nullptr : false;
}
return IsHandleSourceValid(ParentFrame, CurrentFrame, Handle);
}
FStateTreeDataView FStateTreeExecutionContext::GetDataViewOrTemporary(const FStateTreeExecutionFrame* ParentFrame, const FStateTreeExecutionFrame& CurrentFrame, const FStateTreeDataHandle Handle)
{
if (IsHandleSourceValid(ParentFrame, CurrentFrame, Handle))
{
return GetDataView(ParentFrame, CurrentFrame, Handle);
}
return GetTemporaryDataView(ParentFrame, CurrentFrame, Handle);
}
FStateTreeDataView FStateTreeExecutionContext::GetDataViewOrTemporary(const FStateTreeExecutionFrame* ParentFrame, const FStateTreeExecutionFrame& CurrentFrame, const FPropertyBindingCopyInfo& CopyInfo)
{
const FStateTreeDataHandle Handle = CopyInfo.SourceDataHandle.Get<FStateTreeDataHandle>();
if (Handle.GetSource() == EStateTreeDataSourceType::ExternalGlobalParameterData)
{
uint8* MemoryPtr = ExternalGlobalParameters->Find(CopyInfo);
return FStateTreeDataView(CopyInfo.SourceStructType, MemoryPtr);
}
return GetDataViewOrTemporary(ParentFrame, CurrentFrame, Handle);
}
FStateTreeDataView FStateTreeExecutionContext::GetTemporaryDataView(const FStateTreeExecutionFrame* ParentFrame,
const FStateTreeExecutionFrame& CurrentFrame, const FStateTreeDataHandle Handle)
{
switch (Handle.GetSource())
{
case EStateTreeDataSourceType::ExternalGlobalParameterData:
checkf(false, TEXT("External global parameter data currently not supported for linked state-trees"));
return {};
default:
return UE::StateTree::InstanceData::Private::GetTemporaryDataView(Storage, ParentFrame, CurrentFrame, Handle);
}
}
FStateTreeDataView FStateTreeExecutionContext::AddTemporaryInstance(const FStateTreeExecutionFrame& Frame, const FStateTreeIndex16 OwnerNodeIndex, const FStateTreeDataHandle DataHandle, FConstStructView NewInstanceData)
{
const FStructView NewInstance = Storage.AddTemporaryInstance(Owner, Frame, OwnerNodeIndex, DataHandle, NewInstanceData);
if (FStateTreeInstanceObjectWrapper* Wrapper = NewInstance.GetPtr<FStateTreeInstanceObjectWrapper>())
{
return FStateTreeDataView(Wrapper->InstanceObject);
}
return NewInstance;
}
bool FStateTreeExecutionContext::CopyBatchOnActiveInstances(const FStateTreeExecutionFrame* ParentFrame, const FStateTreeExecutionFrame& CurrentFrame, const FStateTreeDataView TargetView, const FStateTreeIndex16 BindingsBatch)
{
const FPropertyBindingCopyInfoBatch& Batch = CurrentFrame.StateTree->PropertyBindings.Super::GetBatch(BindingsBatch);
check(TargetView.GetStruct() == Batch.TargetStruct.Get().Struct);
if (Batch.PropertyFunctionsBegin != Batch.PropertyFunctionsEnd)
{
check(Batch.PropertyFunctionsBegin.IsValid() && Batch.PropertyFunctionsEnd.IsValid());
EvaluatePropertyFunctionsOnActiveInstances(ParentFrame, CurrentFrame, FStateTreeIndex16(Batch.PropertyFunctionsBegin), Batch.PropertyFunctionsEnd.Get() - Batch.PropertyFunctionsBegin.Get());
}
bool bSucceed = true;
for (const FPropertyBindingCopyInfo& Copy : CurrentFrame.StateTree->PropertyBindings.Super::GetBatchCopies(Batch))
{
const FStateTreeDataView SourceView = GetDataView(ParentFrame, CurrentFrame, Copy);
bSucceed &= CurrentFrame.StateTree->PropertyBindings.Super::CopyProperty(Copy, SourceView, TargetView);
}
return bSucceed;
}
bool FStateTreeExecutionContext::CopyBatchWithValidation(const FStateTreeExecutionFrame* ParentFrame, const FStateTreeExecutionFrame& CurrentFrame, const FStateTreeDataView TargetView, const FStateTreeIndex16 BindingsBatch)
{
const FPropertyBindingCopyInfoBatch& Batch = CurrentFrame.StateTree->PropertyBindings.Super::GetBatch(BindingsBatch);
check(TargetView.GetStruct() == Batch.TargetStruct.Get().Struct);
if (Batch.PropertyFunctionsBegin != Batch.PropertyFunctionsEnd)
{
check(Batch.PropertyFunctionsBegin.IsValid() && Batch.PropertyFunctionsEnd.IsValid());
EvaluatePropertyFunctionsWithValidation(ParentFrame, CurrentFrame, FStateTreeIndex16(Batch.PropertyFunctionsBegin), Batch.PropertyFunctionsEnd.Get() - Batch.PropertyFunctionsBegin.Get());
}
bool bSucceed = true;
for (const FPropertyBindingCopyInfo& Copy : CurrentFrame.StateTree->PropertyBindings.Super::GetBatchCopies(Batch))
{
const FStateTreeDataView SourceView = GetDataViewOrTemporary(ParentFrame, CurrentFrame, Copy);
if (!SourceView.IsValid())
{
bSucceed = false;
break;
}
bSucceed &= CurrentFrame.StateTree->PropertyBindings.Super::CopyProperty(Copy, SourceView, TargetView);
}
return bSucceed;
}
bool FStateTreeExecutionContext::CollectActiveExternalData()
{
return CollectActiveExternalData(GetExecState().ActiveFrames);
}
bool FStateTreeExecutionContext::CollectActiveExternalData(const TArrayView<FStateTreeExecutionFrame> Frames)
{
if (bActiveExternalDataCollected)
{
return true;
}
bool bAllExternalDataValid = true;
const FStateTreeExecutionFrame* PrevFrame = nullptr;
for (FStateTreeExecutionFrame& Frame : Frames)
{
if (PrevFrame && PrevFrame->StateTree == Frame.StateTree)
{
Frame.ExternalDataBaseIndex = PrevFrame->ExternalDataBaseIndex;
}
else
{
Frame.ExternalDataBaseIndex = CollectExternalData(Frame.StateTree);
}
if (!Frame.ExternalDataBaseIndex.IsValid())
{
bAllExternalDataValid = false;
}
PrevFrame = &Frame;
}
if (bAllExternalDataValid)
{
bActiveExternalDataCollected = true;
}
return bAllExternalDataValid;
}
FStateTreeIndex16 FStateTreeExecutionContext::CollectExternalData(const UStateTree* StateTree)
{
if (!StateTree)
{
return FStateTreeIndex16::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.StateTree == StateTree)
{
return Cache.BaseIndex;
}
}
const TConstArrayView<FStateTreeExternalDataDesc> ExternalDataDescs = StateTree->GetExternalDataDescs();
const int32 BaseIndex = ContextAndExternalDataViews.Num();
const int32 NumDescs = ExternalDataDescs.Num();
FStateTreeIndex16 Result(BaseIndex);
if (NumDescs > 0)
{
ContextAndExternalDataViews.AddDefaulted(NumDescs);
const TArrayView<FStateTreeDataView> DataViews = MakeArrayView(ContextAndExternalDataViews.GetData() + BaseIndex, NumDescs);
if (ensureMsgf(CollectExternalDataDelegate.IsBound(), TEXT("The StateTree asset has external data, expecting CollectExternalData delegate to be provided.")))
{
if (!CollectExternalDataDelegate.Execute(*this, StateTree, StateTree->GetExternalDataDescs(), DataViews))
{
// The caller is responsible for error reporting.
return FStateTreeIndex16::Invalid;
}
}
// Check that the data is valid and present.
for (int32 Index = 0; Index < NumDescs; Index++)
{
const FStateTreeExternalDataDesc& DataDesc = ExternalDataDescs[Index];
const FStateTreeDataView& DataView = ContextAndExternalDataViews[BaseIndex + Index];
if (DataDesc.Requirement == EStateTreeExternalDataRequirement::Required)
{
// Required items must have valid pointer of the expected type.
if (!DataView.IsValid() || !DataDesc.IsCompatibleWith(DataView))
{
Result = FStateTreeIndex16::Invalid;
break;
}
}
else
{
// Optional items must have same type if they are set.
if (DataView.IsValid() && !DataDesc.IsCompatibleWith(DataView))
{
Result = FStateTreeIndex16::Invalid;
break;
}
}
}
}
if (!Result.IsValid())
{
// Rollback
ContextAndExternalDataViews.SetNum(BaseIndex);
}
// Cached both succeeded and failed attempts.
CollectedExternalCache.Add({ StateTree, Result });
return FStateTreeIndex16(Result);
}
bool FStateTreeExecutionContext::SetGlobalParameters(const FInstancedPropertyBag& Parameters)
{
if (ensureMsgf(RootStateTree.GetDefaultParameters().GetPropertyBagStruct() == Parameters.GetPropertyBagStruct(),
TEXT("Parameters must be of the same struct type. Make sure to migrate the provided parameters to the same type as the StateTree default parameters.")))
{
Storage.SetGlobalParameters(Parameters);
return true;
}
return false;
}
void FStateTreeExecutionContext::CaptureNewStateEvents(TConstArrayView<FStateTreeExecutionFrame> PrevFrames, TConstArrayView<FStateTreeExecutionFrame> NewFrames, TArrayView<FStateTreeFrameStateSelectionEvents> FramesStateSelectionEvents)
{
// Mark the events from delayed transitions as in use, so that each State will receive unique copy of the event struct.
TArray<FStateTreeSharedEvent, TInlineAllocator<16>> EventsInUse;
for (const FStateTreeTransitionDelayedState& DelayedTransition : GetExecState().DelayedTransitions)
{
if (DelayedTransition.CapturedEvent.IsValid())
{
EventsInUse.Add(DelayedTransition.CapturedEvent);
}
}
for (int32 FrameIndex = 0; FrameIndex < NewFrames.Num(); ++FrameIndex)
{
const FStateTreeExecutionFrame& NewFrame = NewFrames[FrameIndex];
// Find states that are unique to the new frame.
TConstArrayView<FStateTreeStateHandle> UniqueStates = NewFrame.ActiveStates;
if (PrevFrames.IsValidIndex(FrameIndex))
{
const FStateTreeExecutionFrame& PrevFrame = PrevFrames[FrameIndex];
if (PrevFrame.FrameID == NewFrame.FrameID)
{
checkf(PrevFrame.RootState == NewFrame.RootState && PrevFrame.StateTree == NewFrame.StateTree, TEXT("If the Id matches, then the root and the tree must also match."));
for (int32 StateIndex = 0; StateIndex < NewFrame.ActiveStates.Num(); ++StateIndex)
{
if (!PrevFrame.ActiveStates.IsValidIndex(StateIndex) || PrevFrame.ActiveStates.StateIDs[StateIndex] != NewFrame.ActiveStates.StateIDs[StateIndex])
{
UniqueStates = TConstArrayView<FStateTreeStateHandle>(&NewFrame.ActiveStates[StateIndex], NewFrame.ActiveStates.Num() - StateIndex);
break;
}
}
}
}
// Capture events for the new states.
for (const FStateTreeStateHandle StateHandle : UniqueStates)
{
if (const FCompactStateTreeState* State = NewFrame.StateTree->GetStateFromHandle(StateHandle))
{
if (State->EventDataIndex.IsValid())
{
FStateTreeSharedEvent& StateTreeEvent = Storage.GetMutableStruct(NewFrame.ActiveInstanceIndexBase.Get() + State->EventDataIndex.Get()).Get<FStateTreeSharedEvent>();
const FStateTreeSharedEvent& EventToCapture = FramesStateSelectionEvents[FrameIndex].Events[State->Depth];
if (EventsInUse.Contains(EventToCapture))
{
// Event is already spoken for, make a copy.
StateTreeEvent = FStateTreeSharedEvent(*EventToCapture);
}
else
{
// Event not in use, steal it.
StateTreeEvent = FramesStateSelectionEvents[FrameIndex].Events[State->Depth];
EventsInUse.Add(EventToCapture);
}
}
}
}
}
}
EStateTreeRunStatus FStateTreeExecutionContext::EnterState(FStateTreeTransitionResult& Transition)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_EnterState);
if (Transition.NextActiveFrames.IsEmpty())
{
return EStateTreeRunStatus::Failed;
}
FStateTreeExecutionState& Exec = GetExecState();
if (bRecordTransitions)
{
RecordedTransitions.Add(MakeRecordedTransitionResult(Transition));
}
// Allocate new tasks.
UpdateInstanceData(Exec.ActiveFrames, Transition.NextActiveFrames);
CaptureNewStateEvents(Exec.ActiveFrames, Transition.NextActiveFrames, Transition.NextActiveFrameEvents);
Exec.StateChangeCount++;
Exec.EnterStateFailedFrameIndex = FStateTreeIndex16::Invalid; // This will make all tasks to be accepted.
Exec.EnterStateFailedTaskIndex = FStateTreeIndex16::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;
FStateTreeTransitionResult CurrentTransition = Transition;
EStateTreeRunStatus Result = EStateTreeRunStatus::Running;
STATETREE_LOG(Log, TEXT("Enter state '%s' (%d)"), *DebugGetStatePath(Transition.NextActiveFrames), Exec.StateChangeCount);
UE_STATETREE_DEBUG_ENTER_PHASE(this, EStateTreeUpdatePhase::EnterStates);
// The previous active frames are needed for state enter logic.
TArray<FStateTreeExecutionFrame, FNonconcurrentLinearArrayAllocator> PreviousActiveFrames;
PreviousActiveFrames = Exec.ActiveFrames;
// Reset the current active frames, new ones are added one by one.
Exec.ActiveFrames.Reset();
// Track any changed state (i.e., not sustained) to prevent reused subtrees from being considered as sustained states since the whole
// tree is reused and should be considered as a new instance.
bool bAnyParentStateChanged = false;
for (int32 FrameIndex = 0; FrameIndex < Transition.NextActiveFrames.Num() && Result != EStateTreeRunStatus::Failed; FrameIndex++)
{
const FStateTreeExecutionFrame& NextFrame = Transition.NextActiveFrames[FrameIndex];
FStateTreeExecutionFrame* CurrentParentFrame = !Exec.ActiveFrames.IsEmpty() ? &Exec.ActiveFrames.Last() : nullptr;
FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames.Add_GetRef(NextFrame);
const UStateTree* CurrentStateTree = NextFrame.StateTree;
const UE::StateTree::FActiveFrameID CurrentFrameID = NextFrame.FrameID;
// We'll add new states one by one, so that active states contain only the states which have EnterState called.
CurrentFrame.ActiveStates.Reset();
if (!ensureMsgf(CurrentFrame.ActiveTasksStatus.IsValid(), TEXT("Frame is not formed correct.")))
{
// Create the status. We lost the previous tasks' completion status.
const FCompactStateTreeFrame* FrameInfo = NextFrame.StateTree->GetFrameFromHandle(NextFrame.RootState);
ensureAlwaysMsgf(FrameInfo, TEXT("The compiled data is invalid. It should contains the information for the root frame."));
CurrentFrame.ActiveTasksStatus = FrameInfo ? FStateTreeTasksCompletionStatus(*FrameInfo) : FStateTreeTasksCompletionStatus();
}
// Get previous active states, they are used to calculate transition type.
FStateTreeActiveStates PreviousActiveStates;
if (PreviousActiveFrames.IsValidIndex(FrameIndex)
&& PreviousActiveFrames[FrameIndex].IsSameFrame(NextFrame))
{
PreviousActiveStates = PreviousActiveFrames[FrameIndex].ActiveStates;
}
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
for (int32 StateIndex = 0; StateIndex < NextFrame.ActiveStates.Num() && Result != EStateTreeRunStatus::Failed; StateIndex++)
{
const FStateTreeStateHandle CurrentHandle = NextFrame.ActiveStates[StateIndex];
const UE::StateTree::FActiveStateID CurrentStateID = NextFrame.ActiveStates.StateIDs[StateIndex];
const FStateTreeStateHandle PreviousHandle = PreviousActiveStates.GetStateSafe(StateIndex);
const FCompactStateTreeState& State = CurrentStateTree->States[CurrentHandle.Index];
// Add only enabled States to the list of active States
if (State.bEnabled && !CurrentFrame.ActiveStates.Push(CurrentHandle, NextFrame.ActiveStates.StateIDs[StateIndex]))
{
STATETREE_LOG(Error, TEXT("%hs: Reached max execution depth when trying to enter state '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
break;
}
//@todo push the same state as previously handle
CurrentFrame.ActiveTasksStatus.Push(State);
UE::StateTree::FTasksCompletionStatus CurrentStateTasksStatus = CurrentFrame.ActiveTasksStatus.GetStatus(State);
CurrentFrame.NumCurrentlyActiveStates = static_cast<uint8>(CurrentFrame.ActiveStates.Num());
FCurrentlyProcessedStateScope StateScope(*this, CurrentHandle);
if (State.Type == EStateTreeStateType::Linked
|| State.Type == EStateTreeStateType::LinkedAsset)
{
if (State.ParameterDataHandle.IsValid()
&& State.ParameterBindingsBatch.IsValid())
{
const FStateTreeDataView StateParamsDataView = GetDataView(CurrentParentFrame, CurrentFrame, State.ParameterDataHandle);
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, StateParamsDataView, State.ParameterBindingsBatch);
}
}
if (CurrentFrame.FrameID == Transition.SourceFrameID
&& CurrentHandle == Transition.TargetState)
{
bOnTargetBranch = true;
}
const bool bWasActive = (PreviousHandle == CurrentHandle) && !bAnyParentStateChanged;
const EStateTreeStateChangeType ChangeType = bWasActive ? EStateTreeStateChangeType::Sustained : EStateTreeStateChangeType::Changed;
if (ChangeType == EStateTreeStateChangeType::Changed)
{
bAnyParentStateChanged = true;
}
CurrentTransition.CurrentState = CurrentHandle;
CurrentTransition.ChangeType = ChangeType;
// Do not enter a disabled State tasks but maintain property bindings
const bool bIsEnteringState = (!bWasActive || bOnTargetBranch) && State.bEnabled;
if (bIsEnteringState)
{
UE_STATETREE_DEBUG_STATE_EVENT(this, CurrentHandle, EStateTreeTraceEventType::OnEntering);
STATETREE_LOG(Log, TEXT("%*sState '%s' (%s)"), (FrameIndex + StateIndex + 1)*UE::StateTree::Debug::IndentSize, TEXT("")
, *GetSafeStateName(NextFrame, CurrentHandle)
, *UEnum::GetDisplayValueAsText(CurrentTransition.ChangeType).ToString());
}
// Call state change events on conditions if needed.
if (bIsEnteringState && State.bHasStateChangeConditions)
{
for (int32 ConditionIndex = State.EnterConditionsBegin; ConditionIndex < (State.EnterConditionsBegin + State.EnterConditionsNum); ConditionIndex++)
{
const FStateTreeConditionBase& Cond = CurrentFrame.StateTree->Nodes[ConditionIndex].Get<const FStateTreeConditionBase>();
if (Cond.bHasShouldCallStateChangeEvents)
{
const bool bShouldCallEnterState = CurrentTransition.ChangeType == EStateTreeStateChangeType::Changed
|| (CurrentTransition.ChangeType == EStateTreeStateChangeType::Sustained && Cond.bShouldStateChangeOnReselect);
if (bShouldCallEnterState)
{
const FStateTreeDataView ConditionInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Cond.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Cond, ConditionIndex, Cond.InstanceDataHandle, ConditionInstanceView);
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).
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, ConditionInstanceView, Cond.BindingsBatch);
}
UE_STATETREE_DEBUG_CONDITION_ENTER_STATE(this, CurrentFrame.StateTree, FStateTreeIndex16(ConditionIndex));
Cond.EnterState(*this, Transition);
// Reset copied properties that might contain object references.
if (Cond.BindingsBatch.IsValid())
{
CurrentFrame.StateTree->PropertyBindings.Super::ResetObjects(Cond.BindingsBatch, ConditionInstanceView);
}
}
}
}
}
// Activate tasks on current state.
for (int32 StateTaskIndex = 0; StateTaskIndex < State.TasksNum; ++StateTaskIndex)
{
const int32 AssetTaskIndex = State.TasksBegin + StateTaskIndex;
const FStateTreeTaskBase& Task = NextFrame.StateTree->Nodes[AssetTaskIndex].Get<const FStateTreeTaskBase>();
const FStateTreeDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Task, AssetTaskIndex, Task.InstanceDataHandle, TaskInstanceView);
// Copy bound properties.
if (Task.BindingsBatch.IsValid())
{
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
}
// Ignore disabled task
if (Task.bTaskEnabled == false)
{
STATETREE_LOG(VeryVerbose, TEXT("%*sSkipped 'EnterState' for disabled Task: '%s'"), UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
continue;
}
const bool bShouldCallEnterState = CurrentTransition.ChangeType == EStateTreeStateChangeType::Changed
|| (CurrentTransition.ChangeType == EStateTreeStateChangeType::Sustained && Task.bShouldStateChangeOnReselect);
if (bIsEnteringState && bShouldCallEnterState)
{
STATETREE_LOG(Verbose, TEXT("%*sTask '%s'.EnterState()"), (FrameIndex + StateIndex + 1)*UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
UE_STATETREE_DEBUG_TASK_ENTER_STATE(this, NextFrame.StateTree, FStateTreeIndex16(AssetTaskIndex));
EStateTreeRunStatus TaskRunStatus = EStateTreeRunStatus::Unset;
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Task_EnterState);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Task_EnterState);
TaskRunStatus = Task.EnterState(*this, CurrentTransition);
}
UE::StateTree::ETaskCompletionStatus TaskStatus = UE::StateTree::ExecutionContext::CastToTaskStatus(TaskRunStatus);
TaskStatus = CurrentStateTasksStatus.SetStatusWithPriority(StateTaskIndex, TaskStatus);
TaskRunStatus = UE::StateTree::ExecutionContext::CastToRunStatus(TaskStatus);
UE_STATETREE_DEBUG_TASK_EVENT(this, AssetTaskIndex, TaskInstanceView, EStateTreeTraceEventType::OnEntered, TaskRunStatus);
Result = UE::StateTree::ExecutionContext::GetPriorityRunStatus(Result, TaskRunStatus);
if (TaskRunStatus == EStateTreeRunStatus::Failed && CurrentStateTasksStatus.IsConsideredForCompletion(StateTaskIndex))
{
Exec.EnterStateFailedFrameIndex = FStateTreeIndex16(FrameIndex);
Exec.EnterStateFailedTaskIndex = FStateTreeIndex16(AssetTaskIndex);
break;
}
}
}
if (bIsEnteringState)
{
UE_STATETREE_DEBUG_STATE_EVENT(this, CurrentHandle, EStateTreeTraceEventType::OnEntered);
}
}
}
UE_STATETREE_DEBUG_EXIT_PHASE(this, EStateTreeUpdatePhase::EnterStates);
UE_STATETREE_DEBUG_ACTIVE_STATES_EVENT(this, Exec.ActiveFrames);
return Result;
}
void FStateTreeExecutionContext::ExitState(const FStateTreeTransitionResult& Transition)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_ExitState);
check(!GetExecState().LastExitedNodeIndex.IsValid());
ON_SCOPE_EXIT { GetExecState().LastExitedNodeIndex = FStateTreeIndex16::Invalid; };
FStateTreeExecutionState& 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 EStateTreeStateChangeType InChangeType, const bool bInShouldCall)
: ChangeType(InChangeType)
, bShouldCall(bInShouldCall)
{
}
EStateTreeStateChangeType ChangeType = EStateTreeStateChangeType::None;
bool bShouldCall = false;
};
TArray<FExitStateCall, FNonconcurrentLinearArrayAllocator> ExitStateCalls;
// Track any changed state (i.e., not sustained) to prevent reused subtrees from being considered as sustained states since the whole
// tree is reused and should be considered as a new instance.
bool bAnyParentStateChanged = false;
int32 FirstModifiedFrame = INDEX_NONE;
int32 FirstModifiedState = INDEX_NONE;
for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); FrameIndex++)
{
FStateTreeExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
const FStateTreeExecutionFrame* NextFrame = nullptr;
if (Transition.NextActiveFrames.IsValidIndex(FrameIndex)
&& Transition.NextActiveFrames[FrameIndex].IsSameFrame(CurrentFrame))
{
NextFrame = &Transition.NextActiveFrames[FrameIndex];
}
const bool bShouldCallOnEvaluatorsAndGlobalTasks = NextFrame == nullptr && CurrentFrame.bIsGlobalFrame;
ExitStateCalls.Emplace(EStateTreeStateChangeType::Changed, bShouldCallOnEvaluatorsAndGlobalTasks);
if (bShouldCallOnEvaluatorsAndGlobalTasks)
{
for (int32 EvalIndex = CurrentStateTree->EvaluatorsBegin; EvalIndex < (CurrentStateTree->EvaluatorsBegin + CurrentStateTree->EvaluatorsNum); EvalIndex++)
{
const FStateTreeEvaluatorBase& Eval = CurrentStateTree->Nodes[EvalIndex].Get<const FStateTreeEvaluatorBase>();
const FStateTreeDataView EvalInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Eval.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Eval, EvalIndex, Eval.InstanceDataHandle, EvalInstanceView);
if (Eval.BindingsBatch.IsValid())
{
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, EvalInstanceView, Eval.BindingsBatch);
}
}
for (int32 TaskIndex = CurrentStateTree->GlobalTasksBegin; TaskIndex < (CurrentStateTree->GlobalTasksBegin + CurrentStateTree->GlobalTasksNum); TaskIndex++)
{
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
const FStateTreeDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Task, TaskIndex, Task.InstanceDataHandle, TaskInstanceView);
if (Task.BindingsBatch.IsValid() && Task.bShouldCopyBoundPropertiesOnExitState)
{
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
}
}
}
for (int32 Index = 0; Index < CurrentFrame.ActiveStates.Num(); Index++)
{
const FStateTreeStateHandle CurrentHandle = CurrentFrame.ActiveStates[Index];
const FStateTreeStateHandle NextHandle = NextFrame ? NextFrame->ActiveStates.GetStateSafe(Index) : FStateTreeStateHandle::Invalid;
const FCompactStateTreeState& State = CurrentStateTree->States[CurrentHandle.Index];
FCurrentlyProcessedStateScope StateScope(*this, CurrentHandle);
if (State.Type == EStateTreeStateType::Linked
|| State.Type == EStateTreeStateType::LinkedAsset)
{
if (State.ParameterDataHandle.IsValid()
&& State.ParameterBindingsBatch.IsValid())
{
const FStateTreeDataView StateParamsDataView = GetDataView(CurrentParentFrame, CurrentFrame, State.ParameterDataHandle);
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, StateParamsDataView, State.ParameterBindingsBatch);
}
}
if (CurrentFrame.FrameID == Transition.SourceFrameID
&& CurrentHandle == Transition.TargetState)
{
bOnTargetBranch = true;
}
const bool bRemainsActive = (NextHandle == CurrentHandle) && !bAnyParentStateChanged;
const EStateTreeStateChangeType ChangeType = bRemainsActive ? EStateTreeStateChangeType::Sustained : EStateTreeStateChangeType::Changed;
if (ChangeType == EStateTreeStateChangeType::Changed)
{
bAnyParentStateChanged = true;
}
// Should call ExitState() on this state.
// (No need to test for 'State.bEnabled' like EnterState since we can't enter a disabled state)
const bool bShouldCallExitState = (!bRemainsActive || bOnTargetBranch);
ExitStateCalls.Emplace(ChangeType, bShouldCallExitState);
if (bShouldCallExitState && FirstModifiedFrame == INDEX_NONE)
{
FirstModifiedFrame = FrameIndex;
FirstModifiedState = Index;
}
// Do property copies, ExitState() is called below.
for (int32 TaskIndex = State.TasksBegin; TaskIndex < (State.TasksBegin + State.TasksNum); TaskIndex++)
{
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
const FStateTreeDataView 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.
STATETREE_LOG(Log, TEXT("Exit state '%s' (%d)"), *DebugGetStatePath(Exec.ActiveFrames), Exec.StateChangeCount);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::ExitStates);
FStateTreeTransitionResult CurrentTransition = Transition;
int32 CallIndex = ExitStateCalls.Num() - 1;
for (int32 FrameIndex = Exec.ActiveFrames.Num() - 1; FrameIndex >= 0; FrameIndex--)
{
FStateTreeExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
for (int32 StateIndex = CurrentFrame.ActiveStates.Num() - 1; StateIndex >= 0; StateIndex--)
{
const FStateTreeStateHandle CurrentHandle = CurrentFrame.ActiveStates[StateIndex];
const UE::StateTree::FActiveStateID CurrentStateID = CurrentFrame.ActiveStates.StateIDs[StateIndex];
const FCompactStateTreeState& State = CurrentStateTree->States[CurrentHandle.Index];
const FExitStateCall& ExitCall = ExitStateCalls[CallIndex--];
CurrentTransition.ChangeType = ExitCall.ChangeType;
STATETREE_LOG(Log, TEXT("%*sState '%s' (%s)"), (FrameIndex + StateIndex + 1)*UE::StateTree::Debug::IndentSize, TEXT("")
, *GetSafeStateName(CurrentFrame, CurrentHandle)
, *UEnum::GetDisplayValueAsText(CurrentTransition.ChangeType).ToString());
UE_STATETREE_DEBUG_STATE_EVENT(this, CurrentHandle, EStateTreeTraceEventType::OnExiting);
if (ExitCall.bShouldCall)
{
FCurrentlyProcessedStateScope StateScope(*this, CurrentHandle);
// Remove any delayed transitions that belong to this state.
Exec.DelayedTransitions.RemoveAllSwap(
[CurrentStateID, Begin = State.TransitionsBegin, End = State.TransitionsBegin + State.TransitionsNum](const FStateTreeTransitionDelayedState& DelayedState)
{
return DelayedState.StateID == CurrentStateID
&& 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 FStateTreeTaskBase& Task = CurrentStateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
const FStateTreeDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Task, TaskIndex, Task.InstanceDataHandle, TaskInstanceView);
// Ignore disabled task
if (Task.bTaskEnabled == false)
{
STATETREE_LOG(VeryVerbose, TEXT("%*sSkipped 'ExitState' for disabled Task: '%s'"), UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
continue;
}
const bool bShouldCallStateChange = CurrentTransition.ChangeType == EStateTreeStateChangeType::Changed
|| (CurrentTransition.ChangeType == EStateTreeStateChangeType::Sustained && Task.bShouldStateChangeOnReselect);
if (bShouldCallStateChange)
{
STATETREE_LOG(Verbose, TEXT("%*sTask '%s'.ExitState()"), (FrameIndex + StateIndex + 1)*UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
UE_STATETREE_DEBUG_TASK_EXIT_STATE(this, CurrentStateTree, FStateTreeIndex16(TaskIndex));
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Task_ExitState);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Task_ExitState);
Task.ExitState(*this, CurrentTransition);
}
UE_STATETREE_DEBUG_TASK_EVENT(this, TaskIndex, TaskInstanceView, EStateTreeTraceEventType::OnExited, Transition.CurrentRunStatus);
GetExecState().LastExitedNodeIndex = FStateTreeIndex16(TaskIndex);
}
}
}
// Call state change events on conditions if needed.
if (State.bHasStateChangeConditions)
{
for (int32 ConditionIndex = (State.EnterConditionsBegin + State.EnterConditionsNum) - 1; ConditionIndex >= State.EnterConditionsBegin; ConditionIndex--)
{
const FStateTreeConditionBase& Cond = CurrentFrame.StateTree->Nodes[ConditionIndex].Get<const FStateTreeConditionBase>();
if (Cond.bHasShouldCallStateChangeEvents)
{
const bool bShouldCallStateChange = CurrentTransition.ChangeType == EStateTreeStateChangeType::Changed
|| (CurrentTransition.ChangeType == EStateTreeStateChangeType::Sustained && Cond.bShouldStateChangeOnReselect);
if (bShouldCallStateChange)
{
const FStateTreeDataView ConditionInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Cond.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Cond, ConditionIndex, Cond.InstanceDataHandle, ConditionInstanceView);
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).
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, ConditionInstanceView, Cond.BindingsBatch);
}
UE_STATETREE_DEBUG_CONDITION_EXIT_STATE(this, CurrentFrame.StateTree, FStateTreeIndex16(ConditionIndex));
Cond.ExitState(*this, Transition);
// Reset copied properties that might contain object references.
if (Cond.BindingsBatch.IsValid())
{
CurrentFrame.StateTree->PropertyBindings.Super::ResetObjects(Cond.BindingsBatch, ConditionInstanceView);
}
}
}
}
}
// Delegate Listeners Cleanup
GetExecState().DelegateActiveListeners.RemoveAll(CurrentFrame.ActiveStates.StateIDs[StateIndex]);
}
UE_STATETREE_DEBUG_STATE_EVENT(this, CurrentHandle, EStateTreeTraceEventType::OnExited);
}
// Frame exit call
{
const FExitStateCall& ExitCall = ExitStateCalls[CallIndex--];
if (ExitCall.bShouldCall)
{
CurrentTransition.ChangeType = ExitCall.ChangeType;
CallStopOnEvaluatorsAndGlobalTasks(CurrentParentFrame, CurrentFrame, CurrentTransition);
// Delegate Listeners Cleanup
GetExecState().DelegateActiveListeners.RemoveAll(CurrentFrame.FrameID);
}
}
}
}
void FStateTreeExecutionContext::RemoveAllDelegateListeners()
{
GetExecState().DelegateActiveListeners = FStateTreeDelegateActiveListeners();
}
void FStateTreeExecutionContext::StateCompleted()
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_StateCompleted);
const FStateTreeExecutionState& Exec = GetExecState();
if (Exec.ActiveFrames.IsEmpty())
{
return;
}
STATETREE_LOG(Verbose, TEXT("State Completed %s (%d)"), *UEnum::GetDisplayValueAsText(Exec.LastTickStatus).ToString(), Exec.StateChangeCount);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::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 FStateTreeExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
const FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
if (FrameIndex <= Exec.EnterStateFailedFrameIndex.Get())
{
for (int32 StateIndex = CurrentFrame.ActiveStates.Num() - 1; StateIndex >= 0; StateIndex--)
{
const FStateTreeStateHandle CurrentHandle = CurrentFrame.ActiveStates[StateIndex];
const FCompactStateTreeState& State = CurrentStateTree->States[CurrentHandle.Index];
FCurrentlyProcessedStateScope StateScope(*this, CurrentHandle);
UE_STATETREE_DEBUG_STATE_EVENT(this, CurrentHandle, EStateTreeTraceEventType::OnStateCompleted);
STATETREE_LOG(Verbose, TEXT("%*sState '%s'"), (FrameIndex + StateIndex + 1)*UE::StateTree::Debug::IndentSize, TEXT(""), *GetSafeStateName(CurrentFrame, CurrentHandle));
// 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 FStateTreeTaskBase& Task = CurrentStateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
const FStateTreeDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Task, TaskIndex, Task.InstanceDataHandle, TaskInstanceView);
// Ignore disabled task
if (Task.bTaskEnabled == false)
{
STATETREE_LOG(VeryVerbose, TEXT("%*sSkipped 'StateCompleted' for disabled Task: '%s'"), UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
continue;
}
STATETREE_LOG(Verbose, TEXT("%*sTask '%s'.StateCompleted()"), (FrameIndex + StateIndex + 1)*UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
Task.StateCompleted(*this, Exec.LastTickStatus, CurrentFrame.ActiveStates);
}
}
// Call state change events on conditions if needed.
if (State.bHasStateChangeConditions)
{
for (int32 ConditionIndex = (State.EnterConditionsBegin + State.EnterConditionsNum) - 1; ConditionIndex >= State.EnterConditionsBegin; ConditionIndex--)
{
const FStateTreeConditionBase& Cond = CurrentFrame.StateTree->Nodes[ConditionIndex].Get<const FStateTreeConditionBase>();
if (Cond.bHasShouldCallStateChangeEvents)
{
const FStateTreeDataView ConditionInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Cond.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Cond, ConditionIndex, Cond.InstanceDataHandle, ConditionInstanceView);
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).
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, ConditionInstanceView, Cond.BindingsBatch);
}
Cond.StateCompleted(*this, Exec.LastTickStatus, CurrentFrame.ActiveStates);
// Reset copied properties that might contain object references.
if (Cond.BindingsBatch.IsValid())
{
CurrentFrame.StateTree->PropertyBindings.Super::ResetObjects(Cond.BindingsBatch, ConditionInstanceView);
}
}
}
}
}
}
}
}
EStateTreeRunStatus FStateTreeExecutionContext::TickEvaluatorsAndGlobalTasks(const float DeltaTime, const bool bTickGlobalTasks)
{
// When a global task is completed it completes the tree execution.
// A global task can complete async. See CompletedStates.
// When a global task fails, stop ticking the following tasks.
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_TickEvaluators);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::TickingGlobalTasks);
STATETREE_LOG(VeryVerbose, TEXT("Ticking Evaluators & Global Tasks"));
FStateTreeExecutionState& Exec = GetExecState();
EStateTreeRunStatus Result = EStateTreeRunStatus::Running;
for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); ++FrameIndex)
{
FStateTreeExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
if (CurrentFrame.bIsGlobalFrame)
{
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
const EStateTreeRunStatus FrameResult = TickEvaluatorsAndGlobalTasksForFrame(DeltaTime, bTickGlobalTasks, FrameIndex, CurrentParentFrame, &CurrentFrame);
Result = UE::StateTree::ExecutionContext::GetPriorityRunStatus(Result, FrameResult);
if (Result == EStateTreeRunStatus::Failed)
{
break;
}
}
}
Exec.bHasPendingCompletedState = Exec.bHasPendingCompletedState || Result != EStateTreeRunStatus::Running;
return Result;
}
EStateTreeRunStatus FStateTreeExecutionContext::TickEvaluatorsAndGlobalTasksForFrame(const float DeltaTime, const bool bTickGlobalTasks, const int32 FrameIndex, const FStateTreeExecutionFrame* CurrentParentFrame, const TNotNull<FStateTreeExecutionFrame*> CurrentFrame)
{
check(CurrentFrame->bIsGlobalFrame);
EStateTreeRunStatus Result = EStateTreeRunStatus::Running;
const UStateTree* CurrentStateTree = CurrentFrame->StateTree;
// Tick evaluators
for (int32 EvalIndex = CurrentStateTree->EvaluatorsBegin; EvalIndex < (CurrentStateTree->EvaluatorsBegin + CurrentStateTree->EvaluatorsNum); EvalIndex++)
{
const FStateTreeEvaluatorBase& Eval = CurrentStateTree->Nodes[EvalIndex].Get<const FStateTreeEvaluatorBase>();
const FStateTreeDataView EvalInstanceView = GetDataView(CurrentParentFrame, *CurrentFrame, Eval.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Eval, EvalIndex, Eval.InstanceDataHandle, EvalInstanceView);
// Copy bound properties.
if (Eval.BindingsBatch.IsValid())
{
CopyBatchOnActiveInstances(CurrentParentFrame, *CurrentFrame, EvalInstanceView, Eval.BindingsBatch);
}
STATETREE_LOG(VeryVerbose, TEXT(" Tick: '%s'"), *Eval.Name.ToString());
UE_STATETREE_DEBUG_EVALUATOR_TICK(this, CurrentStateTree, EvalIndex);
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Eval_Tick);
Eval.Tick(*this, DeltaTime);
UE_STATETREE_DEBUG_EVALUATOR_EVENT(this, EvalIndex, EvalInstanceView, EStateTreeTraceEventType::OnTicked);
}
}
if (bTickGlobalTasks)
{
using namespace UE::StateTree;
FTasksCompletionStatus CurrentGlobalTasksStatus = CurrentFrame->ActiveTasksStatus.GetStatus(CurrentStateTree);
if (!CurrentGlobalTasksStatus.HasAnyFailed())
{
const bool bHasEvents = EventQueue && EventQueue->HasEvents();
if (ExecutionContext::Private::bCopyBoundPropertiesOnNonTickedTask || CurrentStateTree->ShouldTickGlobalTasks(bHasEvents))
{
// Update Tasks data and tick if possible (ie. if no task has yet failed and bShouldTickTasks is true)
FTickTaskArguments TickArgs;
TickArgs.DeltaTime = DeltaTime;
TickArgs.TasksBegin = CurrentStateTree->GlobalTasksBegin;
TickArgs.TasksNum = CurrentStateTree->GlobalTasksNum;
TickArgs.Indent = FrameIndex + 1;
TickArgs.ParentFrame = CurrentParentFrame;
TickArgs.Frame = CurrentFrame;
TickArgs.TasksCompletionStatus = &CurrentGlobalTasksStatus;
TickArgs.bIsGlobalTasks = true;
TickArgs.bShouldTickTasks = true;
TickTasks(TickArgs);
}
}
// Completed global task stops the frame execution.
const ETaskCompletionStatus GlobalTaskStatus = CurrentGlobalTasksStatus.GetCompletionStatus();
Result = ExecutionContext::CastToRunStatus(GlobalTaskStatus);
}
return Result;
}
EStateTreeRunStatus FStateTreeExecutionContext::StartEvaluatorsAndGlobalTasks(FStateTreeIndex16& OutLastInitializedTaskIndex)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_StartEvaluators);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::StartGlobalTasks);
STATETREE_LOG(Verbose, TEXT("Start Evaluators & Global tasks"));
FStateTreeExecutionState& Exec = GetExecState();
OutLastInitializedTaskIndex = FStateTreeIndex16();
EStateTreeRunStatus Result = EStateTreeRunStatus::Running;
for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); FrameIndex++)
{
FStateTreeExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UE::StateTree::FActiveFrameID CurrentFrameID = CurrentFrame.FrameID;
if (CurrentFrame.bIsGlobalFrame)
{
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
UE::StateTree::FTasksCompletionStatus CurrentGlobalTasksStatus = CurrentFrame.ActiveTasksStatus.GetStatus(CurrentStateTree);
// Start evaluators
for (int32 EvalIndex = CurrentStateTree->EvaluatorsBegin; EvalIndex < (CurrentStateTree->EvaluatorsBegin + CurrentStateTree->EvaluatorsNum); EvalIndex++)
{
const FStateTreeEvaluatorBase& Eval = CurrentStateTree->Nodes[EvalIndex].Get<const FStateTreeEvaluatorBase>();
const FStateTreeDataView EvalInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Eval.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Eval, EvalIndex, Eval.InstanceDataHandle, EvalInstanceView);
// Copy bound properties.
if (Eval.BindingsBatch.IsValid())
{
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, EvalInstanceView, Eval.BindingsBatch);
}
STATETREE_LOG(Verbose, TEXT(" Start: '%s'"), *Eval.Name.ToString());
UE_STATETREE_DEBUG_EVALUATOR_ENTER_TREE(this, CurrentStateTree, FStateTreeIndex16(EvalIndex));
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Eval_TreeStart);
Eval.TreeStart(*this);
UE_STATETREE_DEBUG_EVALUATOR_EVENT(this, EvalIndex, EvalInstanceView, EStateTreeTraceEventType::OnTreeStarted);
}
}
// Start Global tasks
// Even if we call Enter/ExitState() on global tasks, they do not enter any specific state.
const FStateTreeTransitionResult Transition = {}; // Empty transition
for (int32 GlobalTaskIndex = 0; GlobalTaskIndex < CurrentStateTree->GlobalTasksNum; ++GlobalTaskIndex)
{
const int32 AssetTaskIndex = CurrentStateTree->GlobalTasksBegin + GlobalTaskIndex;
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[AssetTaskIndex].Get<const FStateTreeTaskBase>();
const FStateTreeDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Task, AssetTaskIndex, Task.InstanceDataHandle, TaskInstanceView);
// Copy bound properties.
if (Task.BindingsBatch.IsValid())
{
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
}
// Ignore disabled task
if (Task.bTaskEnabled == false)
{
STATETREE_LOG(VeryVerbose, TEXT("%*sSkipped 'EnterState' for disabled Task: '%s'"), UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
continue;
}
STATETREE_LOG(Verbose, TEXT(" Start: '%s'"), *Task.Name.ToString());
UE_STATETREE_DEBUG_TASK_ENTER_STATE(this, CurrentStateTree, FStateTreeIndex16(AssetTaskIndex));
EStateTreeRunStatus TaskRunStatus = EStateTreeRunStatus::Unset;
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Task_TreeStart);
TaskRunStatus = Task.EnterState(*this, Transition);
}
UE::StateTree::ETaskCompletionStatus TaskStatus = UE::StateTree::ExecutionContext::CastToTaskStatus(TaskRunStatus);
TaskStatus = CurrentGlobalTasksStatus.SetStatusWithPriority(GlobalTaskIndex, TaskStatus);
TaskRunStatus = UE::StateTree::ExecutionContext::CastToRunStatus(TaskStatus);
UE_STATETREE_DEBUG_TASK_EVENT(this, AssetTaskIndex, TaskInstanceView, EStateTreeTraceEventType::OnEntered, TaskRunStatus);
Result = UE::StateTree::ExecutionContext::GetPriorityRunStatus(Result, TaskRunStatus);
if (TaskRunStatus == EStateTreeRunStatus::Failed && CurrentGlobalTasksStatus.IsConsideredForCompletion(GlobalTaskIndex))
{
OutLastInitializedTaskIndex = FStateTreeIndex16(AssetTaskIndex);
}
}
}
}
return Result;
}
void FStateTreeExecutionContext::StopEvaluatorsAndGlobalTasks(const EStateTreeRunStatus CompletionStatus, const FStateTreeIndex16 LastInitializedTaskIndex)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_StopEvaluators);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::StopGlobalTasks);
STATETREE_LOG(Verbose, TEXT("Stop Evaluators & Global Tasks"));
FStateTreeExecutionState& Exec = GetExecState();
// Update bindings
for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); FrameIndex++)
{
FStateTreeExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
if (CurrentFrame.bIsGlobalFrame)
{
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
for (int32 EvalIndex = CurrentStateTree->EvaluatorsBegin; EvalIndex < (CurrentStateTree->EvaluatorsBegin + CurrentStateTree->EvaluatorsNum); EvalIndex++)
{
const FStateTreeEvaluatorBase& Eval = CurrentStateTree->Nodes[EvalIndex].Get<const FStateTreeEvaluatorBase>();
const FStateTreeDataView EvalInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Eval.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Eval, EvalIndex, Eval.InstanceDataHandle, EvalInstanceView);
// Copy bound properties.
if (Eval.BindingsBatch.IsValid())
{
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, EvalInstanceView, Eval.BindingsBatch);
}
}
for (int32 TaskIndex = CurrentStateTree->GlobalTasksBegin; TaskIndex < (CurrentStateTree->GlobalTasksBegin + CurrentStateTree->GlobalTasksNum); TaskIndex++)
{
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
const FStateTreeDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Task, TaskIndex, Task.InstanceDataHandle, TaskInstanceView);
// Copy bound properties.
if (Task.BindingsBatch.IsValid() && Task.bShouldCopyBoundPropertiesOnExitState)
{
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
}
}
}
}
// Call in reverse order.
FStateTreeTransitionResult Transition;
Transition.TargetState = FStateTreeStateHandle::FromCompletionStatus(CompletionStatus);
Transition.CurrentRunStatus = CompletionStatus;
bool bIsLastGlobalFrame = true;
for (int32 FrameIndex = Exec.ActiveFrames.Num() - 1; FrameIndex >= 0; FrameIndex--)
{
const FStateTreeExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
const FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
if (CurrentFrame.bIsGlobalFrame)
{
// LastInitializedTaskIndex belongs to the last frame.
const FStateTreeIndex16 LastTaskToBeStopped = bIsLastGlobalFrame ? LastInitializedTaskIndex : FStateTreeIndex16::Invalid;
CallStopOnEvaluatorsAndGlobalTasks(CurrentParentFrame, CurrentFrame, Transition, LastTaskToBeStopped);
bIsLastGlobalFrame = false;
}
}
}
void FStateTreeExecutionContext::CallStopOnEvaluatorsAndGlobalTasks(const FStateTreeExecutionFrame* ParentFrame, const FStateTreeExecutionFrame& Frame, const FStateTreeTransitionResult& Transition, const FStateTreeIndex16 LastInitializedTaskIndex /*= FStateTreeIndex16()*/)
{
check(Frame.bIsGlobalFrame);
ON_SCOPE_EXIT { GetExecState().LastExitedNodeIndex = FStateTreeIndex16::Invalid; };
FCurrentlyProcessedFrameScope FrameScope(*this, ParentFrame, Frame);
const UStateTree* CurrentStateTree = Frame.StateTree;
for (int32 TaskIndex = (CurrentStateTree->GlobalTasksBegin + CurrentStateTree->GlobalTasksNum) - 1; TaskIndex >= CurrentStateTree->GlobalTasksBegin; TaskIndex--)
{
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
const FStateTreeDataView TaskInstanceView = GetDataView(ParentFrame, Frame, Task.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Task, TaskIndex, Task.InstanceDataHandle, TaskInstanceView);
// Ignore disabled task
if (Task.bTaskEnabled == false)
{
STATETREE_LOG(VeryVerbose, TEXT("%*sSkipped 'ExitState' for disabled Task: '%s'"), UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
continue;
}
// Relying here that invalid value of LastInitializedTaskIndex == MAX_uint16.
if (TaskIndex <= LastInitializedTaskIndex.Get())
{
STATETREE_LOG(Verbose, TEXT(" Stop: '%s'"), *Task.Name.ToString());
UE_STATETREE_DEBUG_TASK_EXIT_STATE(this, CurrentStateTree, FStateTreeIndex16(TaskIndex));
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Task_TreeStop);
Task.ExitState(*this, Transition);
}
UE_STATETREE_DEBUG_TASK_EVENT(this, TaskIndex, TaskInstanceView, EStateTreeTraceEventType::OnExited, Transition.CurrentRunStatus);
GetExecState().LastExitedNodeIndex = FStateTreeIndex16(TaskIndex);
}
}
for (int32 EvalIndex = (CurrentStateTree->EvaluatorsBegin + CurrentStateTree->EvaluatorsNum) - 1; EvalIndex >= CurrentStateTree->EvaluatorsBegin; EvalIndex--)
{
const FStateTreeEvaluatorBase& Eval = CurrentStateTree->Nodes[EvalIndex].Get<const FStateTreeEvaluatorBase>();
const FStateTreeDataView EvalInstanceView = GetDataView(ParentFrame, Frame, Eval.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Eval, EvalIndex, Eval.InstanceDataHandle, EvalInstanceView);
STATETREE_LOG(Verbose, TEXT(" Stop: '%s'"), *Eval.Name.ToString());
UE_STATETREE_DEBUG_EVALUATOR_EXIT_TREE(this, CurrentStateTree, FStateTreeIndex16(EvalIndex));
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Eval_TreeStop);
Eval.TreeStop(*this);
UE_STATETREE_DEBUG_EVALUATOR_EVENT(this, EvalIndex, EvalInstanceView, EStateTreeTraceEventType::OnTreeStopped);
}
GetExecState().LastExitedNodeIndex = FStateTreeIndex16(EvalIndex);
}
}
EStateTreeRunStatus FStateTreeExecutionContext::StartTemporaryEvaluatorsAndGlobalTasks(const FStateTreeExecutionFrame* CurrentParentFrame, FStateTreeExecutionFrame& CurrentFrame)
{
if (!CurrentFrame.bIsGlobalFrame)
{
return EStateTreeRunStatus::Failed;
}
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_StartEvaluators);
STATETREE_LOG(Verbose, TEXT("Start Temporary Evaluators & Global tasks while trying to select linked asset: %s"), *GetNameSafe(CurrentFrame.StateTree));
FStateTreeExecutionState& Exec = GetExecState();
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::StartGlobalTasksForSelection);
EStateTreeRunStatus Result = EStateTreeRunStatus::Running;
const UE::StateTree::FActiveFrameID CurrentFrameID = CurrentFrame.FrameID;
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
UE::StateTree::FTasksCompletionStatus CurrentTasksStatus = CurrentFrame.ActiveTasksStatus.GetStatus(CurrentStateTree);
// Start evaluators
for (int32 EvalIndex = CurrentStateTree->EvaluatorsBegin; EvalIndex < (CurrentStateTree->EvaluatorsBegin + CurrentStateTree->EvaluatorsNum); EvalIndex++)
{
const FStateTreeEvaluatorBase& Eval = CurrentStateTree->Nodes[EvalIndex].Get<const FStateTreeEvaluatorBase>();
FStateTreeDataView EvalInstanceView = GetDataViewOrTemporary(CurrentParentFrame, CurrentFrame, Eval.InstanceDataHandle);
if (!EvalInstanceView.IsValid())
{
EvalInstanceView = AddTemporaryInstance(CurrentFrame, FStateTreeIndex16(EvalIndex), Eval.InstanceDataHandle, CurrentFrame.StateTree->DefaultInstanceData.GetStruct(Eval.InstanceTemplateIndex.Get()));
check(EvalInstanceView.IsValid());
}
FNodeInstanceDataScope DataScope(*this, &Eval, EvalIndex, Eval.InstanceDataHandle, EvalInstanceView);
// Copy bound properties with the temporary buffer.
if (Eval.BindingsBatch.IsValid())
{
CopyBatchWithValidation(CurrentParentFrame, CurrentFrame, EvalInstanceView, Eval.BindingsBatch);
}
STATETREE_LOG(Verbose, TEXT(" Start: '%s'"), *Eval.Name.ToString());
UE_STATETREE_DEBUG_EVALUATOR_ENTER_TREE(this, CurrentStateTree, FStateTreeIndex16(EvalIndex));
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Eval_TreeStart);
Eval.TreeStart(*this);
UE_STATETREE_DEBUG_EVALUATOR_EVENT(this, EvalIndex, EvalInstanceView, EStateTreeTraceEventType::OnTreeStarted);
}
}
// Start Global tasks
// Even if we call Enter/ExitState() on global tasks, they do not enter any specific state.
const FStateTreeTransitionResult Transition = {}; // Empty transition
for (int32 GlobalTaskIndex = 0; GlobalTaskIndex < CurrentStateTree->GlobalTasksNum; ++GlobalTaskIndex)
{
const int32 AssetTaskIndex = CurrentStateTree->GlobalTasksBegin + GlobalTaskIndex;
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[AssetTaskIndex].Get<const FStateTreeTaskBase>();
// Ignore disabled task
if (Task.bTaskEnabled == false)
{
STATETREE_LOG(VeryVerbose, TEXT("%*sSkipped 'EnterState' for disabled Task: '%s'"), UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
continue;
}
FStateTreeDataView TaskDataView = GetDataViewOrTemporary(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
if (!TaskDataView.IsValid())
{
TaskDataView = AddTemporaryInstance(CurrentFrame, FStateTreeIndex16(AssetTaskIndex), Task.InstanceDataHandle, CurrentFrame.StateTree->DefaultInstanceData.GetStruct(Task.InstanceTemplateIndex.Get()));
check(TaskDataView.IsValid())
}
FNodeInstanceDataScope DataScope(*this, &Task, AssetTaskIndex, Task.InstanceDataHandle, TaskDataView);
// Copy bound properties with the temporary buffer.
if (Task.BindingsBatch.IsValid())
{
CopyBatchWithValidation(CurrentParentFrame, CurrentFrame, TaskDataView, Task.BindingsBatch);
}
STATETREE_LOG(Verbose, TEXT(" Start: '%s'"), *Task.Name.ToString());
UE_STATETREE_DEBUG_TASK_ENTER_STATE(this, CurrentStateTree, FStateTreeIndex16(AssetTaskIndex));
EStateTreeRunStatus TaskRunStatus = EStateTreeRunStatus::Unset;
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Task_TreeStart);
TaskRunStatus = Task.EnterState(*this, Transition);
}
UE::StateTree::ETaskCompletionStatus TaskStatus = UE::StateTree::ExecutionContext::CastToTaskStatus(TaskRunStatus);
TaskStatus = CurrentTasksStatus.SetStatusWithPriority(GlobalTaskIndex, TaskStatus);
TaskRunStatus = UE::StateTree::ExecutionContext::CastToRunStatus(TaskStatus);
UE_STATETREE_DEBUG_TASK_EVENT(this, AssetTaskIndex, TaskDataView, EStateTreeTraceEventType::OnEntered, TaskRunStatus);
Result = UE::StateTree::ExecutionContext::GetPriorityRunStatus(Result, TaskRunStatus);
if (TaskRunStatus == EStateTreeRunStatus::Failed && CurrentTasksStatus.IsConsideredForCompletion(GlobalTaskIndex))
{
break;
}
}
return Result;
}
void FStateTreeExecutionContext::StopTemporaryEvaluatorsAndGlobalTasks(const FStateTreeExecutionFrame* CurrentParentFrame, const FStateTreeExecutionFrame& CurrentFrame)
{
//@todo only stop the evaluators and tasks that were started in StartTemporaryEvaluatorsAndGlobalTasks
STATETREE_LOG(Verbose, TEXT("Stop Temporary Evaluators & Global tasks"));
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::StopGlobalTasksForSelection);
// Create temporary transition to stop the unused global tasks and evaluators.
constexpr EStateTreeRunStatus CompletionStatus = EStateTreeRunStatus::Stopped;
FStateTreeTransitionResult Transition;
Transition.TargetState = FStateTreeStateHandle::FromCompletionStatus(CompletionStatus);
Transition.CurrentRunStatus = CompletionStatus;
TArrayView<FStateTreeTemporaryInstanceData> TempInstances = Storage.GetMutableTemporaryInstances();
for (int32 Index = TempInstances.Num() - 1; Index >= 0; Index--)
{
FStateTreeTemporaryInstanceData& TempInstance = TempInstances[Index];
if (TempInstance.FrameID != CurrentFrame.FrameID)
{
continue;
}
if (TempInstance.OwnerNodeIndex.IsValid()
&& TempInstance.Instance.IsValid())
{
FStateTreeDataView NodeInstanceView;
if (FStateTreeInstanceObjectWrapper* Wrapper = TempInstance.Instance.GetMutablePtr<FStateTreeInstanceObjectWrapper>())
{
NodeInstanceView = FStateTreeDataView(Wrapper->InstanceObject);
}
else
{
NodeInstanceView = FStateTreeDataView(TempInstance.Instance);
}
FConstStructView NodeView = CurrentFrame.StateTree->Nodes[TempInstance.OwnerNodeIndex.Get()];
if (const FStateTreeTaskBase* Task = NodeView.GetPtr<const FStateTreeTaskBase>())
{
FNodeInstanceDataScope DataScope(*this, Task, TempInstance.OwnerNodeIndex.Get(), TempInstance.DataHandle, NodeInstanceView);
STATETREE_LOG(Verbose, TEXT(" Stop: '%s'"), *Task->Name.ToString());
UE_STATETREE_DEBUG_TASK_EXIT_STATE(this, CurrentFrame.StateTree, FStateTreeIndex16(TempInstance.OwnerNodeIndex.Get()));
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Task_TreeStop);
Task->ExitState(*this, Transition);
}
UE_STATETREE_DEBUG_TASK_EVENT(this, TempInstance.OwnerNodeIndex.Get(), NodeInstanceView, EStateTreeTraceEventType::OnExited, Transition.CurrentRunStatus);
}
else if (const FStateTreeEvaluatorBase* Eval = NodeView.GetPtr<const FStateTreeEvaluatorBase>())
{
FNodeInstanceDataScope DataScope(*this, Eval, TempInstance.OwnerNodeIndex.Get(), TempInstance.DataHandle, NodeInstanceView);
STATETREE_LOG(Verbose, TEXT(" Stop: '%s'"), *Eval->Name.ToString());
UE_STATETREE_DEBUG_EVALUATOR_EXIT_TREE(this, CurrentFrame.StateTree, FStateTreeIndex16(TempInstance.OwnerNodeIndex.Get()));
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Eval_TreeStop);
Eval->TreeStop(*this);
UE_STATETREE_DEBUG_EVALUATOR_EVENT(this, TempInstance.OwnerNodeIndex.Get(), NodeInstanceView, EStateTreeTraceEventType::OnTreeStopped);
}
}
}
}
}
EStateTreeRunStatus FStateTreeExecutionContext::TickTasks(const float DeltaTime)
{
// When a task is completed it also completes the state and triggers the completion transition (because LastTickStatus is set).
// A task can complete async. See CompletedStates.
// When a task fails, stop ticking the following tasks.
// When no task ticks, then the leaf completes.
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_TickTasks);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::TickingTasks);
using namespace UE::StateTree;
FStateTreeExecutionState& Exec = GetExecState();
Exec.bHasPendingCompletedState = false;
if (Exec.ActiveFrames.IsEmpty())
{
return EStateTreeRunStatus::Failed;
}
int32 NumTotalEnabledTasks = 0;
const bool bCopyBoundPropertiesOnNonTickedTask = ExecutionContext::Private::bCopyBoundPropertiesOnNonTickedTask;
FTickTaskArguments TickArgs;
TickArgs.DeltaTime = DeltaTime;
TickArgs.bIsGlobalTasks = false;
TickArgs.bShouldTickTasks = true;
STATETREE_CLOG(Exec.ActiveFrames.Num() > 0, VeryVerbose, TEXT("Ticking Tasks"));
for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); ++FrameIndex)
{
TickArgs.ParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
TickArgs.Frame = &Exec.ActiveFrames[FrameIndex];
const UStateTree* CurrentStateTree = TickArgs.Frame->StateTree;
FCurrentlyProcessedFrameScope FrameScope(*this, TickArgs.ParentFrame, *TickArgs.Frame);
if (ExecutionContext::Private::bTickGlobalNodesFollowingTreeHierarchy)
{
if (TickArgs.Frame->bIsGlobalFrame)
{
constexpr bool bTickGlobalTasks = true;
const EStateTreeRunStatus FrameResult = TickEvaluatorsAndGlobalTasksForFrame(DeltaTime, bTickGlobalTasks, FrameIndex, TickArgs.ParentFrame, TickArgs.Frame);
if (FrameResult != EStateTreeRunStatus::Running)
{
if (ExecutionContext::Private::bGlobalTasksCompleteOwningFrame == false || FrameIndex == 0)
{
// Stop the tree execution when it's the root frame or if the previous behavior is desired.
Exec.RequestedStop = ExecutionContext::GetPriorityRunStatus(Exec.RequestedStop, FrameResult);
}
TickArgs.bShouldTickTasks = false;
break;
}
}
}
for (int32 StateIndex = 0; StateIndex < TickArgs.Frame->ActiveStates.Num(); ++StateIndex)
{
const FStateTreeStateHandle CurrentHandle = TickArgs.Frame->ActiveStates[StateIndex];
const FCompactStateTreeState& CurrentState = CurrentStateTree->States[CurrentHandle.Index];
FTasksCompletionStatus CurrentCompletionStatus = TickArgs.Frame->ActiveTasksStatus.GetStatus(CurrentState);
TickArgs.StateID = TickArgs.Frame->ActiveStates.StateIDs[StateIndex];
TickArgs.TasksCompletionStatus = &CurrentCompletionStatus;
FCurrentlyProcessedStateScope StateScope(*this, CurrentHandle);
UE_STATETREE_DEBUG_SCOPED_STATE(this, CurrentHandle);
STATETREE_CLOG(CurrentState.TasksNum > 0, VeryVerbose, TEXT("%*sState '%s'")
, (FrameIndex + StateIndex + 1) * Debug::IndentSize, TEXT("")
, *DebugGetStatePath(Exec.ActiveFrames, TickArgs.Frame, StateIndex));
if (CurrentState.Type == EStateTreeStateType::Linked || CurrentState.Type == EStateTreeStateType::LinkedAsset)
{
if (CurrentState.ParameterDataHandle.IsValid() && CurrentState.ParameterBindingsBatch.IsValid())
{
const FStateTreeDataView StateParamsDataView = GetDataView(TickArgs.ParentFrame, *TickArgs.Frame, CurrentState.ParameterDataHandle);
CopyBatchOnActiveInstances(TickArgs.ParentFrame, *TickArgs.Frame, StateParamsDataView, CurrentState.ParameterBindingsBatch);
}
}
const bool bHasEvents = EventQueue && EventQueue->HasEvents();
bool bRequestLoopStop = false;
if (bCopyBoundPropertiesOnNonTickedTask || CurrentState.ShouldTickTasks(bHasEvents))
{
// Update Tasks data and tick if possible (ie. if no task has yet failed and bShouldTickTasks is true)
TickArgs.TasksBegin = CurrentState.TasksBegin;
TickArgs.TasksNum = CurrentState.TasksNum;
TickArgs.Indent = (FrameIndex + StateIndex + 1);
const FTickTaskResult TickTasksResult = TickTasks(TickArgs);
// Keep updating the binding but do not call tick on tasks if there's a failure.
TickArgs.bShouldTickTasks = TickTasksResult.bShouldTickTasks
&& !CurrentCompletionStatus.HasAnyFailed();
// If a failure and we do not copy then bindings, then we can stop.
bRequestLoopStop = !bCopyBoundPropertiesOnNonTickedTask && !TickTasksResult.bShouldTickTasks;
}
NumTotalEnabledTasks += CurrentState.EnabledTasksNum;
if (bRequestLoopStop)
{
break;
}
}
}
// Collect the result after every tasks has the chance to tick.
//An async or delegate might complete a global or "previous" task (in a different order).
EStateTreeRunStatus FirstFrameResult = EStateTreeRunStatus::Running;
EStateTreeRunStatus FrameResult = EStateTreeRunStatus::Running;
EStateTreeRunStatus StateResult = EStateTreeRunStatus::Running;
for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); ++FrameIndex)
{
using namespace UE::StateTree::ExecutionContext;
const FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
if (CurrentFrame.bIsGlobalFrame)
{
const ETaskCompletionStatus GlobalTasksStatus = CurrentFrame.ActiveTasksStatus.GetStatus(CurrentStateTree).GetCompletionStatus();
if (FrameIndex == 0)
{
FirstFrameResult = CastToRunStatus(GlobalTasksStatus);
}
FrameResult = GetPriorityRunStatus(FrameResult, CastToRunStatus(GlobalTasksStatus));
}
for (int32 StateIndex = 0; StateIndex < CurrentFrame.ActiveStates.Num() && StateResult != EStateTreeRunStatus::Failed; ++StateIndex)
{
const FStateTreeStateHandle CurrentHandle = CurrentFrame.ActiveStates[StateIndex];
const FCompactStateTreeState& State = CurrentStateTree->States[CurrentHandle.Index];
const ETaskCompletionStatus StateTasksStatus = CurrentFrame.ActiveTasksStatus.GetStatus(State).GetCompletionStatus();
StateResult = GetPriorityRunStatus(StateResult, CastToRunStatus(StateTasksStatus));
}
}
if (ExecutionContext::Private::bGlobalTasksCompleteOwningFrame && FirstFrameResult != EStateTreeRunStatus::Running)
{
Exec.RequestedStop = ExecutionContext::GetPriorityRunStatus(Exec.RequestedStop, FrameResult);
}
else if (ExecutionContext::Private::bGlobalTasksCompleteOwningFrame == false && FrameResult != EStateTreeRunStatus::Running)
{
Exec.RequestedStop = ExecutionContext::GetPriorityRunStatus(Exec.RequestedStop, FrameResult);
}
else if (NumTotalEnabledTasks == 0 && StateResult == EStateTreeRunStatus::Running && FrameResult == EStateTreeRunStatus::Running)
{
// No enabled tasks, done ticking.
//Complete the the bottom state in the bottom frame (to trigger the completion transitions).
if (ensureMsgf(Exec.ActiveFrames.Num() > 0, TEXT("No task is allowed to clear/stop/transition. Those actions should be delayed inside the execution context.")))
{
FStateTreeExecutionFrame& LastFrame = Exec.ActiveFrames.Last();
const int32 NumberOfActiveState = LastFrame.ActiveStates.Num();
if (ensureMsgf(NumberOfActiveState != 0, TEXT("No task is allowed to clear/stop/transition. Those action should be delayed inside the execution context.")))
{
const FStateTreeStateHandle CurrentHandle = LastFrame.ActiveStates[NumberOfActiveState - 1];
const FCompactStateTreeState& State = LastFrame.StateTree->States[CurrentHandle.Index];
LastFrame.ActiveTasksStatus.GetStatus(State).SetCompletionStatus(ETaskCompletionStatus::Succeeded);
}
else
{
LastFrame.ActiveTasksStatus.GetStatus(LastFrame.StateTree).SetCompletionStatus(ETaskCompletionStatus::Succeeded);
}
}
else
{
Exec.RequestedStop = EStateTreeRunStatus::Stopped;
}
StateResult = EStateTreeRunStatus::Succeeded;
}
Exec.bHasPendingCompletedState = StateResult != EStateTreeRunStatus::Running || FrameResult != EStateTreeRunStatus::Running;
return StateResult;
}
FStateTreeExecutionContext::FTickTaskResult FStateTreeExecutionContext::TickTasks(const FTickTaskArguments& Args)
{
using namespace UE::StateTree;
check(Args.Frame);
check(Args.TasksCompletionStatus);
bool bShouldTickTasks = Args.bShouldTickTasks;
FStateTreeExecutionState& Exec = GetExecState();
const bool bCopyBoundPropertiesOnNonTickedTask = ExecutionContext::Private::bCopyBoundPropertiesOnNonTickedTask;
const UStateTree* CurrentStateTree = Args.Frame->StateTree;
const FActiveFrameID CurrentFrameID = Args.Frame->FrameID;
check(CurrentStateTree);
for (int32 OwnerTaskIndex = 0; OwnerTaskIndex < Args.TasksNum; ++OwnerTaskIndex)
{
const int32 AssetTaskIndex = Args.TasksBegin + OwnerTaskIndex;
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[AssetTaskIndex].Get<const FStateTreeTaskBase>();
// Ignore disabled task
if (Task.bTaskEnabled == false)
{
STATETREE_LOG(VeryVerbose, TEXT("%*sSkipped 'Tick' for disabled Task: '%s'"), Debug::IndentSize, TEXT(""), *Task.Name.ToString());
continue;
}
const FStateTreeDataView TaskInstanceView = GetDataView(Args.ParentFrame, *Args.Frame, Task.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Task, AssetTaskIndex, Task.InstanceDataHandle, TaskInstanceView);
const bool bHasEvents = EventQueue && EventQueue->HasEvents();
const bool bIsTaskRunning = Args.TasksCompletionStatus->IsRunning(OwnerTaskIndex);
const bool bNeedsTick = bShouldTickTasks
&& bIsTaskRunning
&& (Task.bShouldCallTick || (bHasEvents && Task.bShouldCallTickOnlyOnEvents));
STATETREE_LOG(VeryVerbose, TEXT("%*s Tick: '%s' %s"), Args.Indent * Debug::IndentSize, TEXT("")
, *Task.Name.ToString()
, !bNeedsTick ? TEXT("[not ticked]") : TEXT(""));
// Copy bound properties.
// Only copy properties when the task is actually ticked, and copy properties at tick is requested.
const bool bCopyBatch = (bCopyBoundPropertiesOnNonTickedTask || bNeedsTick)
&& Task.BindingsBatch.IsValid()
&& Task.bShouldCopyBoundPropertiesOnTick;
if (bCopyBatch)
{
CopyBatchOnActiveInstances(Args.ParentFrame, *Args.Frame, TaskInstanceView, Task.BindingsBatch);
}
if (!bNeedsTick)
{
// Task didn't tick because it failed.
//The following tasks should not tick but we might still need to update their bindings.
if (!bIsTaskRunning && bShouldTickTasks && Args.TasksCompletionStatus->HasAnyFailed())
{
bShouldTickTasks = false;
}
continue;
}
//UE_STATETREE_DEBUG_TASK_EVENT(this, AssetTaskIndex, TaskDataView, EStateTreeTraceEventType::OnTickingTask, EStateTreeRunStatus::Running);
UE_STATETREE_DEBUG_TASK_TICK(this, CurrentStateTree, AssetTaskIndex);
EStateTreeRunStatus TaskRunStatus = EStateTreeRunStatus::Unset;
{
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Task_Tick);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Task_Tick);
TaskRunStatus = Task.Tick(*this, Args.DeltaTime);
}
// Set the new status and fetch back the status with priority.
//In case an async task completes the same task.
//Or in case FinishTask() inside the Task.Tick()
ETaskCompletionStatus TaskStatus = UE::StateTree::ExecutionContext::CastToTaskStatus(TaskRunStatus);
TaskStatus = Args.TasksCompletionStatus->SetStatusWithPriority(OwnerTaskIndex, TaskStatus);
TaskRunStatus = ExecutionContext::CastToRunStatus(TaskStatus);
UE_STATETREE_DEBUG_TASK_EVENT(this, AssetTaskIndex, TaskInstanceView,
TaskRunStatus != EStateTreeRunStatus::Running ? EStateTreeTraceEventType::OnTaskCompleted : EStateTreeTraceEventType::OnTicked,
TaskRunStatus);
if (TaskRunStatus == EStateTreeRunStatus::Failed && Args.TasksCompletionStatus->IsConsideredForCompletion(OwnerTaskIndex))
{
bShouldTickTasks = false;
}
}
return FTickTaskResult{bShouldTickTasks};
}
// Deprecated
bool FStateTreeExecutionContext::TestAllConditions(const FStateTreeExecutionFrame* CurrentParentFrame, const FStateTreeExecutionFrame& CurrentFrame, const int32 ConditionsOffset, const int32 ConditionsNum)
{
return TestAllConditionsInternal<false>(CurrentParentFrame, CurrentFrame, FStateTreeStateHandle(), ConditionsOffset, ConditionsNum);
}
bool FStateTreeExecutionContext::TestAllConditionsOnActiveInstances(const FStateTreeExecutionFrame* CurrentParentFrame, const FStateTreeExecutionFrame& CurrentFrame, FStateTreeStateHandle CurrentStateHandle, const int32 ConditionsOffset, const int32 ConditionsNum)
{
return TestAllConditionsInternal<true>(CurrentParentFrame, CurrentFrame, CurrentStateHandle, ConditionsOffset, ConditionsNum);
}
bool FStateTreeExecutionContext::TestAllConditionsWithValidation(const FStateTreeExecutionFrame* CurrentParentFrame, const FStateTreeExecutionFrame& CurrentFrame, FStateTreeStateHandle CurrentStateHandle, const int32 ConditionsOffset, const int32 ConditionsNum)
{
return TestAllConditionsInternal<false>(CurrentParentFrame, CurrentFrame, CurrentStateHandle, ConditionsOffset, ConditionsNum);
}
template<bool bOnActiveInstances>
bool FStateTreeExecutionContext::TestAllConditionsInternal(const FStateTreeExecutionFrame* CurrentParentFrame, const FStateTreeExecutionFrame& CurrentFrame, FStateTreeStateHandle CurrentStateHandle, const int32 ConditionsOffset, const int32 ConditionsNum)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_TestConditions);
if (ConditionsNum == 0)
{
return true;
}
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
FCurrentlyProcessedStateScope StateScope(*this, CurrentStateHandle);
TStaticArray<EStateTreeExpressionOperand, UE::StateTree::MaxExpressionIndent + 1> Operands(InPlace, EStateTreeExpressionOperand::Copy);
TStaticArray<bool, UE::StateTree::MaxExpressionIndent + 1> Values(InPlace, false);
int32 Level = 0;
for (int32 Index = 0; Index < ConditionsNum; Index++)
{
const int32 ConditionIndex = ConditionsOffset + Index;
const FStateTreeConditionBase& Cond = CurrentFrame.StateTree->Nodes[ConditionIndex].Get<const FStateTreeConditionBase>();
checkf(Cond.InstanceDataHandle.GetSource() == EStateTreeDataSourceType::SharedInstanceData
|| Cond.InstanceDataHandle.GetSource() == EStateTreeDataSourceType::SharedInstanceDataObject,
TEXT("Condition only support SharedInstanceData"));
const FStateTreeDataView ConditionInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Cond.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Cond, ConditionIndex, Cond.InstanceDataHandle, ConditionInstanceView);
bool bValue = false;
if (Cond.EvaluationMode == EStateTreeConditionEvaluationMode::Evaluated)
{
// Copy bound properties.
if (Cond.BindingsBatch.IsValid())
{
const bool bBatchCopied = bOnActiveInstances
? CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, ConditionInstanceView, Cond.BindingsBatch)
: CopyBatchWithValidation(CurrentParentFrame, CurrentFrame, ConditionInstanceView, Cond.BindingsBatch);
if (!bBatchCopied)
{
// If the source data cannot be accessed, the whole expression evaluates to false.
UE_STATETREE_DEBUG_CONDITION_EVENT(this, ConditionIndex, ConditionInstanceView, EStateTreeTraceEventType::InternalForcedFailure);
UE_STATETREE_DEBUG_LOG_EVENT(this, Warning, TEXT("Evaluation forced to false: source data cannot be accessed (e.g. enter conditions trying to access inactive parent state)"));
Values[0] = false;
break;
}
}
UE_STATETREE_DEBUG_CONDITION_TEST_CONDITION(this, CurrentFrame.StateTree, Index);
bValue = Cond.TestCondition(*this);
UE_STATETREE_DEBUG_CONDITION_EVENT(this, ConditionIndex, ConditionInstanceView, bValue ? EStateTreeTraceEventType::Passed : EStateTreeTraceEventType::Failed);
// Reset copied properties that might contain object references.
if (Cond.BindingsBatch.IsValid())
{
CurrentFrame.StateTree->PropertyBindings.Super::ResetObjects(Cond.BindingsBatch, ConditionInstanceView);
}
}
else
{
bValue = Cond.EvaluationMode == EStateTreeConditionEvaluationMode::ForcedTrue;
UE_STATETREE_DEBUG_CONDITION_EVENT(this, ConditionIndex, FStateTreeDataView{}, bValue ? EStateTreeTraceEventType::ForcedSuccess : EStateTreeTraceEventType::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.
const EStateTreeExpressionOperand Operand = Index == 0 ? EStateTreeExpressionOperand::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 EStateTreeExpressionOperand::Copy:
Values[Level] = Values[Level + 1];
break;
case EStateTreeExpressionOperand::And:
Values[Level] &= Values[Level + 1];
break;
case EStateTreeExpressionOperand::Or:
Values[Level] |= Values[Level + 1];
break;
}
Operands[Level] = EStateTreeExpressionOperand::Copy;
}
}
return Values[0];
}
//Deprecated
float FStateTreeExecutionContext::EvaluateUtility(const FStateTreeExecutionFrame* CurrentParentFrame, const FStateTreeExecutionFrame& CurrentFrame, const int32 ConsiderationsBegin, const int32 ConsiderationsNum, const float StateWeight)
{
const FStateTreeStateHandle StateHandle = FStateTreeStateHandle::Invalid;
return EvaluateUtilityWithValidation(CurrentParentFrame, CurrentFrame, StateHandle, ConsiderationsBegin, ConsiderationsNum, StateWeight);
}
float FStateTreeExecutionContext::EvaluateUtilityWithValidation(const FStateTreeExecutionFrame* CurrentParentFrame, const FStateTreeExecutionFrame& CurrentFrame, FStateTreeStateHandle CurrentStateHandle, const int32 ConsiderationsBegin, const int32 ConsiderationsNum, const float StateWeight)
{
// @todo: Tracing support
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_EvaluateUtility);
if (ConsiderationsNum == 0)
{
return .0f;
}
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
FCurrentlyProcessedStateScope NextStateScope(*this, CurrentStateHandle);
TStaticArray<EStateTreeExpressionOperand, UE::StateTree::MaxExpressionIndent + 1> Operands(InPlace, EStateTreeExpressionOperand::Copy);
TStaticArray<float, UE::StateTree::MaxExpressionIndent + 1> Values(InPlace, false);
int32 Level = 0;
float Value = .0f;
for (int32 Index = 0; Index < ConsiderationsNum; Index++)
{
const int32 ConsiderationIndex = ConsiderationsBegin + Index;
const FStateTreeConsiderationBase& Consideration = CurrentFrame.StateTree->Nodes[ConsiderationIndex].Get<const FStateTreeConsiderationBase>();
checkf(Consideration.InstanceDataHandle.GetSource() == EStateTreeDataSourceType::SharedInstanceData
|| Consideration.InstanceDataHandle.GetSource() == EStateTreeDataSourceType::SharedInstanceDataObject
, TEXT("Consideration only support SharedInstanceData"));
const FStateTreeDataView ConsiderationInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Consideration.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Consideration, ConsiderationIndex, Consideration.InstanceDataHandle, ConsiderationInstanceView);
// Copy bound properties.
if (Consideration.BindingsBatch.IsValid())
{
// Use validated copy, since we test in situations where the sources are not always valid (e.g. considerations may try to access inactive parent state).
if (!CopyBatchWithValidation(CurrentParentFrame, CurrentFrame, ConsiderationInstanceView, Consideration.BindingsBatch))
{
// If the source data cannot be accessed, the whole expression evaluates to zero.
Values[0] = 0.0f;
break;
}
}
Value = Consideration.GetNormalizedScore(*this);
// Reset copied properties that might contain object references.
if (Consideration.BindingsBatch.IsValid())
{
CurrentFrame.StateTree->PropertyBindings.Super::ResetObjects(Consideration.BindingsBatch, ConsiderationInstanceView);
}
const int32 DeltaIndent = Consideration.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.
const EStateTreeExpressionOperand Operand = Index == 0 ? EStateTreeExpressionOperand::Copy : Consideration.Operand;
Operands[Level] = Operand;
// Store current value at the top of the stack.
Level += OpenParens;
Values[Level] = Value;
// 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 EStateTreeExpressionOperand::Copy:
Values[Level] = Values[Level + 1];
break;
case EStateTreeExpressionOperand::And:
Values[Level] = FMath::Min(Values[Level], Values[Level + 1]);
break;
case EStateTreeExpressionOperand::Or:
Values[Level] = FMath::Max(Values[Level], Values[Level + 1]);
break;
}
Operands[Level] = EStateTreeExpressionOperand::Copy;
}
}
return StateWeight * Values[0];
}
void FStateTreeExecutionContext::EvaluatePropertyFunctionsOnActiveInstances(const FStateTreeExecutionFrame* CurrentParentFrame, const FStateTreeExecutionFrame& CurrentFrame, FStateTreeIndex16 FuncsBegin, uint16 FuncsNum)
{
for (int32 FuncIndex = FuncsBegin.Get(); FuncIndex < FuncsBegin.Get() + FuncsNum; ++FuncIndex)
{
const FStateTreePropertyFunctionBase& Func = CurrentFrame.StateTree->Nodes[FuncIndex].Get<const FStateTreePropertyFunctionBase>();
const FStateTreeDataView FuncInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Func.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Func, FuncIndex, Func.InstanceDataHandle, FuncInstanceView);
// Copy bound properties.
if (Func.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).
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, FuncInstanceView, Func.BindingsBatch);
}
Func.Execute(*this);
// Reset copied properties that might contain object references.
if (Func.BindingsBatch.IsValid())
{
CurrentFrame.StateTree->PropertyBindings.Super::ResetObjects(Func.BindingsBatch, FuncInstanceView);
}
}
}
void FStateTreeExecutionContext::EvaluatePropertyFunctionsWithValidation(const FStateTreeExecutionFrame* CurrentParentFrame, const FStateTreeExecutionFrame& CurrentFrame, FStateTreeIndex16 FuncsBegin, uint16 FuncsNum)
{
for (int32 FuncIndex = FuncsBegin.Get(); FuncIndex < FuncsBegin.Get() + FuncsNum; ++FuncIndex)
{
const FStateTreePropertyFunctionBase& Func = CurrentFrame.StateTree->Nodes[FuncIndex].Get<const FStateTreePropertyFunctionBase>();
const FStateTreeDataView FuncInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Func.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Func, FuncIndex, Func.InstanceDataHandle, FuncInstanceView);
// Copy bound properties.
if (Func.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).
CopyBatchWithValidation(CurrentParentFrame, CurrentFrame, FuncInstanceView, Func.BindingsBatch);
}
Func.Execute(*this);
// Reset copied properties that might contain object references.
if (Func.BindingsBatch.IsValid())
{
CurrentFrame.StateTree->PropertyBindings.Super::ResetObjects(Func.BindingsBatch, FuncInstanceView);
}
}
}
FString FStateTreeExecutionContext::DebugGetEventsAsString() const
{
TStringBuilder<512> StrBuilder;
if (EventQueue)
{
for (const FStateTreeSharedEvent& Event : EventQueue->GetEventsView())
{
if (Event.IsValid())
{
if (StrBuilder.Len() > 0)
{
StrBuilder << TEXT(", ");
}
const bool bHasTag = Event->Tag.IsValid();
const bool bHasPayload = Event->Payload.GetScriptStruct() != nullptr;
if (bHasTag || bHasPayload)
{
StrBuilder << (TEXT('('));
if (bHasTag)
{
StrBuilder << TEXT("Tag: '");
StrBuilder << Event->Tag.GetTagName();
StrBuilder << TEXT('\'');
}
if (bHasTag && bHasPayload)
{
StrBuilder << TEXT(", ");
}
if (bHasPayload)
{
StrBuilder << TEXT(" Payload: '");
StrBuilder << Event->Payload.GetScriptStruct()->GetFName();
StrBuilder << TEXT('\'');
}
StrBuilder << TEXT(") ");
}
}
}
}
return StrBuilder.ToString();
}
bool FStateTreeExecutionContext::RequestTransition(
const FStateTreeExecutionFrame& CurrentFrame,
const FStateTreeStateHandle NextState,
const EStateTreeTransitionPriority Priority,
const FStateTreeSharedEvent* TransitionEvent,
const EStateTreeSelectionFallback Fallback)
{
// Skip lower priority transitions.
if (NextTransition.Priority >= Priority)
{
return false;
}
if (NextState.IsCompletionState())
{
SetupNextTransition(CurrentFrame, NextState, Priority);
STATETREE_LOG(Verbose, TEXT("Transition on state '%s' -> state '%s'"),
*GetSafeStateName(CurrentFrame, CurrentFrame.ActiveStates.Last()), *NextState.Describe());
return true;
}
if (!NextState.IsValid())
{
// NotSet is no-operation, but can be used to mask a transition at parent state. Returning unset keeps updating current state.
SetupNextTransition(CurrentFrame, FStateTreeStateHandle::Invalid, Priority);
return true;
}
FStateSelectionResult StateSelectionResult;
if (SelectState(CurrentFrame, NextState, StateSelectionResult, TransitionEvent, Fallback))
{
SetupNextTransition(CurrentFrame, NextState, Priority);
NextTransition.NextActiveFrames = StateSelectionResult.GetSelectedFrames();
NextTransition.NextActiveFrameEvents = StateSelectionResult.GetFramesStateSelectionEvents();
// Consume events from states, if required.
for (int32 FrameIndex = 0; FrameIndex < NextTransition.NextActiveFrames.Num(); FrameIndex++)
{
const FStateTreeExecutionFrame& Frame = NextTransition.NextActiveFrames[FrameIndex];
const FStateTreeFrameStateSelectionEvents& FrameEvents = NextTransition.NextActiveFrameEvents[FrameIndex];
for (int32 StateIndex = 0; StateIndex < Frame.ActiveStates.Num(); StateIndex++)
{
if (FrameEvents.Events[StateIndex].IsValid())
{
const FCompactStateTreeState& State = Frame.StateTree->States[StateIndex];
if (State.bConsumeEventOnSelect)
{
ConsumeEvent(FrameEvents.Events[StateIndex]);
}
}
}
}
STATETREE_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 FStateTreeExecutionContext::SetupNextTransition(const FStateTreeExecutionFrame& CurrentFrame, const FStateTreeStateHandle NextState, const EStateTreeTransitionPriority Priority)
{
const FStateTreeExecutionState& Exec = GetExecState();
NextTransition.SourceFrameID = CurrentFrame.FrameID;
NextTransition.SourceStateID = UE::StateTree::FActiveStateID();
if (CurrentlyProcessedState.IsValid())
{
const int32 CurrentStateIndex = CurrentFrame.ActiveStates.IndexOfReverse(CurrentlyProcessedState);
if (CurrentStateIndex != INDEX_NONE)
{
NextTransition.SourceStateID = CurrentFrame.ActiveStates.StateIDs[CurrentStateIndex];
}
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
NextTransition.SourceState = CurrentlyProcessedState;
NextTransition.SourceStateTree = CurrentFrame.StateTree;
NextTransition.SourceRootState = CurrentFrame.ActiveStates.GetStateSafe(0);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
NextTransition.TargetState = NextState;
NextTransition.CurrentState = FStateTreeStateHandle::Invalid;
NextTransition.CurrentRunStatus = Exec.LastTickStatus;
NextTransition.ChangeType = EStateTreeStateChangeType::Changed;
NextTransition.Priority = Priority;
FStateTreeExecutionFrame& NewFrame = NextTransition.NextActiveFrames.AddDefaulted_GetRef();
NewFrame.StateTree = CurrentFrame.StateTree;
NewFrame.RootState = CurrentFrame.RootState;
NewFrame.ActiveTasksStatus = CurrentFrame.ActiveTasksStatus;
if (NextState == FStateTreeStateHandle::Invalid)
{
NewFrame.ActiveStates = {};
}
else
{
NewFrame.ActiveStates = FStateTreeActiveStates(NextState, UE::StateTree::FActiveStateID::Invalid);
}
}
bool FStateTreeExecutionContext::TriggerTransitions()
{
//1. Process transition requests. Keep the single request with the highest priority.
//2. Process tick/event/delegate transitions and tasks. TriggerTransitions, from bottom to top.
// If delayed,
// If delayed completed, then process.
// Else add them to the delayed transition list.
//3. If no transition, Process completion transitions, from top to bottom.
//4. If transition occurs, check if there are any frame (sub-tree) that completed.
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_TriggerTransition);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::TriggerTransitions);
FAllowDirectTransitionsScope AllowDirectTransitionsScope(*this); // Set flag for the scope of this function to allow direct transitions without buffering.
FStateTreeExecutionState& Exec = GetExecState();
if (EventQueue && EventQueue->HasEvents())
{
STATETREE_LOG(Verbose, TEXT("Trigger transitions with events: %s"), *DebugGetEventsAsString());
UE_STATETREE_DEBUG_LOG_EVENT(this, Log, TEXT("Trigger transitions with events: %s"), *DebugGetEventsAsString());
}
NextTransition = FStateTreeTransitionResult();
//
// Process transition requests
//
for (const FStateTreeTransitionRequest& Request : InstanceData.GetTransitionRequests())
{
// Find frame associated with the request.
const FStateTreeExecutionFrame* CurrentFrame = Exec.FindActiveFrame(Request.SourceFrameID);
if (CurrentFrame)
{
if (RequestTransition(*CurrentFrame, Request.TargetState, Request.Priority, /*TransitionEvent*/nullptr, Request.Fallback))
{
NextTransitionSource = FStateTreeTransitionSource(CurrentFrame->StateTree, EStateTreeTransitionSourceType::ExternalRequest, Request.TargetState, Request.Priority);
}
}
}
//@todo should only clear once when the transition is successful.
//to prevent 2 async requests and the first requests fails for X reason.
//they will be identified by a Frame/StateID so it's fine if they stay in the array.
InstanceData.ResetTransitionRequests();
//
// Collect expired delayed transitions
//
TArray<FStateTreeTransitionDelayedState, TInlineAllocator<8>> ExpiredTransitionsDelayed;
for (TArray<FStateTreeTransitionDelayedState>::TIterator It = Exec.DelayedTransitions.CreateIterator(); It; ++It)
{
if (It->TimeLeft <= 0.0f)
{
ExpiredTransitionsDelayed.Emplace(MoveTemp(*It));
It.RemoveCurrentSwap();
}
}
//
// Collect tick, event, and task based transitions.
//
struct FTransitionHandler
{
FTransitionHandler() = default;
FTransitionHandler(const int32 InFrameIndex, const FStateTreeStateHandle InStateHandle, const UE::StateTree::FActiveStateID InStateID, const EStateTreeTransitionPriority InPriority)
: StateHandle(InStateHandle)
, StateID(InStateID)
, TaskIndex(FStateTreeIndex16::Invalid)
, FrameIndex(InFrameIndex)
, Priority(InPriority)
{
}
FTransitionHandler(const int32 InFrameIndex, const FStateTreeStateHandle InStateHandle, const UE::StateTree::FActiveStateID InStateID, const FStateTreeIndex16 InTaskIndex, const EStateTreeTransitionPriority InPriority)
: StateHandle(InStateHandle)
, StateID(InStateID)
, TaskIndex(InTaskIndex)
, FrameIndex(InFrameIndex)
, Priority(InPriority)
{
}
FStateTreeStateHandle StateHandle;
UE::StateTree::FActiveStateID StateID;
FStateTreeIndex16 TaskIndex = FStateTreeIndex16::Invalid;
int32 FrameIndex = 0;
EStateTreeTransitionPriority Priority = EStateTreeTransitionPriority::Normal;
bool operator<(const FTransitionHandler& Other) const
{
// Highest priority first.
return Priority > Other.Priority;
}
};
TArray<FTransitionHandler, TInlineAllocator<16>> TransitionHandlers;
if (Exec.ActiveFrames.Num() > 0)
{
// Re-cache bHasEvents, RequestTransition above can create new events.
const bool bHasEvents = EventQueue && EventQueue->HasEvents();
const bool bHasBroadcastedDelegates = Storage.HasBroadcastedDelegates();
// Transition() can TriggerTransitions() in a loop when a sub-frame completes.
//We do not want to evaluate the transition from that sub-frame.
const int32 EndFrameIndex = TriggerTransitionsFromFrameIndex.Get(Exec.ActiveFrames.Num() - 1);
for (int32 FrameIndex = EndFrameIndex; FrameIndex >= 0; FrameIndex--)
{
FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
for (int32 StateIndex = CurrentFrame.ActiveStates.Num() - 1; StateIndex >= 0; StateIndex--)
{
const FStateTreeStateHandle StateHandle = CurrentFrame.ActiveStates[StateIndex];
const UE::StateTree::FActiveStateID StateID = CurrentFrame.ActiveStates.StateIDs[StateIndex];
const FCompactStateTreeState& State = CurrentStateTree->States[StateHandle.Index];
// Do not process any transitions from a disabled state
if (!State.bEnabled)
{
continue;
}
// Transition tasks.
if (State.bHasTransitionTasks)
{
bool bAdded = false;
for (int32 TaskIndex = (State.TasksBegin + State.TasksNum) - 1; TaskIndex >= State.TasksBegin; TaskIndex--)
{
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
if (Task.bShouldAffectTransitions && Task.bTaskEnabled)
{
TransitionHandlers.Emplace(FrameIndex, StateHandle, StateID, FStateTreeIndex16(TaskIndex), Task.TransitionHandlingPriority);
bAdded = true;
}
}
ensureMsgf(bAdded, TEXT("bHasTransitionTasks is set but not task were added for the State: '%s' inside theStateTree %s"), *State.Name.ToString(), *CurrentStateTree->GetPathName());
}
// Has expired transition delayed.
const bool bHasActiveTransitionDelayed = ExpiredTransitionsDelayed.ContainsByPredicate([StateID](const FStateTreeTransitionDelayedState& Other)
{
return Other.StateID == StateID;
});
// Regular transitions on state
//or A transition task can trigger an event. We need to add the state if that is a possibility
//or Expired transition delayed
if (State.ShouldTickTransitions(bHasEvents, bHasBroadcastedDelegates) || State.bHasTransitionTasks || bHasActiveTransitionDelayed)
{
TransitionHandlers.Emplace(FrameIndex, StateHandle, StateID, EStateTreeTransitionPriority::Normal);
}
}
if (CurrentFrame.bIsGlobalFrame)
{
// Global transition tasks.
if (CurrentFrame.StateTree->bHasGlobalTransitionTasks)
{
bool bAdded = false;
for (int32 TaskIndex = (CurrentStateTree->GlobalTasksBegin + CurrentStateTree->GlobalTasksNum) - 1; TaskIndex >= CurrentFrame.StateTree->GlobalTasksBegin; TaskIndex--)
{
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[TaskIndex].Get<const FStateTreeTaskBase>();
if (Task.bShouldAffectTransitions && Task.bTaskEnabled)
{
TransitionHandlers.Emplace(FrameIndex, FStateTreeStateHandle(), UE::StateTree::FActiveStateID::Invalid, FStateTreeIndex16(TaskIndex), Task.TransitionHandlingPriority);
bAdded = true;
}
}
ensureMsgf(bAdded, TEXT("bHasGlobalTransitionTasks is set but not task were added for the StateTree `%s`"), *CurrentStateTree->GetPathName());
}
}
}
// Sort by priority and adding order.
TransitionHandlers.StableSort();
}
//
// Process task and state transitions in priority order.
//
for (const FTransitionHandler& Handler : TransitionHandlers)
{
const int32 FrameIndex = Handler.FrameIndex;
FStateTreeExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UE::StateTree::FActiveFrameID CurrentFrameID = CurrentFrame.FrameID;
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
FCurrentlyProcessedStateScope StateScope(*this, Handler.StateHandle);
UE_STATETREE_DEBUG_SCOPED_STATE(this, Handler.StateHandle);
if (Handler.TaskIndex.IsValid())
{
const FStateTreeTaskBase& Task = CurrentStateTree->Nodes[Handler.TaskIndex.Get()].Get<const FStateTreeTaskBase>();
// Ignore disabled task
if (Task.bTaskEnabled == false)
{
STATETREE_LOG(VeryVerbose, TEXT("%*sSkipped 'TriggerTransitions' for disabled Task: '%s'"), UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
continue;
}
const FStateTreeDataView TaskInstanceView = GetDataView(CurrentParentFrame, CurrentFrame, Task.InstanceDataHandle);
FNodeInstanceDataScope DataScope(*this, &Task, Handler.TaskIndex.Get(), Task.InstanceDataHandle, TaskInstanceView);
// Copy bound properties.
if (Task.BindingsBatch.IsValid())
{
CopyBatchOnActiveInstances(CurrentParentFrame, CurrentFrame, TaskInstanceView, Task.BindingsBatch);
}
STATETREE_LOG(VeryVerbose, TEXT("%*sTriggerTransitions: '%s'"), UE::StateTree::Debug::IndentSize, TEXT(""), *Task.Name.ToString());
UE_STATETREE_DEBUG_TASK_EVENT(this, Handler.TaskIndex.Get(), TaskInstanceView, EStateTreeTraceEventType::OnEvaluating, EStateTreeRunStatus::Running);
check(TaskInstanceView.IsValid());
Task.TriggerTransitions(*this);
}
else if (Handler.StateHandle.IsValid())
{
check(Handler.StateID.IsValid());
const FCompactStateTreeState& State = CurrentStateTree->States[Handler.StateHandle.Index];
// Transitions
for (uint8 TransitionCounter = 0; TransitionCounter < State.TransitionsNum; ++TransitionCounter)
{
// All transition conditions must pass
const int16 TransitionIndex = State.TransitionsBegin + TransitionCounter;
const FCompactStateTransition& Transition = CurrentStateTree->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 <= NextTransition.Priority)
{
continue;
}
// Skip completion transitions
if (EnumHasAnyFlags(Transition.Trigger, EStateTreeTransitionTrigger::OnStateCompleted))
{
continue;
}
// If a delayed transition has passed the delay, try trigger it.
if (Transition.HasDelay())
{
bool bTriggeredDelayedTransition = false;
for (const FStateTreeTransitionDelayedState& DelayedTransition : ExpiredTransitionsDelayed)
{
if (DelayedTransition.StateID == Handler.StateID && DelayedTransition.TransitionIndex == FStateTreeIndex16(TransitionIndex))
{
STATETREE_LOG(Verbose, TEXT("Passed delayed transition from '%s' (%s) -> '%s'"),
*GetSafeStateName(CurrentFrame, CurrentFrame.ActiveStates.Last()), *State.Name.ToString(), *GetSafeStateName(CurrentFrame, Transition.State));
// Trigger Delayed Transition when the delay has passed.
if (RequestTransition(CurrentFrame, Transition.State, Transition.Priority, &DelayedTransition.CapturedEvent, Transition.Fallback))
{
// If the transition was successfully requested with a specific event, consume and remove the event, it's been used.
if (DelayedTransition.CapturedEvent.IsValid() && Transition.bConsumeEventOnSelect)
{
ConsumeEvent(DelayedTransition.CapturedEvent);
}
NextTransitionSource = FStateTreeTransitionSource(CurrentFrame.StateTree, FStateTreeIndex16(TransitionIndex), Transition.State, Transition.Priority);
bTriggeredDelayedTransition = true;
break;
}
}
}
if (bTriggeredDelayedTransition)
{
continue;
}
}
TArray<const FStateTreeSharedEvent*, TInlineAllocator<8>> TransitionEvents;
if (Transition.Trigger == EStateTreeTransitionTrigger::OnEvent)
{
check(Transition.RequiredEvent.IsValid());
TConstArrayView<FStateTreeSharedEvent> EventsQueue = GetEventsToProcessView();
for (const FStateTreeSharedEvent& Event : EventsQueue)
{
check(Event.IsValid());
if (Transition.RequiredEvent.DoesEventMatchDesc(*Event))
{
TransitionEvents.Emplace(&Event);
}
}
}
else if (EnumHasAnyFlags(Transition.Trigger, EStateTreeTransitionTrigger::OnTick))
{
// Dummy event to make sure we iterate to loop below once.
TransitionEvents.Emplace(nullptr);
}
else if (EnumHasAnyFlags(Transition.Trigger, EStateTreeTransitionTrigger::OnDelegate))
{
if (Storage.IsDelegateBroadcasted(Transition.RequiredDelegateDispatcher))
{
// Dummy event to make sure we iterate to loop below once.
TransitionEvents.Emplace(nullptr);
}
}
else
{
ensureMsgf(false, TEXT("The trigger type is not supported."));
}
for (const FStateTreeSharedEvent* TransitionEvent : TransitionEvents)
{
bool bPassed = false;
{
FCurrentlyProcessedTransitionEventScope TransitionEventScope(*this, TransitionEvent ? TransitionEvent->Get() : nullptr);
UE_STATETREE_DEBUG_TRANSITION_EVENT(this, FStateTreeTransitionSource(CurrentFrame.StateTree, FStateTreeIndex16(TransitionIndex), Transition.State, Transition.Priority), EStateTreeTraceEventType::OnEvaluating);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::TransitionConditions);
bPassed = TestAllConditionsOnActiveInstances(CurrentParentFrame, CurrentFrame, Handler.StateHandle, Transition.ConditionsBegin, Transition.ConditionsNum);
}
if (bPassed)
{
// If the transitions is delayed, set up the delay.
if (Transition.HasDelay())
{
uint32 TransitionEventHash = 0u;
if (TransitionEvent && TransitionEvent->IsValid())
{
TransitionEventHash = GetTypeHash(*TransitionEvent->Get());
}
const bool bIsDelayedTransitionExisting = Exec.DelayedTransitions.ContainsByPredicate([CurrentStateID = Handler.StateID, TransitionIndex, TransitionEventHash](const FStateTreeTransitionDelayedState& DelayedState)
{
return DelayedState.StateID == CurrentStateID
&& DelayedState.TransitionIndex.Get() == TransitionIndex
&& DelayedState.CapturedEventHash == TransitionEventHash;
});
if (!bIsDelayedTransitionExisting)
{
// Initialize new delayed transition.
const float DelayDuration = Transition.Delay.GetRandomDuration(Exec.RandomStream);
if (DelayDuration > 0.0f)
{
FStateTreeTransitionDelayedState& DelayedState = Exec.DelayedTransitions.AddDefaulted_GetRef();
DelayedState.StateID = Handler.StateID;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
DelayedState.StateTree = CurrentFrame.StateTree;
DelayedState.StateHandle = Handler.StateHandle;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
DelayedState.TransitionIndex = FStateTreeIndex16(TransitionIndex);
DelayedState.TimeLeft = DelayDuration;
if (TransitionEvent && TransitionEvent->IsValid())
{
DelayedState.CapturedEvent = *TransitionEvent;
DelayedState.CapturedEventHash = TransitionEventHash;
}
BeginDelayedTransition(DelayedState);
STATETREE_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);
// Delay state added, skip requesting the transition.
continue;
}
// Fallthrough to request transition if duration was zero.
}
else
{
// We get here if the transitions re-triggers during the delay, on which case we'll just ignore it.
continue;
}
}
UE_STATETREE_DEBUG_TRANSITION_EVENT(this, FStateTreeTransitionSource(CurrentFrame.StateTree, FStateTreeIndex16(TransitionIndex), Transition.State, Transition.Priority), EStateTreeTraceEventType::OnRequesting);
if (RequestTransition(CurrentFrame, Transition.State, Transition.Priority, TransitionEvent, Transition.Fallback))
{
// If the transition was successfully requested with a specific event, consume and remove the event, it's been used.
if (TransitionEvent && Transition.bConsumeEventOnSelect)
{
ConsumeEvent(*TransitionEvent);
}
NextTransitionSource = FStateTreeTransitionSource(CurrentFrame.StateTree, FStateTreeIndex16(TransitionIndex), Transition.State, Transition.Priority);
break;
}
}
}
}
}
}
// All events have had the change to be reacted to, clear the event queue (if this instance owns it).
if (InstanceData.IsOwningEventQueue() && EventQueue)
{
EventQueue->Reset();
}
Storage.ResetBroadcastedDelegates();
//
// Check state completion transitions.
//
bool bProcessSubTreeCompletion = true;
if (NextTransition.Priority == EStateTreeTransitionPriority::None
&& (Exec.LastTickStatus != EStateTreeRunStatus::Running || Exec.bHasPendingCompletedState))
{
// Find the pending completed frame/state. Don't cache the result because this function is reentrant.
//Stop at the first completion.
int32 FrameIndexToStart = -1;
int32 StateIndexToStart = -1;
EStateTreeRunStatus CurrentStatus = EStateTreeRunStatus::Unset;
for (int32 FrameIndex = 0; FrameIndex < Exec.ActiveFrames.Num(); ++FrameIndex)
{
using namespace UE::StateTree;
const FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
const ETaskCompletionStatus FrameTasksStatus = CurrentFrame.ActiveTasksStatus.GetStatus(CurrentStateTree).GetCompletionStatus();
if (FrameTasksStatus != ETaskCompletionStatus::Running)
{
if (FrameIndex == 0)
{
// If first frame, then complete the tree execution.
Exec.RequestedStop = ExecutionContext::GetPriorityRunStatus(Exec.RequestedStop, ExecutionContext::CastToRunStatus(FrameTasksStatus));
break;
}
else if (ExecutionContext::Private::bGlobalTasksCompleteOwningFrame)
{
const int32 ParentFrameIndex = FrameIndex - 1;
FStateTreeExecutionFrame& ParentFrame = Exec.ActiveFrames[ParentFrameIndex];
const FStateTreeStateHandle ParentLinkedState = ParentFrame.ActiveStates.Last();
if (ensure(ParentLinkedState.IsValid()))
{
// Set the parent linked state as last completed state, and update tick status to the status from the transition.
STATETREE_LOG(Verbose, TEXT("Completed subtree '%s' from global: %s"),
*GetSafeStateName(ParentFrame, ParentLinkedState),
*UEnum::GetDisplayValueAsText(ExecutionContext::CastToRunStatus(FrameTasksStatus)).ToString()
);
const FCompactStateTreeState& State = ParentFrame.StateTree->States[ParentLinkedState.Index];
ParentFrame.ActiveTasksStatus.GetStatus(State).SetCompletionStatus(FrameTasksStatus);
Exec.bHasPendingCompletedState = true;
CurrentStatus = ExecutionContext::CastToRunStatus(FrameTasksStatus);
FrameIndexToStart = ParentFrameIndex;
StateIndexToStart = ParentFrame.ActiveStates.Num() - 1;
break;
}
}
}
for (int32 StateIndex = 0; StateIndex < CurrentFrame.ActiveStates.Num(); ++StateIndex)
{
const FStateTreeStateHandle CurrentHandle = CurrentFrame.ActiveStates[StateIndex];
const FCompactStateTreeState& State = CurrentStateTree->States[CurrentHandle.Index];
const UE::StateTree::ETaskCompletionStatus StateTasksStatus = CurrentFrame.ActiveTasksStatus.GetStatus(State).GetCompletionStatus();
if (StateTasksStatus != UE::StateTree::ETaskCompletionStatus::Running)
{
CurrentStatus = ExecutionContext::CastToRunStatus(StateTasksStatus);
FrameIndexToStart = FrameIndex;
StateIndexToStart = StateIndex;
break;
}
}
if (CurrentStatus != EStateTreeRunStatus::Unset)
{
break;
}
}
if (CurrentStatus != EStateTreeRunStatus::Unset)
{
const bool bIsCurrentStatusSucceeded = CurrentStatus == EStateTreeRunStatus::Succeeded;
const bool bIsCurrentStatusFailed = CurrentStatus == EStateTreeRunStatus::Failed;
const bool bIsCurrentStatusStopped = CurrentStatus == EStateTreeRunStatus::Stopped;
checkf(bIsCurrentStatusSucceeded || bIsCurrentStatusFailed || bIsCurrentStatusStopped, TEXT("Running is not accepted in the CurrentStatus loop."));
const EStateTreeTransitionTrigger CompletionTrigger = bIsCurrentStatusSucceeded ? EStateTreeTransitionTrigger::OnStateSucceeded : EStateTreeTransitionTrigger::OnStateFailed;
// Start from the last completed state and move up to the first state.
for (int32 FrameIndex = FrameIndexToStart; FrameIndex >= 0; --FrameIndex)
{
FStateTreeExecutionFrame* CurrentParentFrame = FrameIndex > 0 ? &Exec.ActiveFrames[FrameIndex - 1] : nullptr;
FStateTreeExecutionFrame& CurrentFrame = Exec.ActiveFrames[FrameIndex];
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
const int32 CurrentStateIndexToStart = FrameIndex == FrameIndexToStart ? StateIndexToStart : CurrentFrame.ActiveStates.Num() - 1;
// Check completion transitions
for (int32 StateIndex = CurrentStateIndexToStart; StateIndex >= 0; --StateIndex)
{
const FStateTreeStateHandle StateHandle = CurrentFrame.ActiveStates[StateIndex];
const FCompactStateTreeState& State = CurrentStateTree->States[StateHandle.Index];
if (State.ShouldTickCompletionTransitions(bIsCurrentStatusSucceeded, bIsCurrentStatusFailed))
{
FCurrentlyProcessedStateScope StateScope(*this, StateHandle);
UE_STATETREE_DEBUG_SCOPED_STATE_PHASE(this, StateHandle, EStateTreeUpdatePhase::TriggerTransitions);
for (uint8 TransitionCounter = 0; TransitionCounter < State.TransitionsNum; ++TransitionCounter)
{
// All transition conditions must pass
const int16 TransitionIndex = State.TransitionsBegin + TransitionCounter;
const FCompactStateTransition& Transition = CurrentStateTree->Transitions[TransitionIndex];
// Skip disabled transitions
if (!Transition.bTransitionEnabled)
{
continue;
}
const bool bTransitionAccepted = !bIsCurrentStatusStopped
? EnumHasAnyFlags(Transition.Trigger, CompletionTrigger)
: Transition.Trigger == EStateTreeTransitionTrigger::OnStateCompleted;
if (bTransitionAccepted)
{
bool bPassed = false;
{
UE_STATETREE_DEBUG_TRANSITION_EVENT(this, FStateTreeTransitionSource(CurrentFrame.StateTree, FStateTreeIndex16(TransitionIndex), Transition.State, Transition.Priority), EStateTreeTraceEventType::OnEvaluating);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::TransitionConditions);
bPassed = TestAllConditionsOnActiveInstances(CurrentParentFrame, CurrentFrame, StateHandle, Transition.ConditionsBegin, Transition.ConditionsNum);
}
if (bPassed)
{
// No delay allowed on completion conditions.
// No priority on completion transitions, use the priority to signal that state is selected.
UE_STATETREE_DEBUG_TRANSITION_EVENT(this, FStateTreeTransitionSource(CurrentFrame.StateTree, FStateTreeIndex16(TransitionIndex), Transition.State, Transition.Priority), EStateTreeTraceEventType::OnRequesting);
if (RequestTransition(CurrentFrame, Transition.State, EStateTreeTransitionPriority::Normal, /*TransitionEvent*/nullptr, Transition.Fallback))
{
NextTransitionSource = FStateTreeTransitionSource(CurrentFrame.StateTree, FStateTreeIndex16(TransitionIndex), Transition.State, Transition.Priority);
break;
}
}
}
}
if (NextTransition.Priority != EStateTreeTransitionPriority::None) //-V547
{
break;
}
}
}
// if a valid completion transition has already been found, the remaining transitions in parent frames won't have a higher priority than the found one
// so skip the remainder. this also prevented false positive warnings and ensures from STDebugger
if (NextTransition.Priority != EStateTreeTransitionPriority::None) //-V547
{
break;
}
}
// Handle the case where no transition was found.
if (NextTransition.Priority == EStateTreeTransitionPriority::None) //-V547
{
STATETREE_LOG(Verbose, TEXT("Could not trigger completion transition, jump back to root state."));
UE_STATETREE_DEBUG_LOG_EVENT(this, Warning, TEXT("Could not trigger completion transition, jump back to root state."));
check(!Exec.ActiveFrames.IsEmpty());
FStateTreeExecutionFrame& RootFrame = Exec.ActiveFrames[0];
FCurrentlyProcessedFrameScope RootFrameScope(*this, nullptr, RootFrame);
FCurrentlyProcessedStateScope RootStateScope(*this, FStateTreeStateHandle::Root);
if (RequestTransition(RootFrame, FStateTreeStateHandle::Root, EStateTreeTransitionPriority::Normal))
{
NextTransitionSource = FStateTreeTransitionSource(RootFrame.StateTree, EStateTreeTransitionSourceType::Internal, FStateTreeStateHandle::Root, EStateTreeTransitionPriority::Normal);
}
else
{
STATETREE_LOG(Warning, TEXT("Failed to select root state. Stopping the tree with failure."));
UE_STATETREE_DEBUG_LOG_EVENT(this, Error, TEXT("Failed to select root state. Stopping the tree with failure."));
SetupNextTransition(RootFrame, FStateTreeStateHandle::Failed, EStateTreeTransitionPriority::Critical);
// In this case we don't want to complete subtrees, we want to force the whole tree to stop.
bProcessSubTreeCompletion = false;
}
}
}
}
// Check if the transition was succeed/failed, if we're on a sub-tree, complete the subtree instead of transition.
if (NextTransition.TargetState.IsCompletionState() && bProcessSubTreeCompletion)
{
// Check that the transition source frame is a sub-tree, the first frame (0 index) is not a subtree.
const int32 SourceFrameIndex = Exec.IndexOfActiveFrame(NextTransition.SourceFrameID);
if (SourceFrameIndex > 0)
{
const FStateTreeExecutionFrame& SourceFrame = Exec.ActiveFrames[SourceFrameIndex];
const int32 ParentFrameIndex = SourceFrameIndex - 1;
FStateTreeExecutionFrame& ParentFrame = Exec.ActiveFrames[ParentFrameIndex];
const FStateTreeStateHandle ParentLinkedState = ParentFrame.ActiveStates.Last();
if (ParentLinkedState.IsValid())
{
const EStateTreeRunStatus RunStatus = NextTransition.TargetState.ToCompletionStatus();
#if ENABLE_VISUAL_LOG
const int32 NextTransitionSourceIndex = SourceFrame.ActiveStates.IndexOfReverse(NextTransition.SourceStateID);
const FStateTreeStateHandle NextTransitionSourceState = NextTransitionSourceIndex != INDEX_NONE
? SourceFrame.ActiveStates[NextTransitionSourceIndex]
: FStateTreeStateHandle::Invalid;
STATETREE_LOG(Verbose, TEXT("Completed subtree '%s' from state '%s': %s"),
*GetSafeStateName(ParentFrame, ParentLinkedState),
*GetSafeStateName(SourceFrame, NextTransitionSourceState),
*UEnum::GetDisplayValueAsText(RunStatus).ToString()
);
#endif
// Set the parent linked state as last completed state, and update tick status to the status from the transition.
const UE::StateTree::ETaskCompletionStatus TaskStatus = UE::StateTree::ExecutionContext::CastToTaskStatus(RunStatus);
const FCompactStateTreeState& State = ParentFrame.StateTree->States[ParentLinkedState.Index];
ParentFrame.ActiveTasksStatus.GetStatus(State).SetCompletionStatus(TaskStatus);
Exec.bHasPendingCompletedState = true;
Exec.LastTickStatus = RunStatus;
// Clear the transition and return that no transition took place.
// Since the LastTickStatus != running, the transition loop will try another transition
// now starting from the linked parent state. If we run out of retires in the selection loop (e.g. very deep hierarchy)
// we will continue on next tick.
TriggerTransitionsFromFrameIndex = ParentFrameIndex;
NextTransition = FStateTreeTransitionResult();
return false;
}
}
}
return NextTransition.TargetState.IsValid();
}
TOptional<FStateTreeTransitionResult> FStateTreeExecutionContext::MakeTransitionResult(const FRecordedStateTreeTransitionResult& RecordedTransition) const
{
FStateTreeTransitionResult Result;
for (int32 RecordedFrameIndex = 0; RecordedFrameIndex < RecordedTransition.NextActiveFrames.Num(); RecordedFrameIndex++)
{
const FRecordedStateTreeExecutionFrame& RecordedExecutionFrame = RecordedTransition.NextActiveFrames[RecordedFrameIndex];
if (RecordedExecutionFrame.StateTree == nullptr)
{
return {};
}
if (RecordedExecutionFrame.StateTree->GetStateFromHandle(RecordedExecutionFrame.RootState) == nullptr)
{
return {};
}
const FCompactStateTreeFrame* CompactFrame = RecordedExecutionFrame.StateTree->GetFrameFromHandle(RecordedExecutionFrame.RootState);
if (CompactFrame == nullptr)
{
return {};
}
FStateTreeExecutionFrame& ExecutionFrame = Result.NextActiveFrames.AddDefaulted_GetRef();
ExecutionFrame.StateTree = RecordedExecutionFrame.StateTree;
ExecutionFrame.RootState = RecordedExecutionFrame.RootState;
ExecutionFrame.ActiveStates = RecordedExecutionFrame.ActiveStates;
ExecutionFrame.ActiveTasksStatus = FStateTreeTasksCompletionStatus(*CompactFrame);
ExecutionFrame.bIsGlobalFrame = RecordedExecutionFrame.bIsGlobalFrame;
FStateTreeFrameStateSelectionEvents& StateTreeFrameStateSelectionEvents = Result.NextActiveFrameEvents.AddDefaulted_GetRef();
for (int32 EventIdx = 0; EventIdx < RecordedExecutionFrame.EventIndices.Num(); EventIdx++)
{
if (RecordedTransition.NextActiveFrameEvents.IsValidIndex(EventIdx))
{
const FStateTreeEvent& RecordedStateTreeEvent = RecordedTransition.NextActiveFrameEvents[EventIdx];
StateTreeFrameStateSelectionEvents.Events[EventIdx] = FStateTreeSharedEvent(RecordedStateTreeEvent);
}
}
}
if (Result.NextActiveFrames.Num() != Result.NextActiveFrameEvents.Num())
{
return {};
}
if (RecordedTransition.SourceStateTree == nullptr)
{
return {};
}
if (RecordedTransition.SourceStateTree->GetFrameFromHandle(RecordedTransition.SourceRootState) == nullptr)
{
return {};
}
// Try to find the same frame and the same state in the currently active frames.
// Recorded transitions can be saved and replayed out of context.
const FStateTreeExecutionState& Exec = GetExecState();
const FStateTreeExecutionFrame* ExecFrame = Exec.ActiveFrames.FindByPredicate([StateTree = RecordedTransition.SourceStateTree, RootState = RecordedTransition.SourceRootState](const FStateTreeExecutionFrame& Frame)
{
return Frame.StateTree == StateTree && Frame.RootState == RootState;
});
if (ExecFrame)
{
Result.SourceFrameID = ExecFrame->FrameID;
const int32 SourceStateIndex = ExecFrame->ActiveStates.IndexOfReverse(RecordedTransition.SourceState);
if (SourceStateIndex != INDEX_NONE)
{
Result.SourceStateID = ExecFrame->ActiveStates.StateIDs[SourceStateIndex];
}
}
Result.TargetState = RecordedTransition.TargetState;
Result.Priority = RecordedTransition.Priority;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
Result.SourceState = RecordedTransition.SourceState;
Result.SourceStateTree = RecordedTransition.SourceStateTree;
Result.SourceRootState = RecordedTransition.SourceRootState;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
return Result;
}
FRecordedStateTreeTransitionResult FStateTreeExecutionContext::MakeRecordedTransitionResult(const FStateTreeTransitionResult& Transition) const
{
check(Transition.NextActiveFrames.Num() == Transition.NextActiveFrameEvents.Num());
FRecordedStateTreeTransitionResult Result;
for (int32 FrameIndex = 0; FrameIndex < Transition.NextActiveFrames.Num(); FrameIndex++)
{
const FStateTreeExecutionFrame& ExecutionFrame = Transition.NextActiveFrames[FrameIndex];
const FStateTreeFrameStateSelectionEvents& StateSelectionEvents = Transition.NextActiveFrameEvents[FrameIndex];
FRecordedStateTreeExecutionFrame& RecordedFrame = Result.NextActiveFrames.AddDefaulted_GetRef();
RecordedFrame.StateTree = ExecutionFrame.StateTree;
RecordedFrame.RootState = ExecutionFrame.RootState;
RecordedFrame.ActiveStates = ExecutionFrame.ActiveStates;
RecordedFrame.bIsGlobalFrame = ExecutionFrame.bIsGlobalFrame;
for (int32 StateIndex = 0; StateIndex < ExecutionFrame.ActiveStates.Num(); StateIndex++)
{
const FStateTreeEvent* Event = StateSelectionEvents.Events[StateIndex].Get();
if (Event)
{
const int32 EventIndex = Result.NextActiveFrameEvents.Add(*Event);
RecordedFrame.EventIndices[StateIndex] = static_cast<uint8>(EventIndex);
}
}
}
const FStateTreeExecutionState& Exec = GetExecState();
if (const FStateTreeExecutionFrame* FoundSourceFrame = Exec.FindActiveFrame(Transition.SourceFrameID))
{
Result.SourceStateTree = FoundSourceFrame->StateTree;
Result.SourceRootState = FoundSourceFrame->RootState;
const int32 ActiveStateIndex = FoundSourceFrame->ActiveStates.IndexOfReverse(Transition.SourceStateID);
if (ActiveStateIndex != INDEX_NONE)
{
Result.SourceState = FoundSourceFrame->ActiveStates[ActiveStateIndex];
}
}
Result.TargetState = Transition.TargetState;
Result.Priority = Transition.Priority;
return Result;
}
bool FStateTreeExecutionContext::SelectState(const FStateTreeExecutionFrame& CurrentFrame,
const FStateTreeStateHandle NextState,
FStateSelectionResult& OutSelectionResult,
const FStateTreeSharedEvent* TransitionEvent,
const EStateTreeSelectionFallback Fallback)
{
TGuardValue<const FStateSelectionResult*> GuardValue(CurrentSelectionResult, &OutSelectionResult);
const FStateTreeExecutionState& Exec = GetExecState();
if (Exec.ActiveFrames.IsEmpty())
{
STATETREE_LOG(Error, TEXT("%hs: SelectState can only be called on initialized tree. '%s' using StateTree '%s'."),
__FUNCTION__, *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return false;
}
if (!NextState.IsValid())
{
return false;
}
// Walk towards the root from current state.
TArray<FStateTreeStateHandle, TInlineAllocator<FStateTreeActiveStates::MaxStates>> PathToNextState;
FStateTreeStateHandle CurrState = NextState;
while (CurrState.IsValid())
{
if (PathToNextState.Num() == FStateTreeActiveStates::MaxStates)
{
STATETREE_LOG(Error, TEXT("%hs: Reached max execution depth when trying to select state %s from '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetSafeStateName(CurrentFrame, NextState), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(&RootStateTree));
return false;
}
// Store the states that are in between the 'NextState' and common ancestor.
PathToNextState.Push(CurrState);
CurrState = CurrentFrame.StateTree->States[CurrState.Index].Parent;
}
Algo::Reverse(PathToNextState);
const UStateTree* NextStateTree = CurrentFrame.StateTree;
const FStateTreeStateHandle NextRootState = PathToNextState[0];
// Find the frame that the next state belongs to.
int32 CurrentFrameIndex = INDEX_NONE;
int32 CurrentStateTreeIndex = INDEX_NONE;
for (int32 FrameIndex = Exec.ActiveFrames.Num() - 1; FrameIndex >= 0; FrameIndex--)
{
const FStateTreeExecutionFrame& Frame = Exec.ActiveFrames[FrameIndex];
if (Frame.StateTree == NextStateTree)
{
CurrentStateTreeIndex = 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 FStateTreeExecutionFrame* CurrentFrameInActiveFrames = nullptr;
if (CurrentFrameIndex != INDEX_NONE)
{
const int32 NumCommonFrames = CurrentFrameIndex + 1;
OutSelectionResult = FStateSelectionResult(MakeArrayView(Exec.ActiveFrames.GetData(), NumCommonFrames));
CurrentFrameInActiveFrames = &Exec.ActiveFrames[CurrentFrameIndex];
}
else if (CurrentStateTreeIndex != INDEX_NONE)
{
// If we could not find a common frame, we assume that we jumped to different subtree in same asset.
const int32 NumCommonFrames = CurrentStateTreeIndex + 1;
OutSelectionResult = FStateSelectionResult(MakeArrayView(Exec.ActiveFrames.GetData(), NumCommonFrames));
CurrentFrameInActiveFrames = &Exec.ActiveFrames[CurrentStateTreeIndex];
}
else
{
STATETREE_LOG(Error, TEXT("%hs: Encountered unrecognized state %s during state selection from '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetNameSafe(NextStateTree), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(NextStateTree));
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 = OutSelectionResult.FramesNum() - 1;
FStateTreeExecutionFrame& LastFrame = OutSelectionResult.GetSelectedFrames()[LastFrameIndex];
// Find index of the first state to be evaluated.
int32 FirstNewStateIndex = 0;
if (CurrentFrameIndex != INDEX_NONE)
{
// If LastFrame.ActiveStates is a subset of PathToNextState (e.g when someone use "TryEnter" selection behavior and then make a transition to it's child or if one is reentering the same state).
// In such case loop below won't break on anything and FirstNewStateIndex will be incorrectly 0, thus we initialize it to be right after the shorter range.
FirstNewStateIndex = FMath::Max(0, FMath::Min(PathToNextState.Num(), LastFrame.ActiveStates.Num()) - 1);
for (int32 Index = 0; Index < FMath::Min(PathToNextState.Num(), LastFrame.ActiveStates.Num()); ++Index)
{
if (LastFrame.ActiveStates[Index] != PathToNextState[Index])
{
FirstNewStateIndex = Index;
break;
}
}
}
ensureMsgf(LastFrame.ActiveStates.Num() >= FirstNewStateIndex, TEXT("ActiveTasksStatus won't be in sync with the amount of states."));
LastFrame.ActiveStates.SetNum(FirstNewStateIndex);
// Existing state's data is safe to access during select.
LastFrame.NumCurrentlyActiveStates = static_cast<uint8>(LastFrame.ActiveStates.Num());
FStateSelectionResult InitialSelection;
if (Fallback == EStateTreeSelectionFallback::NextSelectableSibling)
{
InitialSelection = OutSelectionResult;
}
// We take copy of the last frame and assign it later, as SelectStateInternal() might change the array and invalidate the pointer.
const FStateTreeExecutionFrame* CurrentParentFrame = LastFrameIndex > 0 ? &OutSelectionResult.GetSelectedFrames()[LastFrameIndex - 1] : nullptr;
// Path from the first new state up to the NextState
TConstArrayView<FStateTreeStateHandle> NewStatesPathToNextState(&PathToNextState[FirstNewStateIndex], PathToNextState.Num() - FirstNewStateIndex);
if (SelectStateInternal(CurrentParentFrame, OutSelectionResult.GetSelectedFrames()[LastFrameIndex], CurrentFrameInActiveFrames, NewStatesPathToNextState, OutSelectionResult, TransitionEvent))
{
return true;
}
// Failed to Select Next State, handle fallback here
// Return true on the first next sibling that gets selected successfully
if (Fallback == EStateTreeSelectionFallback::NextSelectableSibling && PathToNextState.Num() >= 2)
{
const FStateTreeStateHandle Parent = PathToNextState.Last(1);
if (Parent.IsValid())
{
const FCompactStateTreeState& ParentState = CurrentFrame.StateTree->States[Parent.Index];
uint16 ChildState = CurrentFrame.StateTree->States[NextState.Index].GetNextSibling();
for (; ChildState < ParentState.ChildrenEnd; ChildState = CurrentFrame.StateTree->States[ChildState].GetNextSibling())
{
FStateTreeStateHandle ChildStateHandle = FStateTreeStateHandle(ChildState);
// Start selection from blank slate.
OutSelectionResult = InitialSelection;
// We take copy of the last frame and assign it later, as SelectStateInternal() might change the array and invalidate the pointer.
CurrentParentFrame = LastFrameIndex > 0 ? &OutSelectionResult.GetSelectedFrames()[LastFrameIndex - 1] : nullptr;
if (SelectStateInternal(CurrentParentFrame, OutSelectionResult.GetSelectedFrames()[LastFrameIndex], CurrentFrameInActiveFrames, {ChildStateHandle}, OutSelectionResult))
{
return true;
}
}
}
}
return false;
}
bool FStateTreeExecutionContext::SelectStateInternal(
const FStateTreeExecutionFrame* CurrentParentFrame,
FStateTreeExecutionFrame& CurrentFrame,
const FStateTreeExecutionFrame* CurrentFrameInActiveFrames,
TConstArrayView<FStateTreeStateHandle> PathToNextState,
FStateSelectionResult& OutSelectionResult,
const FStateTreeSharedEvent* TransitionEvent)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_SelectState);
const FStateTreeExecutionState& Exec = GetExecState();
check(!PathToNextState.IsEmpty());
const FStateTreeStateHandle NextStateHandle = PathToNextState[0];
if (!NextStateHandle.IsValid())
{
// Trying to select non-existing state.
STATETREE_LOG(Error, TEXT("%hs: Trying to select invalid state from '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
return false;
}
FCurrentlyProcessedFrameScope FrameScope(*this, CurrentParentFrame, CurrentFrame);
FCurrentlyProcessedStateScope NextStateScope(*this, NextStateHandle);
FCurrentFrameStateSelectionEventsScope CapturedEventsScope(*this, OutSelectionResult.GetFramesStateSelectionEvents().Last());
const UStateTree* CurrentStateTree = CurrentFrame.StateTree;
const FCompactStateTreeState& NextState = CurrentStateTree->States[NextStateHandle.Index];
if (NextState.bEnabled == false)
{
// Do not select disabled state
STATETREE_LOG(VeryVerbose, TEXT("%hs: Ignoring disabled state '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
return false;
}
UE_STATETREE_DEBUG_SCOPED_STATE_PHASE(this, NextStateHandle, EStateTreeUpdatePhase::StateSelection);
// The state cannot be directly selected.
if (NextState.SelectionBehavior == EStateTreeStateSelectionBehavior::None)
{
return false;
}
const UStateTree* NextLinkedStateAsset = NextState.LinkedAsset;
// Look up linked state overrides
const FInstancedPropertyBag* NextLinkedStateParameterOverride = nullptr;
if (NextState.Type == EStateTreeStateType::LinkedAsset)
{
if (const FStateTreeReference* Override = GetLinkedStateTreeOverrideForTag(NextState.Tag))
{
NextLinkedStateAsset = Override->GetStateTree();
NextLinkedStateParameterOverride = &Override->GetParameters();
STATETREE_LOG(VeryVerbose, TEXT("%hs: In state '%s', overriding linked asset '%s' with '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle),
*GetFullNameSafe(NextState.LinkedAsset), *GetFullNameSafe(NextLinkedStateAsset),
*GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
}
}
if (NextState.ParameterDataHandle.IsValid())
{
// Instantiate state parameters if not done yet.
FStateTreeDataView NextStateParametersView = GetDataViewOrTemporary(CurrentParentFrame, CurrentFrame, NextState.ParameterDataHandle);
if (!NextStateParametersView.IsValid())
{
// Allocate temporary instance for parameters if the state has params.
// The subtree state selection below assumes that this creates always a valid temporary, we'll create the temp data even if parameters are empty.
// @todo: Empty params is valid and common case, we should not require to create empty parameters data (this needs to be handle in compiler and UpdateInstanceData too).
if (NextLinkedStateParameterOverride)
{
// Create from an override.
FStateTreeDataView TempStateParametersView = AddTemporaryInstance(CurrentFrame, FStateTreeIndex16::Invalid, NextState.ParameterDataHandle, FConstStructView(TBaseStructure<FCompactStateTreeParameters>::Get()));
check(TempStateParametersView.IsValid());
FCompactStateTreeParameters& StateParams = TempStateParametersView.GetMutable<FCompactStateTreeParameters>();
StateParams.Parameters = *NextLinkedStateParameterOverride;
NextStateParametersView = FStateTreeDataView(StateParams.Parameters.GetMutableValue());
}
else
{
// Create from template in the asset.
const FConstStructView DefaultStateParamsInstanceData = CurrentFrame.StateTree->DefaultInstanceData.GetStruct(NextState.ParameterTemplateIndex.Get());
FStateTreeDataView TempStateParametersView = AddTemporaryInstance(CurrentFrame, FStateTreeIndex16::Invalid, NextState.ParameterDataHandle, DefaultStateParamsInstanceData);
check(TempStateParametersView.IsValid());
FCompactStateTreeParameters& StateParams = TempStateParametersView.GetMutable<FCompactStateTreeParameters>();
NextStateParametersView = FStateTreeDataView(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.
// The copy can fail, if the overridden parameters do not match, this is by design.
CopyBatchWithValidation(CurrentParentFrame, CurrentFrame, NextStateParametersView, NextState.ParameterBindingsBatch);
}
}
const bool bIsDestinationState = PathToNextState.Num() < 2;
const bool bShouldPrerequisitesBeChecked = bIsDestinationState || NextState.bCheckPrerequisitesWhenActivatingChildDirectly;
TArray<const FStateTreeSharedEvent*, TInlineAllocator<FStateTreeEventQueue::MaxActiveEvents>> StateSelectionEvents;
if (NextState.EventDataIndex.IsValid())
{
check(NextState.RequiredEventToEnter.IsValid());
// Use the same event as performed transition unless it didn't lead to this state as only state selected by the transition should get it's event.
if (TransitionEvent && TransitionEvent->IsValid() && bIsDestinationState)
{
if (NextState.RequiredEventToEnter.DoesEventMatchDesc(*TransitionEvent->Get()))
{
StateSelectionEvents.Emplace(TransitionEvent);
}
}
else
{
TArrayView<FStateTreeSharedEvent> EventsQueue = GetMutableEventsToProcessView();
for (FStateTreeSharedEvent& Event : EventsQueue)
{
check(Event.IsValid());
if (NextState.RequiredEventToEnter.DoesEventMatchDesc(*Event))
{
StateSelectionEvents.Emplace(&Event);
}
}
// Couldn't find matching state's event, but it's marked as not required. Adding an empty event which allows us to continue the state selection.
if (!bShouldPrerequisitesBeChecked && StateSelectionEvents.IsEmpty())
{
StateSelectionEvents.Emplace();
}
}
if (StateSelectionEvents.IsEmpty())
{
return false;
}
}
else
{
StateSelectionEvents.Emplace();
}
// Activate/Push a new state
if (!CurrentFrame.ActiveStates.Push(NextStateHandle, UE::StateTree::FActiveStateID(Storage.GenerateUniqueId())))
{
STATETREE_LOG(Error, TEXT("%hs: Reached max execution depth when trying to select state %s from '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
return false;
}
CurrentFrame.ActiveTasksStatus.Push(NextState);
// 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 FStateTreeStateHandle MatchingActiveHandle = CurrentFrameInActiveFrames->ActiveStates.GetStateSafe(CurrentStateIndex);
if (MatchingActiveHandle == NextStateHandle)
{
CurrentFrame.NumCurrentlyActiveStates = static_cast<uint8>(CurrentFrame.ActiveStates.Num());
}
}
bool bSucceededToSelectState = false;
for (const FStateTreeSharedEvent* StateSelectionEvent : StateSelectionEvents)
{
if (StateSelectionEvent)
{
CurrentlyProcessedStateSelectionEvents->Events[NextState.Depth] = *StateSelectionEvent;
}
if (bShouldPrerequisitesBeChecked)
{
// Check that the state can be entered
UE_STATETREE_DEBUG_ENTER_PHASE(this, EStateTreeUpdatePhase::EnterConditions);
const bool bEnterConditionsPassed = TestAllConditionsWithValidation(CurrentParentFrame, CurrentFrame, NextStateHandle, NextState.EnterConditionsBegin, NextState.EnterConditionsNum);
UE_STATETREE_DEBUG_EXIT_PHASE(this, EStateTreeUpdatePhase::EnterConditions);
if (!bEnterConditionsPassed)
{
continue;
}
}
if (!bIsDestinationState)
{
// Next child state is already known. Passing TransitionEvent further so state selected directly by transition can use it.
if (SelectStateInternal(CurrentParentFrame, CurrentFrame, CurrentFrameInActiveFrames, PathToNextState.Mid(1), OutSelectionResult, TransitionEvent))
{
bSucceededToSelectState = true;
break;
}
}
else if (NextState.Type == EStateTreeStateType::Linked)
{
if (NextState.LinkedState.IsValid())
{
if (OutSelectionResult.IsFull())
{
STATETREE_LOG(Error, TEXT("%hs: Reached max execution depth when trying to select state %s from '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
break;
}
FStateTreeExecutionFrame NewFrame;
NewFrame.StateTree = CurrentFrame.StateTree;
NewFrame.RootState = NextState.LinkedState;
NewFrame.ExternalDataBaseIndex = CurrentFrame.ExternalDataBaseIndex;
// Check and prevent recursion.
const bool bNewFrameAlreadySelected = OutSelectionResult.GetSelectedFrames().ContainsByPredicate([&NewFrame](const FStateTreeExecutionFrame& Frame) {
return Frame.IsSameFrame(NewFrame);
});
if (bNewFrameAlreadySelected)
{
STATETREE_LOG(Error, TEXT("%hs: Trying to recursively enter subtree '%s' from '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetSafeStateName(NewFrame, NewFrame.RootState), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
break;
}
// If the Frame already exists, copy instance indices so that conditions that rely on active states work correctly.
const FStateTreeExecutionFrame* ExistingFrame = Exec.ActiveFrames.FindByPredicate(
[StateTree = NewFrame.StateTree, RootState = NewFrame.RootState](const FStateTreeExecutionFrame& Frame)
{
return Frame.StateTree == StateTree && Frame.RootState == RootState;
});
if (ExistingFrame)
{
NewFrame.FrameID = ExistingFrame->FrameID;
NewFrame.ActiveTasksStatus = ExistingFrame->ActiveTasksStatus;
NewFrame.ActiveInstanceIndexBase = ExistingFrame->ActiveInstanceIndexBase;
NewFrame.GlobalInstanceIndexBase = ExistingFrame->GlobalInstanceIndexBase;
NewFrame.StateParameterDataHandle = ExistingFrame->StateParameterDataHandle;
NewFrame.GlobalParameterDataHandle = ExistingFrame->GlobalParameterDataHandle;
}
else
{
NewFrame.FrameID = UE::StateTree::FActiveFrameID(Storage.GenerateUniqueId());
const FCompactStateTreeFrame* FrameInfo = NewFrame.StateTree->GetFrameFromHandle(NewFrame.RootState);
ensureAlwaysMsgf(FrameInfo, TEXT("The compiled data is invalid. It should contains the information for the new root frame."));
NewFrame.ActiveTasksStatus = FrameInfo ? FStateTreeTasksCompletionStatus(*FrameInfo) : FStateTreeTasksCompletionStatus();
// Since the StateTree 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.
}
OutSelectionResult.PushFrame(NewFrame);
// If State is linked, proceed to the linked state.
if (SelectStateInternal(&CurrentFrame, OutSelectionResult.GetSelectedFrames().Last(), ExistingFrame, {NewFrame.RootState}, OutSelectionResult))
{
bSucceededToSelectState = true;
break;
}
OutSelectionResult.PopFrame();
}
else
{
STATETREE_LOG(Warning, TEXT("%hs: Trying to enter invalid linked subtree from '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
}
}
else if (NextState.Type == EStateTreeStateType::LinkedAsset)
{
if (NextLinkedStateAsset == nullptr || NextLinkedStateAsset->States.Num() == 0)
{
break;
}
if (OutSelectionResult.IsFull())
{
STATETREE_LOG(Error, TEXT("%hs: Reached max execution depth when trying to select state %s from '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
break;
}
// The linked state tree should have compatible context requirements.
if (!NextLinkedStateAsset->HasCompatibleContextData(RootStateTree)
|| NextLinkedStateAsset->GetSchema()->GetClass() != RootStateTree.GetSchema()->GetClass())
{
STATETREE_LOG(Error, TEXT("%hs: The linked State Tree '%s' does not have compatible schema, trying to select state %s from '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetFullNameSafe(NextLinkedStateAsset), *GetSafeStateName(CurrentFrame, NextStateHandle), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
break;
}
FStateTreeExecutionFrame NewFrame;
NewFrame.StateTree = NextLinkedStateAsset;
NewFrame.RootState = FStateTreeStateHandle::Root;
NewFrame.bIsGlobalFrame = true;
// Check and prevent recursion.
const bool bNewFrameAlreadySelected = OutSelectionResult.GetSelectedFrames().ContainsByPredicate([&NewFrame](const FStateTreeExecutionFrame& Frame) {
return Frame.IsSameFrame(NewFrame);
});
if (bNewFrameAlreadySelected)
{
STATETREE_LOG(Error, TEXT("%hs: Trying to recursively enter subtree '%s' from '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetSafeStateName(NewFrame, NewFrame.RootState), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
break;
}
// If the Frame already exists, copy instance indices so that conditions that rely on active states work correctly.
const FStateTreeExecutionFrame* ExistingFrame = Exec.ActiveFrames.FindByPredicate(
[StateTree = NewFrame.StateTree, RootState = NewFrame.RootState](const FStateTreeExecutionFrame& Frame)
{
return Frame.StateTree == StateTree && Frame.RootState == RootState;
});
bool bStartedTemporaryEvaluatorsAndGlobalTasks = false;
if (ExistingFrame)
{
NewFrame.FrameID = ExistingFrame->FrameID;
NewFrame.ActiveTasksStatus = ExistingFrame->ActiveTasksStatus;
NewFrame.ActiveInstanceIndexBase = ExistingFrame->ActiveInstanceIndexBase;
NewFrame.GlobalInstanceIndexBase = ExistingFrame->GlobalInstanceIndexBase;
NewFrame.StateParameterDataHandle = ExistingFrame->StateParameterDataHandle;
NewFrame.GlobalParameterDataHandle = ExistingFrame->GlobalParameterDataHandle;
NewFrame.ExternalDataBaseIndex = ExistingFrame->ExternalDataBaseIndex;
}
else
{
NewFrame.FrameID = UE::StateTree::FActiveFrameID(Storage.GenerateUniqueId());
const FCompactStateTreeFrame* FrameInfo = NewFrame.StateTree->GetFrameFromHandle(NewFrame.RootState);
ensureMsgf(FrameInfo, TEXT("The compiled data is invalid. It should contains the information for the root frame."));
NewFrame.ActiveTasksStatus = FrameInfo ? FStateTreeTasksCompletionStatus(*FrameInfo) : FStateTreeTasksCompletionStatus();
// 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.StateTree);
if (!NewFrame.ExternalDataBaseIndex.IsValid())
{
STATETREE_LOG(VeryVerbose, TEXT("%hs: Cannot select state '%s' because failed to collect external data for nested tree '%s'. '%s' using StateTree '%s'."),
__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetFullNameSafe(NewFrame.StateTree), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
break;
}
// The state parameters will be from the root state.
const FCompactStateTreeState& RootState = NewFrame.StateTree->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(&CurrentFrame, NewFrame) != EStateTreeRunStatus::Running)
{
STATETREE_LOG(VeryVerbose, TEXT("%hs: Cannot select state '%s' because cannot start nested tree's '%s' global tasks and evaluators. '%s' using StateTree '%s'."),
__FUNCTION__, *GetSafeStateName(CurrentFrame, NextStateHandle), *GetFullNameSafe(NewFrame.StateTree), *GetNameSafe(&Owner), *GetFullNameSafe(CurrentFrame.StateTree));
StopTemporaryEvaluatorsAndGlobalTasks(nullptr, NewFrame);
GetExecState().DelegateActiveListeners.RemoveAll(NewFrame.FrameID);
break;
}
bStartedTemporaryEvaluatorsAndGlobalTasks = true;
}
OutSelectionResult.PushFrame(NewFrame);
// If State is linked, proceed to the linked state.
if (SelectStateInternal(&CurrentFrame, OutSelectionResult.GetSelectedFrames().Last(), ExistingFrame, {NewFrame.RootState}, OutSelectionResult))
{
bSucceededToSelectState = true;
break;
}
if (bStartedTemporaryEvaluatorsAndGlobalTasks)
{
StopTemporaryEvaluatorsAndGlobalTasks(&CurrentFrame, NewFrame);
GetExecState().DelegateActiveListeners.RemoveAll(NewFrame.FrameID);
}
OutSelectionResult.PopFrame();
}
else if (NextState.SelectionBehavior == EStateTreeStateSelectionBehavior::TryEnterState)
{
// Select this state.
UE_STATETREE_DEBUG_STATE_EVENT(this, NextStateHandle, EStateTreeTraceEventType::OnStateSelected);
bSucceededToSelectState = true;
break;
}
else if (NextState.SelectionBehavior == EStateTreeStateSelectionBehavior::TryFollowTransitions)
{
UE_STATETREE_DEBUG_SCOPED_STATE_PHASE(this, NextStateHandle, EStateTreeUpdatePhase::TrySelectBehavior);
EStateTreeTransitionPriority CurrentPriority = EStateTreeTransitionPriority::None;
for (uint8 i = 0; i < NextState.TransitionsNum; i++)
{
const int16 TransitionIndex = NextState.TransitionsBegin + i;
const FCompactStateTransition& Transition = CurrentStateTree->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, EStateTreeTransitionTrigger::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))
{
STATETREE_LOG(Error, TEXT("%hs: Loop detected when trying to select state %s from '%s'. Prior states: %s. '%s' using StateTree '%s'.")
, __FUNCTION__
, *GetSafeStateName(CurrentFrame, NextStateHandle)
, *GetStateStatusString(Exec)
, *DebugGetStatePath(OutSelectionResult.GetSelectedFrames(), &CurrentFrame)
, *GetNameSafe(&Owner)
, *GetFullNameSafe(CurrentFrame.StateTree));
continue;
}
TArray<const FStateTreeSharedEvent*, TInlineAllocator<FStateTreeEventQueue::MaxActiveEvents>> SelectedStateTransitionEvents;
if (Transition.Trigger == EStateTreeTransitionTrigger::OnEvent)
{
check(Transition.RequiredEvent.IsValid());
if (StateSelectionEvent)
{
SelectedStateTransitionEvents.Emplace(StateSelectionEvent);
}
else
{
TArrayView<FStateTreeSharedEvent> EventsQueue = GetMutableEventsToProcessView();
for (FStateTreeSharedEvent& Event : EventsQueue)
{
check(Event.IsValid());
if (Transition.RequiredEvent.DoesEventMatchDesc(*Event))
{
SelectedStateTransitionEvents.Emplace(&Event);
}
}
}
}
else if (EnumHasAnyFlags(Transition.Trigger, EStateTreeTransitionTrigger::OnTick))
{
SelectedStateTransitionEvents.Emplace();
}
else if (EnumHasAnyFlags(Transition.Trigger, EStateTreeTransitionTrigger::OnDelegate))
{
if (Storage.IsDelegateBroadcasted(Transition.RequiredDelegateDispatcher))
{
SelectedStateTransitionEvents.Emplace();
}
}
else
{
ensureMsgf(false, TEXT("Missing a transition trigger type."));
}
for (const FStateTreeSharedEvent* SelectedStateTransitionEvent : SelectedStateTransitionEvents)
{
bool bTransitionConditionsPassed = false;
{
FCurrentlyProcessedTransitionEventScope TransitionEventScope(*this, SelectedStateTransitionEvent ? SelectedStateTransitionEvent->Get() : nullptr);
UE_STATETREE_DEBUG_TRANSITION_EVENT(this, FStateTreeTransitionSource(CurrentFrame.StateTree, FStateTreeIndex16(TransitionIndex), Transition.State, Transition.Priority), EStateTreeTraceEventType::OnEvaluating);
UE_STATETREE_DEBUG_SCOPED_PHASE(this, EStateTreeUpdatePhase::TransitionConditions);
bTransitionConditionsPassed = TestAllConditionsWithValidation(CurrentParentFrame, CurrentFrame, NextStateHandle, 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.
FStateSelectionResult StateSelectionResult;
if (SelectState(CurrentFrame, Transition.State, StateSelectionResult, SelectedStateTransitionEvent, Transition.Fallback))
{
// Selection succeeded.
// Cannot break yet because higher priority transitions may override the selection.
OutSelectionResult = StateSelectionResult;
CurrentPriority = Transition.Priority;
break;
}
}
}
}
if (CurrentPriority != EStateTreeTransitionPriority::None)
{
bSucceededToSelectState = true;
break;
}
}
else if (NextState.SelectionBehavior == EStateTreeStateSelectionBehavior::TrySelectChildrenInOrder)
{
if (NextState.HasChildren())
{
UE_STATETREE_DEBUG_SCOPED_STATE_PHASE(this, NextStateHandle, EStateTreeUpdatePhase::TrySelectBehavior);
// If the state has children, proceed to select children.
for (uint16 ChildState = NextState.ChildrenBegin; ChildState < NextState.ChildrenEnd; ChildState = CurrentStateTree->States[ChildState].GetNextSibling())
{
if (SelectStateInternal(CurrentParentFrame, CurrentFrame, CurrentFrameInActiveFrames, {FStateTreeStateHandle(ChildState)}, OutSelectionResult))
{
// Selection succeeded
bSucceededToSelectState = true;
break;
}
}
if (bSucceededToSelectState)
{
break;
}
}
else
{
// Select this state (For backwards compatibility)
UE_STATETREE_DEBUG_STATE_EVENT(this, NextStateHandle, EStateTreeTraceEventType::OnStateSelected);
bSucceededToSelectState = true;
break;
}
}
else if (NextState.SelectionBehavior == EStateTreeStateSelectionBehavior::TrySelectChildrenAtRandom)
{
if (NextState.HasChildren())
{
UE_STATETREE_DEBUG_SCOPED_STATE_PHASE(this, NextStateHandle, EStateTreeUpdatePhase::TrySelectBehavior);
TArray<uint16, TInlineAllocator<8>> NextLevelChildStates;
for (uint16 ChildState = NextState.ChildrenBegin; ChildState < NextState.ChildrenEnd; ChildState = CurrentStateTree->States[ChildState].GetNextSibling())
{
NextLevelChildStates.Push(ChildState);
}
while (!NextLevelChildStates.IsEmpty())
{
const int32 ChildStateIndex = Exec.RandomStream.RandRange(0, NextLevelChildStates.Num() - 1);
if (SelectStateInternal(CurrentParentFrame, CurrentFrame, CurrentFrameInActiveFrames,
{FStateTreeStateHandle(NextLevelChildStates[ChildStateIndex])}, OutSelectionResult))
{
// Selection succeeded
bSucceededToSelectState = true;
break;
}
constexpr EAllowShrinking AllowShrinking = EAllowShrinking::No;
NextLevelChildStates.RemoveAtSwap(ChildStateIndex, AllowShrinking);
}
if (bSucceededToSelectState)
{
break;
}
}
else
{
// Select this state (For backwards compatibility)
UE_STATETREE_DEBUG_STATE_EVENT(this, NextStateHandle, EStateTreeTraceEventType::OnStateSelected);
bSucceededToSelectState = true;
break;
}
}
else if (NextState.SelectionBehavior == EStateTreeStateSelectionBehavior::TrySelectChildrenWithHighestUtility)
{
if (NextState.HasChildren())
{
UE_STATETREE_DEBUG_SCOPED_STATE_PHASE(this, NextStateHandle, EStateTreeUpdatePhase::TrySelectBehavior);
TArray<uint16, TInlineAllocator<8>> NextLevelChildStates;
for (uint16 ChildState = NextState.ChildrenBegin; ChildState < NextState.ChildrenEnd; ChildState = CurrentStateTree->States[ChildState].GetNextSibling())
{
NextLevelChildStates.Push(ChildState);
}
while (!NextLevelChildStates.IsEmpty())
{
//Find one with highest score in the remaining candidates
float HighestScore = -std::numeric_limits<float>::infinity();;
uint16 StateIndexWithHighestScore = FStateTreeStateHandle::InvalidIndex;
int32 ArrayIndexWithHighestScore = INDEX_NONE;
for (int32 Index = 0; Index < NextLevelChildStates.Num(); ++Index)
{
const uint16 CurrentStateIndex = NextLevelChildStates[Index];
const FCompactStateTreeState& CurrentState = CurrentStateTree->States[CurrentStateIndex];
const FStateTreeStateHandle CurrentStateHandle = FStateTreeStateHandle(CurrentStateIndex);
const float Score = EvaluateUtilityWithValidation(CurrentParentFrame, CurrentFrame, CurrentStateHandle, CurrentState.UtilityConsiderationsBegin, CurrentState.UtilityConsiderationsNum, CurrentState.Weight);
if (Score > HighestScore)
{
HighestScore = Score;
StateIndexWithHighestScore = CurrentStateIndex;
ArrayIndexWithHighestScore = Index;
}
}
if (FStateTreeStateHandle::IsValidIndex(StateIndexWithHighestScore))
{
if (SelectStateInternal(CurrentParentFrame, CurrentFrame, CurrentFrameInActiveFrames, { FStateTreeStateHandle(StateIndexWithHighestScore) }, OutSelectionResult))
{
// Selection succeeded
bSucceededToSelectState = true;
break;
}
// Disqualify the state we failed to enter
NextLevelChildStates.RemoveAtSwap(ArrayIndexWithHighestScore, EAllowShrinking::No);
}
else
{
// No states in array were valid
break;
}
}
if (bSucceededToSelectState)
{
break;
}
}
else
{
// Select this state (For backwards compatibility)
UE_STATETREE_DEBUG_STATE_EVENT(this, NextStateHandle, EStateTreeTraceEventType::OnStateSelected);
bSucceededToSelectState = true;
break;
}
}
else if (NextState.SelectionBehavior == EStateTreeStateSelectionBehavior::TrySelectChildrenAtRandomWeightedByUtility)
{
if (NextState.HasChildren())
{
TArray<TTuple<uint16, float>, TInlineAllocator<8>> NextLevelChildStates;
float TotalScore = .0f;
for (uint16 CurrentStateIndex = NextState.ChildrenBegin; CurrentStateIndex < NextState.ChildrenEnd; CurrentStateIndex = CurrentStateTree->States[CurrentStateIndex].GetNextSibling())
{
const FCompactStateTreeState& CurrentState = CurrentStateTree->States[CurrentStateIndex];
const FStateTreeStateHandle CurrentStateHandle = FStateTreeStateHandle(CurrentStateIndex);
const float CurrentStateScore = EvaluateUtilityWithValidation(CurrentParentFrame, CurrentFrame, CurrentStateHandle, CurrentState.UtilityConsiderationsBegin, CurrentState.UtilityConsiderationsNum, CurrentState.Weight);
NextLevelChildStates.Emplace(CurrentStateIndex, CurrentStateScore);
TotalScore += CurrentStateScore;
}
while (!NextLevelChildStates.IsEmpty())
{
const float RandomScore = Exec.RandomStream.FRand() * TotalScore;
float AccumulatedScore = .0f;
for (int32 Index = 0; Index < NextLevelChildStates.Num(); ++Index)
{
const TTuple<uint16, float>& StateScorePair = NextLevelChildStates[Index];
const uint16 StateIndex = StateScorePair.Key;
const float StateScore = StateScorePair.Value;
AccumulatedScore += StateScore;
if (RandomScore < AccumulatedScore || (Index == (NextLevelChildStates.Num() - 1)))
{
// States with zero possibility won't be selected
if (StateScore != 0.f && SelectStateInternal(CurrentParentFrame, CurrentFrame, CurrentFrameInActiveFrames, { FStateTreeStateHandle(StateIndex) }, OutSelectionResult))
{
// Selection succeeded
bSucceededToSelectState = true;
break;
}
//Disqualify the state we failed to enter, and restart the loop
TotalScore -= StateScore;
constexpr EAllowShrinking AllowShrinking = EAllowShrinking::No;
NextLevelChildStates.RemoveAtSwap(Index, AllowShrinking);
break;
}
}
if (bSucceededToSelectState)
{
break;
}
}
if (bSucceededToSelectState)
{
break;
}
}
else
{
// Select this state (For backwards compatibility)
UE_STATETREE_DEBUG_STATE_EVENT(this, NextStateHandle, EStateTreeTraceEventType::OnStateSelected);
bSucceededToSelectState = true;
break;
}
}
}
if (!bSucceededToSelectState)
{
// State could not be selected, restore.
CurrentFrame.NumCurrentlyActiveStates = PrevNumCurrentlyActiveStates;
CurrentFrame.ActiveStates.Pop();
}
return bSucceededToSelectState;
}
FString FStateTreeExecutionContext::GetSafeStateName(const FStateTreeExecutionFrame& CurrentFrame, const FStateTreeStateHandle State) const
{
if (State == FStateTreeStateHandle::Invalid)
{
return TEXT("(State Invalid)");
}
else if (State == FStateTreeStateHandle::Succeeded)
{
return TEXT("(State Succeeded)");
}
else if (State == FStateTreeStateHandle::Failed)
{
return TEXT("(State Failed)");
}
else if (CurrentFrame.StateTree && CurrentFrame.StateTree->States.IsValidIndex(State.Index))
{
return *CurrentFrame.StateTree->States[State.Index].Name.ToString();
}
return TEXT("(Unknown)");
}
FString FStateTreeExecutionContext::DebugGetStatePath(TConstArrayView<FStateTreeExecutionFrame> ActiveFrames, const FStateTreeExecutionFrame* CurrentFrame, const int32 ActiveStateIndex) const
{
FString StatePath;
const UStateTree* LastStateTree = &RootStateTree;
for (const FStateTreeExecutionFrame& Frame : ActiveFrames)
{
if (!ensure(Frame.StateTree))
{
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.StateTree != LastStateTree)
{
StatePath.Appendf(TEXT("[%s]"), *GetNameSafe(Frame.StateTree));
LastStateTree = Frame.StateTree;
}
for (int32 i = 0; i < Num; i++)
{
const FCompactStateTreeState& State = Frame.StateTree->States[Frame.ActiveStates[i].Index];
StatePath.Appendf(TEXT("%s%s"), i == 0 ? TEXT("") : TEXT("."), *State.Name.ToString());
}
}
return StatePath;
}
FString FStateTreeExecutionContext::GetStateStatusString(const FStateTreeExecutionState& ExecState) const
{
if (ExecState.TreeRunStatus != EStateTreeRunStatus::Running)
{
return TEXT("--:") + UEnum::GetDisplayValueAsText(ExecState.LastTickStatus).ToString();
}
return GetSafeStateName(ExecState.ActiveFrames.Last(), ExecState.ActiveFrames.Last().ActiveStates.Last()) + TEXT(":") + UEnum::GetDisplayValueAsText(ExecState.LastTickStatus).ToString();
}
// Deprecated
FString FStateTreeExecutionContext::GetInstanceDescription() const
{
return GetInstanceDescriptionInternal();
}
#undef STATETREE_LOG
#undef STATETREE_CLOG
#undef UE_STATETREE_ENSURE_COMPLETED_STATE_RUN_STATUS