行为树随笔(RequestExecution篇)
会在StopTree()中,如果有异步在进行的Task,例如如果当前行为树中有UBTComposite_SimpleParallel节点,并且正在激活运行中,因为它下面的子节点是异步执行的,所以如果它下面有Task的EBTNodeResult是InProgress,就要Abort它,然后将bWaitingForLatentAborts设置为True。而RequestBy的类型是UBTNode*,代
一. RequestExecution()
1. 确定了
bSwitchToHigherPriority = (ContinueWithResult == EBTNodeResult::Aborted);
和
bAlreadyHasRequest = (ExecutionRequest.ExecuteNode != NULL);
2.分两种情况确定SearchStart和SearchEnd与ExecuteNode。
(1)
if (bSwitchToHigherPriority && RequestedByChildIndex >= 0)
的时候:
ExecuteNode是RequestBy所在节点的父节点,(因为RequestBy在这种情况是UBTDecorator),也就是与RequestOn指向一样。
SearchStart是RequestOn下面子节点索引是RequestedByChildIndex的子节点的FirstNode(因为在一个FBTCompositeChild上,可能会有多个装饰器,Task或者UBTCompositeNode,Task或者UBTCompositeNode上可能还会有多服务)。所以SearchStart代表了这个FBTCompositeChild上的第一个节点,也就是这一分支的根下第一个节点。
SearchEnd是RequestOn下面子节点索引是 RequestedByChildIndex+1 的子节点的FirstNode,也就是这个SearchStart到SearchEnd的范围就是RequestedByChildIndex这一条分支。
(2)反之:
ExecuteNode是RequestBy所在节点的父节点。
SearchStart是RequestBy所在节点。
SearchEnd是无限大。
3. 根据
if (bAlreadyHasRequest && ExecutionRequest.SearchStart.TakesPriorityOver(ExecutionIdx))
决定要不要合并请求。bAlreadyHasRequest 表示现在还存在一份执行请求未处理,并且如果未处理的那份请求SearchStart节点优先级高于现在这个请求的SearchStart节点。说明需要合并一下,合并就是把现在这个请求得到的SearchEnd节点赋给了已有的ExecutionRequest中的SearchEnd节点,然后return出去,不再接着往下执行。
4. 接着将ExecutionRequest中其他的属性进行赋值,
ExecutionRequest.ContinueWithResult = ContinueWithResult;
ExecutionRequest.bTryNextChild = !bSwitchToHigherPriority;
ExecutionRequest.bIsRestart = (RequestedBy != GetActiveNode());
PendingExecution.Lock();
这里PendingExecution.Lock();代表了Request不能被执行。
Unlock的地方在ProcessExecutionRequest中找到NextTask之后,执行ProcessPendingExecution之前。
if (!SearchData.bPostponeSearch)
{
// clear request accumulator
ExecutionRequest = FBTNodeExecutionInfo();
// unlock execution data, can get locked again if AbortCurrentTask starts any new requests
PendingExecution.Unlock();
if (bIsSearchValid)
{
// abort task if needed
if (InstanceStack.Last().ActiveNodeType == EBTActiveNode::ActiveTask)
{
// prevent new execution requests for nodes inside the deactivated branch
// that may result from the aborted task.
SearchData.bFilterOutRequestFromDeactivatedBranch = true;
AbortCurrentTask();
SearchData.bFilterOutRequestFromDeactivatedBranch = false;
}
// set next task to execute only when not lock for execution as everything has been cancelled/rollback
if (!PendingExecution.IsLocked())
{
PendingExecution.NextTask = NextTask;
PendingExecution.bOutOfNodes = (NextTask == NULL);
}
}
ProcessPendingExecution();
}
else
{
// more important execution request was found
// stop everything and search again in next tick
ScheduleExecutionUpdate();
}
5. 查看当前有没有正在进的行行为树的任务搜索
if (SearchData.bSearchInProgress)
{
UE_VLOG(GetOwner(), LogBehaviorTree, Log, TEXT("> aborting current task search!"));
SearchData.bPostponeSearch = true;
}
如果当前正在进行行为树的任务搜索,把“延迟搜索”标志设为 true,表示当前正在进行的行为树的任务搜索会被中断,然后在因为RequestExecution最后会调用
if (bScheduleNewSearch)
{
ScheduleExecutionUpdate();
}
所以本次请求会在下一帧开始处理。
6. 判断一下本次要开始的搜索是不是一个无效的
const bool bIsActiveNodeAborting = InstanceStack.Num() && InstanceStack.Last().ActiveNodeType == EBTActiveNode::AbortingTask;
const bool bInvalidateCurrentSearch = bWaitingForLatentAborts || bIsActiveNodeAborting;
const bool bScheduleNewSearch = !bWaitingForLatentAborts;
if (bInvalidateCurrentSearch)
{
if (ExecutionRequest.SearchEnd.IsSet())
{
UE_VLOG(GetOwner(), LogBehaviorTree, Log, TEXT("> removing limit from end of search range because of request during task abortion!"));
ExecutionRequest.SearchEnd = FBTNodeIndex();
}
RollbackSearchChanges();
}
可以看出判断bInvalidateCurrentSearch需要看两个条件,bWaitingForLatentAborts 或者bIsActiveNodeAborting。
(1)bWaitingForLatentAborts有两种情况会被设置为True。
会在StopTree()中,如果有异步在进行的Task,例如如果当前行为树中有UBTComposite_SimpleParallel节点,并且正在激活运行中,因为它下面的子节点是异步执行的,所以如果它下面有Task的EBTNodeResult是InProgress,就要Abort它,然后将bWaitingForLatentAborts设置为True。
----------------------------------------------------------------------------------------------------------------------------------
会在ApplySearchUpdates()中,如果SearchUpdate中记录了EBTNodeUpdateMode为Remove的Task类型节点,如果这个Task节点的EBTNodeResult是InProgress,将bWaitingForLatentAborts设置为True。
(2)bIsActiveNodeAborting首先看它的赋值
const bool bIsActiveNodeAborting = InstanceStack.Num() && InstanceStack.Last().ActiveNodeType == EBTActiveNode::AbortingTask;
说明当前行为树的活跃Task节点的EBTActiveNode为AbortingTask,bIsActiveNodeAborting即为True。
活跃Task节点被设置为AbortingTask的情况也有两种。
会在StopTree()中,当活跃Task节点的EBTActiveNode为ActiveTask时会将它的EBTActiveNode改为AbortingTask。
----------------------------------------------------------------------------------------------------------------------------------
会在AbortCurrentTask()中。
void UBehaviorTreeComponent::AbortCurrentTask()
{
const int32 CurrentInstanceIdx = InstanceStack.Num() - 1;
FBehaviorTreeInstance& CurrentInstance = InstanceStack[CurrentInstanceIdx];
CurrentInstance.ActiveNodeType = EBTActiveNode::AbortingTask;
UBTTaskNode* CurrentTask = (UBTTaskNode*)CurrentInstance.ActiveNode;
// remove all observers before requesting abort
UnregisterMessageObserversFrom(CurrentTask);
// protect memory of this task from rollbacks
// at this point, invalid search rollback already happened
// only reason to do the rollback is restoring tree state during abort for accumulated requests
// but this task needs to remain unchanged: it's still aborting and internal memory can be modified on AbortTask call
SearchData.bPreserveActiveNodeMemoryOnRollback = true;
UE_VLOG(GetOwner(), LogBehaviorTree, Log, TEXT("Abort task: %s"), *UBehaviorTreeTypes::DescribeNodeHelper(CurrentTask));
// abort task using current state of tree
uint8* NodeMemory = (uint8*)(CurrentTask->GetNodeMemory<uint8>(CurrentInstance));
EBTNodeResult::Type TaskResult = CurrentTask->WrappedAbortTask(*this, NodeMemory);
// pass task finished if wasn't already notified (FinishLatentAbort)
if (CurrentInstance.ActiveNodeType == EBTActiveNode::AbortingTask &&
CurrentInstanceIdx == (InstanceStack.Num() - 1))
{
OnTaskFinished(CurrentTask, TaskResult);
}
}
而AbortCurrentTask会在ProcessExecutionRequest()中调用
if (!SearchData.bPostponeSearch)
{
// clear request accumulator
ExecutionRequest = FBTNodeExecutionInfo();
// unlock execution data, can get locked again if AbortCurrentTask starts any new requests
PendingExecution.Unlock();
if (bIsSearchValid)
{
// abort task if needed
if (InstanceStack.Last().ActiveNodeType == EBTActiveNode::ActiveTask)
{
// prevent new execution requests for nodes inside the deactivated branch
// that may result from the aborted task.
SearchData.bFilterOutRequestFromDeactivatedBranch = true;
AbortCurrentTask();
SearchData.bFilterOutRequestFromDeactivatedBranch = false;
}
// set next task to execute only when not lock for execution as everything has been cancelled/rollback
if (!PendingExecution.IsLocked())
{
PendingExecution.NextTask = NextTask;
PendingExecution.bOutOfNodes = (NextTask == NULL);
}
}
ProcessPendingExecution();
}
找到NextTask后,如果当前Task的ActiveNode是ActiveTask,那么就要打断当前当前活跃的Task。
注:1. ExecutionRequest的类型是FBTNodeExecutionInfo,结构是:
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 */
const UBTCompositeNode* ExecuteNode;
/** subtree index */
uint16 ExecuteInstanceIdx;
/** result used for resuming execution */
TEnumAsByte<EBTNodeResult::Type> 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) { }
};
FBTNodeIndex的结构是
struct FBTNodeIndex
{
static constexpr uint16 InvalidIndex = TNumericLimits<uint16>::Max(); // (This is also the same as INDEX_NONE assigned to IndexType!)
/** index of instance of stack */
uint16 InstanceIndex;
/** execution index within instance */
uint16 ExecutionIndex;
FBTNodeIndex() : InstanceIndex(InvalidIndex), ExecutionIndex(InvalidIndex) {}
FBTNodeIndex(uint16 InInstanceIndex, uint16 InExecutionIndex) : InstanceIndex(InInstanceIndex), ExecutionIndex(InExecutionIndex) {}
bool TakesPriorityOver(const FBTNodeIndex& Other) const;
bool IsSet() const { return InstanceIndex < InvalidIndex; }
FORCEINLINE bool operator==(const FBTNodeIndex& Other) const { return Other.ExecutionIndex == ExecutionIndex && Other.InstanceIndex == InstanceIndex; }
FORCEINLINE bool operator!=(const FBTNodeIndex& Other) const { return !operator==(Other); }
FORCEINLINE friend uint32 GetTypeHash(const FBTNodeIndex& Other) { return Other.ExecutionIndex ^ Other.InstanceIndex; }
FORCEINLINE FString Describe() const { return FString::Printf(TEXT("[%d:%d]"), InstanceIndex, ExecutionIndex); }
};
注:2.RequestExecution传进来的参数是:
void UBehaviorTreeComponent::RequestExecution(const UBTCompositeNode* RequestedOn, const int32 InstanceIdx, const UBTNode* RequestedBy,
const int32 RequestedByChildIndex, const EBTNodeResult::Type ContinueWithResult, const bool bStoreForDebugger)
传进来的RequestOn类型是UBTCompositeNode*,代表它只能是复合节点,也就是发起节点所在节点的父节点。而RequestBy的类型是UBTNode*,代表了它可以是任意类型的节点,就是Task节点或者装饰器节点。
注:3. AbortCurrentTask()方法虽然是打断Task的方法,但是方法最后调用的是OnTaskFinished()方法,只不过传的参数中类型是Abort。
注:4.
USTRUCT()
struct FBTCompositeChild
{
GENERATED_USTRUCT_BODY()
/** child node */
UPROPERTY()
TObjectPtr<UBTCompositeNode> ChildComposite = nullptr;
UPROPERTY()
TObjectPtr<UBTTaskNode> ChildTask = nullptr;
/** execution decorators */
UPROPERTY()
TArray<TObjectPtr<UBTDecorator>> Decorators;
/** logic operations for decorators */
UPROPERTY()
TArray<FBTDecoratorLogic> DecoratorOps;
};
UCLASS(Abstract, MinimalAPI)
class UBTCompositeNode : public UBTNode
{
GENERATED_UCLASS_BODY()
/** child nodes */
UPROPERTY()
TArray<FBTCompositeChild> Children;
/** service nodes */
UPROPERTY()
TArray<TObjectPtr<UBTService>> Services;
。。。。。。
}
namespace EBTActiveNode
{
// keep in sync with DescribeActiveNode()
enum Type
{
Composite,
ActiveTask,
AbortingTask,
InactiveTask,
};
}
namespace EBTTaskStatus
{
// keep in sync with DescribeTaskStatus()
enum Type
{
Active,
Aborting,
Inactive,
};
}
namespace EBTNodeUpdateMode
{
// keep in sync with DescribeNodeUpdateMode()
enum Type
{
Unknown,
Add, // add node
Remove, // remove node
};
}
UENUM()
enum class EBTChildIndex : uint8
{
FirstNode,
TaskNode,
};
namespace BTSpecialChild
{
inline constexpr int32 NotInitialized = -1; // special value for child indices: needs to be initialized
inline constexpr int32 ReturnToParent = -2; // special value for child indices: return to parent node
inline constexpr uint8 OwnedByComposite = MAX_uint8; // special value for aux node's child index: owned by composite node instead of a task
}
更多推荐
所有评论(0)