// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" #include "EngineDefines.h" #include "BehaviorTree/BehaviorTreeTypes.h" #include "GameplayTagContainer.h" #include "AITypes.h" #include "BrainComponent.h" #include "ProfilingDebugging/CsvProfiler.h" #include "BehaviorTreeComponent.generated.h" class FBehaviorTreeDebugger; class UBehaviorTree; class UBTAuxiliaryNode; class UBTCompositeNode; class UBTDecorator; class UBTNode; class UBTTask_RunBehavior; class UBTTask_RunBehaviorDynamic; class UBTTaskNode; struct FScopedBehaviorTreeLock; struct FBTNodeExecutionInfo { /** index of first task allowed to be executed */ FBTNodeIndex SearchStart; /** index of last task allowed to be executed */ FBTNodeIndex SearchEnd; /** node to be executed */ UBTCompositeNode* ExecuteNode; /** subtree index */ uint16 ExecuteInstanceIdx; /** result used for resuming execution */ TEnumAsByte ContinueWithResult; /** if set, tree will try to execute next child of composite instead of forcing branch containing SearchStart */ uint8 bTryNextChild : 1; /** if set, request was not instigated by finishing task/initialization but is a restart (e.g. decorator) */ uint8 bIsRestart : 1; FBTNodeExecutionInfo() : ExecuteNode(NULL), bTryNextChild(false), bIsRestart(false) { } }; struct FBTPendingExecutionInfo { /** next task to execute */ UBTTaskNode* NextTask; /** if set, tree ran out of nodes */ uint32 bOutOfNodes : 1; /** if set, request can't be executed */ uint32 bLocked : 1; FBTPendingExecutionInfo() : NextTask(NULL), bOutOfNodes(false), bLocked(false) {} bool IsSet() const { return (NextTask || bOutOfNodes) && !bLocked; } bool IsLocked() const { return bLocked; } void Lock() { bLocked = true; } void Unlock() { bLocked = false; } }; struct FBTTreeStartInfo { UBehaviorTree* Asset; EBTExecutionMode::Type ExecuteMode; uint8 bPendingInitialize : 1; FBTTreeStartInfo() : Asset(nullptr), ExecuteMode(EBTExecutionMode::Looped), bPendingInitialize(0) {} bool IsSet() const { return Asset != nullptr; } bool HasPendingInitialize() const { return bPendingInitialize && IsSet(); } }; UCLASS(ClassGroup = AI, meta = (BlueprintSpawnableComponent)) class AIMODULE_API UBehaviorTreeComponent : public UBrainComponent { GENERATED_UCLASS_BODY() // UActorComponent overrides virtual void RegisterComponentTickFunctions(bool bRegister) override; virtual void SetComponentTickEnabled(bool bEnabled) override; // Begin UBrainComponent overrides virtual void StartLogic() override; virtual void RestartLogic() override; virtual void StopLogic(const FString& Reason) override; virtual void PauseLogic(const FString& Reason) override; virtual EAILogicResuming::Type ResumeLogic(const FString& Reason) override; /** indicates instance has been initialized to work with specific BT asset */ bool TreeHasBeenStarted() const; public: /** DO NOT USE. This constructor is for internal usage only for hot-reload purposes. */ UBehaviorTreeComponent(FVTableHelper& Helper); virtual bool IsRunning() const override; virtual bool IsPaused() const override; virtual void Cleanup() override; virtual void HandleMessage(const FAIMessage& Message) override; // End UBrainComponent overrides // Begin UActorComponent overrides virtual void UninitializeComponent() override; // End UActorComponent overrides /** starts execution from root */ void StartTree(UBehaviorTree& Asset, EBTExecutionMode::Type ExecuteMode = EBTExecutionMode::Looped); /** stops execution */ void StopTree(EBTStopMode::Type StopMode = EBTStopMode::Safe); /** restarts execution from root */ void RestartTree(); /** request execution change */ void RequestExecution(UBTCompositeNode* RequestedOn, int32 InstanceIdx, const UBTNode* RequestedBy, int32 RequestedByChildIndex, EBTNodeResult::Type ContinueWithResult, bool bStoreForDebugger = true); /** request execution change: helpers for decorator nodes */ void RequestExecution(const UBTDecorator* RequestedBy); /** request execution change: helpers for task nodes */ void RequestExecution(EBTNodeResult::Type ContinueWithResult); /** request unregistration of aux nodes in the specified branch */ UE_DEPRECATED(5.0, "This function is deprecated. Please use RequestBranchDeactivation instead.") void RequestUnregisterAuxNodesInBranch(const UBTCompositeNode* Node); /** request branch deactivation: helper for decorator */ void RequestBranchDeactivation(const UBTDecorator& RequestedBy); /** finish latent execution or abort */ void OnTaskFinished(const UBTTaskNode* TaskNode, EBTNodeResult::Type TaskResult); /** setup message observer for given task */ void RegisterMessageObserver(const UBTTaskNode* TaskNode, FName MessageType); void RegisterMessageObserver(const UBTTaskNode* TaskNode, FName MessageType, FAIRequestID MessageID); /** remove message observers registered with task */ void UnregisterMessageObserversFrom(const UBTTaskNode* TaskNode); void UnregisterMessageObserversFrom(const FBTNodeIndex& TaskIdx); /** add active parallel task */ void RegisterParallelTask(const UBTTaskNode* TaskNode); /** remove parallel task */ void UnregisterParallelTask(const UBTTaskNode* TaskNode, uint16 InstanceIdx); /** unregister all aux nodes less important than given index */ void UnregisterAuxNodesUpTo(const FBTNodeIndex& Index); /** unregister all aux nodes between given execution index range: FromIndex < AuxIndex < ToIndex */ void UnregisterAuxNodesInRange(const FBTNodeIndex& FromIndex, const FBTNodeIndex& ToIndex); /** unregister all aux nodes in branch of tree */ void UnregisterAuxNodesInBranch(const UBTCompositeNode* Node, bool bApplyImmediately = true); /** BEGIN UActorComponent overrides */ virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; /** END UActorComponent overrides */ /** Schedule when will be the next tick, 0.0f means next frame, FLT_MAX means never */ void ScheduleNextTick(float NextDeltaTime); /** process execution flow */ void ProcessExecutionRequest(); /** schedule execution flow update in next tick */ void ScheduleExecutionUpdate(); /** tries to find behavior tree instance in context */ int32 FindInstanceContainingNode(const UBTNode* Node) const; /** tries to find template node for given instanced node */ UBTNode* FindTemplateNode(const UBTNode* Node) const; /** @return current tree */ UBehaviorTree* GetCurrentTree() const; /** @return tree from top of instance stack */ UBehaviorTree* GetRootTree() const; /** @return active node */ const UBTNode* GetActiveNode() const; /** get index of active instance on stack */ uint16 GetActiveInstanceIdx() const; /** @return node memory */ uint8* GetNodeMemory(UBTNode* Node, int32 InstanceIdx) const; /** @return true if ExecutionRequest is switching to higher priority node */ bool IsRestartPending() const; /** @return true if waiting for abort to finish */ bool IsAbortPending() const; /** @return true if active node is one of child nodes of given one */ bool IsExecutingBranch(const UBTNode* Node, int32 ChildIndex = -1) const; /** @return true if aux node is currently active */ bool IsAuxNodeActive(const UBTAuxiliaryNode* AuxNode) const; bool IsAuxNodeActive(const UBTAuxiliaryNode* AuxNodeTemplate, int32 InstanceIdx) const; /** Returns true if InstanceStack contains any BT runtime instances */ bool IsInstanceStackEmpty() const { return (InstanceStack.Num() == 0); } /** @return status of speficied task */ EBTTaskStatus::Type GetTaskStatus(const UBTTaskNode* TaskNode) const; virtual FString GetDebugInfoString() const override; virtual FString DescribeActiveTasks() const; virtual FString DescribeActiveTrees() const; /** @return the cooldown tag end time, 0.0f if CooldownTag is not found */ UFUNCTION(BlueprintCallable, Category = "AI|Logic") float GetTagCooldownEndTime(FGameplayTag CooldownTag) const; /** add to the cooldown tag's duration */ UFUNCTION(BlueprintCallable, Category = "AI|Logic") void AddCooldownTagDuration(FGameplayTag CooldownTag, float CooldownDuration, bool bAddToExistingDuration); /** assign subtree to RunBehaviorDynamic task specified by tag */ UFUNCTION(BlueprintCallable, Category="AI|Logic") virtual void SetDynamicSubtree(FGameplayTag InjectTag, UBehaviorTree* BehaviorAsset); // Code for timing BT Search for FramePro #if !UE_BUILD_SHIPPING static void EndFrame(); #endif #if ENABLE_VISUAL_LOG virtual void DescribeSelfToVisLog(struct FVisualLogEntry* Snapshot) const override; #endif #if CSV_PROFILER /** Set a custom CSV tick stat name, must point to a static string */ void SetCSVTickStatName(const char* InCSVTickStatName) { CSVTickStatName = InCSVTickStatName; } #endif protected: /** stack of behavior tree instances */ TArray InstanceStack; /** list of known subtree instances */ TArray KnownInstances; /** instanced nodes */ UPROPERTY(transient) TArray> NodeInstances; /** search data being currently used */ FBehaviorTreeSearchData SearchData; /** execution request, search will be performed when current task finish execution/aborting */ FBTNodeExecutionInfo ExecutionRequest; /** result of ExecutionRequest, will be applied when current task finish aborting */ FBTPendingExecutionInfo PendingExecution; /** list of all pending branch deactivation requests */ TArray PendingBranchesToDeactivate; /** stored data for starting new tree, waits until previously running finishes aborting */ FBTTreeStartInfo TreeStartInfo; /** message observers mapped by instance & execution index */ TMultiMap TaskMessageObservers; /** behavior cooldowns mapped by tag to last time it was set */ TMap CooldownTagsMap; #if USE_BEHAVIORTREE_DEBUGGER /** search flow for debugger */ mutable TArray > CurrentSearchFlow; mutable TArray > CurrentRestarts; mutable TMap SearchStartBlackboard; mutable TArray RemovedInstances; /** debugger's recorded data */ mutable TArray DebuggerSteps; /** set when at least one debugger window is opened */ static int32 ActiveDebuggerCounter; #endif // Code for timing BT Search for FramePro #if !UE_BUILD_SHIPPING static bool bAddedEndFrameCallback; static double FrameSearchTime; static int32 NumSearchTimeCalls; #endif /** index of last active instance on stack */ uint16 ActiveInstanceIdx; /** if set, StopTree calls will be deferred */ uint8 StopTreeLock; /** if set, StopTree will be called at the end of tick */ uint8 bDeferredStopTree : 1; /** loops tree execution */ uint8 bLoopExecution : 1; /** set when execution is waiting for tasks to finish their latent abort (current or parallel's main) */ uint8 bWaitingForLatentAborts : 1; /** set when execution update is scheduled for next tick */ uint8 bRequestedFlowUpdate : 1; /** set when tree stop was called */ uint8 bRequestedStop : 1; /** if set, tree execution is allowed */ uint8 bIsRunning : 1; /** if set, execution requests will be postponed */ uint8 bIsPaused : 1; /** if set, all branch deactivation requests will be queued up */ uint8 bBranchDeactivationSuspended : 1; /** push behavior tree instance on execution stack * @NOTE: should never be called out-side of BT execution, meaning only BT tasks can push another BT instance! */ bool PushInstance(UBehaviorTree& TreeAsset); /** add unique Id of newly created subtree to KnownInstances list and return its index */ uint8 UpdateInstanceId(UBehaviorTree* TreeAsset, const UBTNode* OriginNode, int32 OriginInstanceIdx); /** remove instanced nodes, known subtree instances and safely clears their persistent memory */ void RemoveAllInstances(); /** copy memory block from running instances to persistent memory */ void CopyInstanceMemoryToPersistent(); /** copy memory block from persistent memory to running instances (rollback) */ void CopyInstanceMemoryFromPersistent(); /** find next task to execute */ UBTTaskNode* FindNextTask(UBTCompositeNode* ParentNode, uint16 ParentInstanceIdx, EBTNodeResult::Type LastResult); /** called when tree runs out of nodes to execute */ void OnTreeFinished(); /** apply pending node updates from SearchData */ void ApplySearchData(UBTNode* NewActiveNode); /** apply pending node updates required for discarded search */ void ApplyDiscardedSearch(); /** apply updates from specific list */ void ApplySearchUpdates(const TArray& UpdateList, int32 NewNodeExecutionIndex, bool bPostUpdate = false); /** abort currently executed task */ void AbortCurrentTask(); /** execute new task */ void ExecuteTask(UBTTaskNode* TaskNode); /** deactivate all nodes up to requested one */ bool DeactivateUpTo(UBTCompositeNode* Node, uint16 NodeInstanceIdx, EBTNodeResult::Type& NodeResult, int32& OutLastDeactivatedChildIndex); /** returns true if execution was waiting on latent aborts and they are all finished; */ bool TrackPendingLatentAborts(); /** tracks if there are new tasks using latent abort in progress */ void TrackNewLatentAborts(); /** return true if the current or any parallel task has a latent abort in progress */ bool HasActiveLatentAborts() const; /** apply pending execution from last task search */ void ProcessPendingExecution(); /** apply pending tree initialization */ void ProcessPendingInitialize(); /** restore state of tree to state before search */ void RollbackSearchChanges(); /** make a snapshot for debugger */ void StoreDebuggerExecutionStep(EBTExecutionSnap::Type SnapType); /** make a snapshot for debugger from given subtree instance */ void StoreDebuggerInstance(FBehaviorTreeDebuggerInstance& InstanceInfo, uint16 InstanceIdx, EBTExecutionSnap::Type SnapType) const; void StoreDebuggerRemovedInstance(uint16 InstanceIdx) const; /** store search step for debugger */ void StoreDebuggerSearchStep(const UBTNode* Node, uint16 InstanceIdx, EBTNodeResult::Type NodeResult) const; void StoreDebuggerSearchStep(const UBTNode* Node, uint16 InstanceIdx, bool bPassed) const; /** store restarting node for debugger */ void StoreDebuggerRestart(const UBTNode* Node, uint16 InstanceIdx, bool bAllowed); /** describe blackboard's key values */ void StoreDebuggerBlackboard(TMap& BlackboardValueDesc) const; /** gather nodes runtime descriptions */ void StoreDebuggerRuntimeValues(TArray& RuntimeDescriptions, UBTNode* RootNode, uint16 InstanceIdx) const; /** update runtime description of given task node in latest debugger's snapshot */ void UpdateDebuggerAfterExecution(const UBTTaskNode* TaskNode, uint16 InstanceIdx) const; /** check if debugger is currently running and can gather data */ static bool IsDebuggerActive(); /** Return NodeA's relative priority in regards to NodeB */ EBTNodeRelativePriority CalculateRelativePriority(const UBTNode* NodeA, const UBTNode* NodeB) const; /** Deactivate a branch as the decorator condition is not passing anymore */ void DeactivateBranch(const UBTDecorator& RequestedBy); /** Suspend any branch deactivation and queue them to be processed later by ResumeBranchDeactivation() */ void SuspendBranchDeactivation(); /** Resume branch deactivation and execute all the queued up ones */ void ResumeBranchDeactivation(); struct FBTSuspendBranchDeactivationScoped { FBTSuspendBranchDeactivationScoped(UBehaviorTreeComponent& InBTComp) : BTComp(InBTComp) { BTComp.SuspendBranchDeactivation(); } ~FBTSuspendBranchDeactivationScoped() { BTComp.ResumeBranchDeactivation(); } UBehaviorTreeComponent& BTComp; }; friend UBTNode; friend UBTCompositeNode; friend UBTTaskNode; friend UBTTask_RunBehavior; friend UBTTask_RunBehaviorDynamic; friend FBehaviorTreeDebugger; friend FBehaviorTreeInstance; friend FScopedBehaviorTreeLock; protected: /** data asset defining the tree */ UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = AI) TObjectPtr DefaultBehaviorTreeAsset; /** Used to tell tickmanager that we want interval ticking */ bool bTickedOnce = false; /** Predicted next DeltaTime*/ float NextTickDeltaTime = 0.0f; /** Accumulated DeltaTime if ticked more than predicted next delta time */ float AccumulatedTickDeltaTime = 0.0f; /** GameTime of the last DeltaTime request, used for debugging to output warnings about ticking */ float LastRequestedDeltaTimeGameTime = 0; #if CSV_PROFILER /** CSV tick stat name. Can be changed but must point to a static string */ const char* CSVTickStatName = "BehaviorTreeTick"; #endif }; ////////////////////////////////////////////////////////////////////////// // Inlines FORCEINLINE UBehaviorTree* UBehaviorTreeComponent::GetCurrentTree() const { return InstanceStack.Num() ? KnownInstances[InstanceStack[ActiveInstanceIdx].InstanceIdIndex].TreeAsset : NULL; } FORCEINLINE UBehaviorTree* UBehaviorTreeComponent::GetRootTree() const { return InstanceStack.Num() ? KnownInstances[InstanceStack[0].InstanceIdIndex].TreeAsset : NULL; } FORCEINLINE const UBTNode* UBehaviorTreeComponent::GetActiveNode() const { return InstanceStack.Num() ? InstanceStack[ActiveInstanceIdx].ActiveNode : NULL; } FORCEINLINE uint16 UBehaviorTreeComponent::GetActiveInstanceIdx() const { return ActiveInstanceIdx; } FORCEINLINE bool UBehaviorTreeComponent::IsRestartPending() const { return ExecutionRequest.ExecuteNode && !ExecutionRequest.bTryNextChild; } FORCEINLINE bool UBehaviorTreeComponent::IsAbortPending() const { return bWaitingForLatentAborts || PendingExecution.IsSet(); }