2025-11-25 08:19:33 -05:00
#if GRAPH_DESIGNER
/// ---------------------------------------------
/// Behavior Designer
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.BehaviorDesigner.Runtime.Systems
{
using Opsive.BehaviorDesigner.Runtime.Components ;
using Opsive.BehaviorDesigner.Runtime.Groups ;
using Opsive.BehaviorDesigner.Runtime.Tasks ;
using Opsive.BehaviorDesigner.Runtime.Utility ;
using Unity.Burst ;
using Unity.Collections ;
using Unity.Entities ;
using Unity.Jobs ;
using UnityEngine ;
/// <summary>
/// Traverses and ensures the correct tasks are active.
/// </summary>
[UpdateInGroup(typeof(TraversalSystemGroup))]
[UpdateAfter(typeof(TraversalTaskSystemGroup))]
public partial struct EvaluationSystem : ISystem
{
private bool m_JobScheduled ;
private EntityQuery m_Query ;
private JobHandle m_Dependency ;
private EntityCommandBuffer m_EntityCommandBuffer ;
/// <summary>
/// The system has been created.
/// </summary>
/// <param name="state">The state of the system.</param>
private void OnCreate ( ref SystemState state )
{
m_JobScheduled = false ;
2026-01-03 18:19:39 -05:00
m_Query = SystemAPI . QueryBuilder ( ) . WithAllRW < BranchComponent , TaskComponent > ( ) . WithAbsent < BakedBehaviorTree > ( ) . Build ( ) ;
2025-11-25 08:19:33 -05:00
}
/// <summary>
/// Starts the job which traverses the tree.
/// </summary>
/// <param name="state">The current state of the system.</param>
private void OnUpdate ( ref SystemState state )
{
m_JobScheduled = true ;
m_EntityCommandBuffer = new EntityCommandBuffer ( state . WorldUpdateAllocator ) ;
m_Dependency = state . Dependency = new EvaluationJob ( )
{
EntityCommandBuffer = m_EntityCommandBuffer . AsParallelWriter ( ) ,
} . ScheduleParallel ( m_Query , state . Dependency ) ;
}
/// <summary>
/// Completes the job and releases any memory.
/// </summary>
/// <param name="entityManager">The running EntityManager.</param>
/// <param name="stopRunning">Has the system been stopped?</param>
[BurstCompile]
public void Complete ( EntityManager entityManager , bool stopRunning = false )
{
if ( ! m_JobScheduled ) {
return ;
}
if ( ! stopRunning ) {
m_Dependency . Complete ( ) ;
m_EntityCommandBuffer . Playback ( entityManager ) ;
m_EntityCommandBuffer . Dispose ( ) ;
}
m_JobScheduled = false ;
}
/// <summary>
/// Job which traverses the tree.
/// </summary>
[BurstCompile]
public partial struct EvaluationJob : IJobEntity
{
[Tooltip("CommandBuffer which sets the component data.")]
public EntityCommandBuffer . ParallelWriter EntityCommandBuffer ;
/// <summary>
/// Executes the job.
/// </summary>
/// <param name="entity">The entity that is being acted upon.</param>
/// <param name="entityIndex">The index of the entity.</param
/// <param name="branchComponents">An array of branch components.</param>
/// <param name="taskComponents">An array of task components.</param>
[BurstCompile]
public void Execute ( Entity entity , [ EntityIndexInQuery ] int entityIndex , ref DynamicBuffer < BranchComponent > branchComponents , ref DynamicBuffer < TaskComponent > taskComponents )
{
2026-05-10 11:47:55 -04:00
// Track active flag types to avoid branch-wide scans when toggling system tags.
var trackedTypeIndices = new FixedList4096Bytes < int > ( ) ;
var trackedTypeCounts = new FixedList4096Bytes < ushort > ( ) ;
for ( int i = 0 ; i < branchComponents . Length ; + + i ) {
var branchComponent = branchComponents [ i ] ;
if ( branchComponent . ActiveIndex = = ushort . MaxValue | | branchComponent . ActiveFlagComponentType . TypeIndex = = TypeIndex . Null ) {
continue ;
}
IncrementTrackedTypeCount ( branchComponent . ActiveFlagComponentType . TypeIndex , ref trackedTypeIndices , ref trackedTypeCounts ) ;
}
2025-11-25 08:19:33 -05:00
for ( int i = 0 ; i < branchComponents . Length ; + + i ) {
var branchComponent = branchComponents [ i ] ;
if ( branchComponent . ActiveIndex ! = ushort . MaxValue & & branchComponent . ActiveIndex = = branchComponent . NextIndex ) {
var activeTask = taskComponents [ branchComponent . ActiveIndex ] ;
if ( activeTask . Status = = TaskStatus . Success | | activeTask . Status = = TaskStatus . Failure ) {
branchComponent . NextIndex = activeTask . ParentIndex ;
}
}
if ( branchComponent . ActiveIndex ! = branchComponent . NextIndex ) {
// Do not switch into a disabled task.
2026-05-10 11:47:55 -04:00
if ( branchComponent . NextIndex ! = ushort . MaxValue & & ! taskComponents [ branchComponent . NextIndex ] . Enabled ) {
2025-11-25 08:19:33 -05:00
var taskComponent = taskComponents [ branchComponent . NextIndex ] ;
taskComponent . Status = TaskStatus . Inactive ;
var taskComponentBuffer = taskComponents ;
taskComponentBuffer [ branchComponent . NextIndex ] = taskComponent ;
branchComponent . NextIndex = branchComponent . ActiveIndex ;
} else {
// The status for all children should be reset back to their inactive state if the next task is within a new branch. This will prevent
// the return status from being reset when the task ends normally.
var taskComponentBuffer = taskComponents ;
if ( branchComponent . NextIndex ! = ushort . MaxValue & &
! TraversalUtility . IsParent ( ( ushort ) branchComponent . ActiveIndex , ( ushort ) branchComponent . NextIndex , ref taskComponentBuffer ) ) {
var nextTaskComponent = taskComponents [ branchComponent . NextIndex ] ;
if ( branchComponent . ActiveIndex ! = ushort . MaxValue & & nextTaskComponent . Status ! = TaskStatus . Running ) { // If the next task is already running then an interrupt has already reset the children.
var childCount = TraversalUtility . GetChildCount ( branchComponent . NextIndex , ref taskComponentBuffer ) ;
for ( int j = 0 ; j < childCount ; + + j ) {
var childTaskComponent = taskComponents [ branchComponent . NextIndex + j + 1 ] ;
childTaskComponent . Status = TaskStatus . Inactive ;
taskComponentBuffer [ branchComponent . NextIndex + j + 1 ] = childTaskComponent ;
}
}
nextTaskComponent . Status = nextTaskComponent . Status = = TaskStatus . Running ? TaskStatus . Running : TaskStatus . Queued ;
taskComponentBuffer [ branchComponent . NextIndex ] = nextTaskComponent ;
}
branchComponent . ActiveIndex = branchComponent . NextIndex ;
// Change the component tag if the task type is different.
var componentType = branchComponent . ActiveIndex ! = ushort . MaxValue ? taskComponents [ branchComponent . ActiveIndex ] . FlagComponentType : new ComponentType ( ) ;
if ( componentType ! = branchComponent . ActiveFlagComponentType ) {
2026-05-10 11:47:55 -04:00
if ( branchComponent . ActiveFlagComponentType . TypeIndex ! = TypeIndex . Null & &
DecrementTrackedTypeCount ( branchComponent . ActiveFlagComponentType . TypeIndex , ref trackedTypeIndices , ref trackedTypeCounts ) = = 0 ) {
2025-11-25 08:19:33 -05:00
// The task of that type is no longer active - disable the system to prevent it from running.
2026-05-10 11:47:55 -04:00
EntityCommandBuffer . SetComponentEnabled ( entityIndex , entity , branchComponent . ActiveFlagComponentType , false ) ;
2025-11-25 08:19:33 -05:00
}
// A new system type should start.
2026-05-10 11:47:55 -04:00
if ( componentType . TypeIndex ! = TypeIndex . Null & &
IncrementTrackedTypeCount ( componentType . TypeIndex , ref trackedTypeIndices , ref trackedTypeCounts ) = = 0 ) {
EntityCommandBuffer . SetComponentEnabled ( entityIndex , entity , componentType , true ) ;
2025-11-25 08:19:33 -05:00
}
branchComponent . ActiveFlagComponentType = componentType ;
}
}
var branchComponentBuffer = branchComponents ;
branchComponentBuffer [ i ] = branchComponent ;
}
}
}
2026-05-10 11:47:55 -04:00
/// <summary>
/// Increments the active count for a component type.
/// </summary>
/// <param name="typeIndex">The type index to increment.</param>
/// <param name="trackedTypeIndices">The tracked type indices.</param>
/// <param name="trackedTypeCounts">The tracked type counts.</param>
/// <returns>The previous count for the type.</returns>
[BurstCompile]
private static ushort IncrementTrackedTypeCount ( int typeIndex , ref FixedList4096Bytes < int > trackedTypeIndices , ref FixedList4096Bytes < ushort > trackedTypeCounts )
{
var trackedTypeIndex = FindTrackedTypeIndex ( typeIndex , ref trackedTypeIndices ) ;
if ( trackedTypeIndex = = - 1 ) {
trackedTypeIndices . Add ( typeIndex ) ;
trackedTypeCounts . Add ( 1 ) ;
return 0 ;
}
var previousCount = trackedTypeCounts [ trackedTypeIndex ] ;
trackedTypeCounts [ trackedTypeIndex ] = ( ushort ) ( previousCount + 1 ) ;
return previousCount ;
}
/// <summary>
/// Returns the index of the type within the tracked type list.
/// </summary>
/// <param name="typeIndex">The type index to search for.</param>
/// <param name="trackedTypeIndices">The tracked type indices.</param>
/// <returns>The index of the type within the tracked list. Returns -1 if not found.</returns>
[BurstCompile]
private static int FindTrackedTypeIndex ( int typeIndex , ref FixedList4096Bytes < int > trackedTypeIndices )
{
for ( int i = 0 ; i < trackedTypeIndices . Length ; + + i ) {
if ( trackedTypeIndices [ i ] = = typeIndex ) {
return i ;
}
}
return - 1 ;
}
/// <summary>
/// Decrements the active count for a component type.
/// </summary>
/// <param name="typeIndex">The type index to decrement.</param>
/// <param name="trackedTypeIndices">The tracked type indices.</param>
/// <param name="trackedTypeCounts">The tracked type counts.</param>
/// <returns>The new count for the type.</returns>
[BurstCompile]
private static ushort DecrementTrackedTypeCount ( int typeIndex , ref FixedList4096Bytes < int > trackedTypeIndices , ref FixedList4096Bytes < ushort > trackedTypeCounts )
{
var trackedTypeIndex = FindTrackedTypeIndex ( typeIndex , ref trackedTypeIndices ) ;
if ( trackedTypeIndex = = - 1 ) {
return 0 ;
}
var count = trackedTypeCounts [ trackedTypeIndex ] ;
if ( count < = 1 ) {
trackedTypeCounts [ trackedTypeIndex ] = 0 ;
return 0 ;
}
count - - ;
trackedTypeCounts [ trackedTypeIndex ] = count ;
return count ;
}
2025-11-25 08:19:33 -05:00
}
}
/// <summary>
/// Loops through the active tasks to determine if the system should stay active for the current tick.
/// </summary>
[UpdateInGroup(typeof(TraversalSystemGroup))]
[UpdateAfter(typeof(EvaluationSystem))]
public partial struct DetermineEvaluationSystem : ISystem
{
2026-01-03 18:19:39 -05:00
[Tooltip("Should the group stay active? An inactive tree does not run.")]
public bool Active { get ; private set ; }
[ Tooltip ( "Should the group be evaluated? This bool indicates if the entire tree should be evaluated instead of the reevaluation" +
"concept for conditional aborts. The tree will be reevaluated if any of the leaf tasks have a status of running." ) ]
public bool Evaluate { get ; private set ; }
2025-11-25 08:19:33 -05:00
private bool m_JobScheduled ;
private JobHandle m_Dependency ;
private EntityQuery m_Query32 ;
private EntityQuery m_Query64 ;
private EntityQuery m_Query128 ;
private EntityQuery m_Query512 ;
private EntityQuery m_Query4096 ;
2026-05-10 11:47:55 -04:00
private EntityCommandBuffer m_EntityCommandBuffer ;
2025-11-25 08:19:33 -05:00
private NativeArray < bool > m_Results ;
/// <summary>
/// The system has been created.
/// </summary>
/// <param name="state">The state of the system.</param>
private void OnCreate ( ref SystemState state )
{
Active = Evaluate = true ;
m_JobScheduled = false ;
2026-01-03 18:19:39 -05:00
m_Query32 = SystemAPI . QueryBuilder ( ) . WithAllRW < BranchComponent > ( ) . WithAll < TaskComponent , EvaluationComponent32 , EvaluateFlag > ( ) . WithAbsent < BakedBehaviorTree > ( ) . Build ( ) ;
m_Query64 = SystemAPI . QueryBuilder ( ) . WithAllRW < BranchComponent > ( ) . WithAll < TaskComponent , EvaluationComponent64 , EvaluateFlag > ( ) . WithAbsent < BakedBehaviorTree > ( ) . Build ( ) ;
m_Query128 = SystemAPI . QueryBuilder ( ) . WithAllRW < BranchComponent > ( ) . WithAll < TaskComponent , EvaluationComponent128 , EvaluateFlag > ( ) . WithAbsent < BakedBehaviorTree > ( ) . Build ( ) ;
m_Query512 = SystemAPI . QueryBuilder ( ) . WithAllRW < BranchComponent > ( ) . WithAll < TaskComponent , EvaluationComponent512 , EvaluateFlag > ( ) . WithAbsent < BakedBehaviorTree > ( ) . Build ( ) ;
m_Query4096 = SystemAPI . QueryBuilder ( ) . WithAllRW < BranchComponent > ( ) . WithAll < TaskComponent , EvaluationComponent4096 , EvaluateFlag > ( ) . WithAbsent < BakedBehaviorTree > ( ) . Build ( ) ;
2025-11-25 08:19:33 -05:00
}
/// <summary>
/// Executes the job to determine if the system should stay active and evaluating.
/// </summary>
/// <param name="state">The current state of the system.</param>
[BurstCompile]
private void OnUpdate ( ref SystemState state )
{
Active = Evaluate = true ;
m_JobScheduled = true ;
2026-05-10 11:47:55 -04:00
if ( m_Query32 . IsEmptyIgnoreFilter & & m_Query64 . IsEmptyIgnoreFilter & & m_Query128 . IsEmptyIgnoreFilter & & m_Query512 . IsEmptyIgnoreFilter & & m_Query4096 . IsEmptyIgnoreFilter ) {
Active = Evaluate = false ;
m_JobScheduled = false ;
return ;
2025-11-25 08:19:33 -05:00
}
2026-05-10 11:47:55 -04:00
m_EntityCommandBuffer = new EntityCommandBuffer ( Allocator . TempJob ) ;
m_Results = new NativeArray < bool > ( 3 , Allocator . TempJob , NativeArrayOptions . ClearMemory ) ;
var entityCommandBufferParallelWriter = m_EntityCommandBuffer . AsParallelWriter ( ) ;
2026-01-03 18:19:39 -05:00
// Chain jobs sequentially since they all write to the shared Results array.
m_Dependency = new DetermineEvaluationJob32 ( )
2025-11-25 08:19:33 -05:00
{
2026-05-10 11:47:55 -04:00
EntityCommandBuffer = entityCommandBufferParallelWriter ,
2025-11-25 08:19:33 -05:00
Results = m_Results
2026-01-03 18:19:39 -05:00
} . ScheduleParallel ( m_Query32 , state . Dependency ) ;
m_Dependency = new DetermineEvaluationJob64 ( )
2025-11-25 08:19:33 -05:00
{
2026-05-10 11:47:55 -04:00
EntityCommandBuffer = entityCommandBufferParallelWriter ,
2025-11-25 08:19:33 -05:00
Results = m_Results
} . ScheduleParallel ( m_Query64 , m_Dependency ) ;
2026-01-03 18:19:39 -05:00
m_Dependency = new DetermineEvaluationJob128 ( )
2025-11-25 08:19:33 -05:00
{
2026-05-10 11:47:55 -04:00
EntityCommandBuffer = entityCommandBufferParallelWriter ,
2025-11-25 08:19:33 -05:00
Results = m_Results
} . ScheduleParallel ( m_Query128 , m_Dependency ) ;
2026-01-03 18:19:39 -05:00
m_Dependency = new DetermineEvaluationJob512 ( )
2025-11-25 08:19:33 -05:00
{
2026-05-10 11:47:55 -04:00
EntityCommandBuffer = entityCommandBufferParallelWriter ,
2025-11-25 08:19:33 -05:00
Results = m_Results
} . ScheduleParallel ( m_Query512 , m_Dependency ) ;
2026-01-03 18:19:39 -05:00
m_Dependency = new DetermineEvaluationJob4096 ( )
2025-11-25 08:19:33 -05:00
{
2026-05-10 11:47:55 -04:00
EntityCommandBuffer = entityCommandBufferParallelWriter ,
2025-11-25 08:19:33 -05:00
Results = m_Results
} . ScheduleParallel ( m_Query4096 , m_Dependency ) ;
2026-01-03 18:19:39 -05:00
state . Dependency = m_Dependency ;
2025-11-25 08:19:33 -05:00
}
/// <summary>
/// Completes the job and releases any memory.
/// </summary>
/// <param name="entityManager">The running EntityManager.</param>
[BurstCompile]
public void Complete ( EntityManager entityManager )
{
if ( ! m_JobScheduled ) {
return ;
}
m_Dependency . Complete ( ) ;
2026-05-10 11:47:55 -04:00
if ( m_EntityCommandBuffer . IsCreated ) {
m_EntityCommandBuffer . Playback ( entityManager ) ;
m_EntityCommandBuffer . Dispose ( ) ;
m_EntityCommandBuffer = default ;
}
2025-11-25 08:19:33 -05:00
if ( m_Results . IsCreated ) {
if ( m_Results [ 0 ] ) {
Active = m_Results [ 1 ] ;
Evaluate = m_Results [ 2 ] ;
} else {
// If the first element is false then no trees executed.
Active = Evaluate = false ;
}
m_Results . Dispose ( ) ;
2026-05-10 11:47:55 -04:00
m_Results = default ;
2025-11-25 08:19:33 -05:00
}
m_JobScheduled = false ;
}
/// <summary>
/// The system has been destroyed.
/// </summary>
/// <param name="state">The current state of the system.</param>
private void OnDestroy ( ref SystemState state )
{
2026-05-10 11:47:55 -04:00
if ( ! m_JobScheduled ) {
2025-11-25 08:19:33 -05:00
return ;
}
2026-05-10 11:47:55 -04:00
// During world teardown these containers may already be released by ECS internals.
m_Dependency . Complete ( ) ;
m_EntityCommandBuffer = default ;
m_Results = default ;
m_JobScheduled = false ;
2025-11-25 08:19:33 -05:00
}
/// <summary>
/// Job which determine if the system should stay active. If any behavior tree should stay active then the entire system must remain active.
/// </summary>
[BurstCompile]
public partial struct DetermineEvaluationJob32 : IJobEntity
{
[Tooltip("CommandBuffer which sets the component data.")]
public EntityCommandBuffer . ParallelWriter EntityCommandBuffer ;
[Tooltip("The computed results.")]
[NativeDisableParallelForRestriction] public NativeArray < bool > Results ;
/// <summary>
/// Executes the job.
/// </summary>
/// <param name="entity">The entity that is being acted upon.</param>
/// <param name="entityIndex">The index of the entity.</param>
/// <param name="branchComponents">An array of branch components.</param>
/// <param name="taskComponents">An array of task components.</param>
/// <param name="evaluationComponent">The EvaluationComponent that belongs to the entity.</param>
[BurstCompile]
private void Execute ( Entity entity , [ EntityIndexInQuery ] int entityIndex , ref DynamicBuffer < BranchComponent > branchComponents , in DynamicBuffer < TaskComponent > taskComponents , ref EvaluationComponent32 evaluationComponent )
{
var evaluatedTasks = evaluationComponent . EvaluatedTasks ;
EvaluationUtility . DetermineEvaluation ( entity , entityIndex , ref branchComponents , taskComponents , ref evaluatedTasks , evaluationComponent . EvaluationType , evaluationComponent . MaxEvaluationCount , EntityCommandBuffer , Results ) ;
evaluationComponent . EvaluatedTasks = evaluatedTasks ;
}
}
/// <summary>
/// Job which determine if the system should stay active. If any behavior tree should stay active then the entire system must remain active.
/// </summary>
[BurstCompile]
public partial struct DetermineEvaluationJob64 : IJobEntity
{
[Tooltip("CommandBuffer which sets the component data.")]
public EntityCommandBuffer . ParallelWriter EntityCommandBuffer ;
[Tooltip("The computed results.")]
[NativeDisableParallelForRestriction] public NativeArray < bool > Results ;
/// <summary>
/// Executes the job.
/// </summary>
/// <param name="entity">The entity that is being acted upon.</param>
/// <param name="entityIndex">The index of the entity.</param>
/// <param name="branchComponents">An array of branch components.</param>
/// <param name="taskComponents">An array of task components.</param>
/// <param name="evaluationComponent">The EvaluationComponent that belongs to the entity.</param>
[BurstCompile]
private void Execute ( Entity entity , [ EntityIndexInQuery ] int entityIndex , ref DynamicBuffer < BranchComponent > branchComponents , in DynamicBuffer < TaskComponent > taskComponents , ref EvaluationComponent64 evaluationComponent )
{
var evaluatedTasks = evaluationComponent . EvaluatedTasks ;
EvaluationUtility . DetermineEvaluation ( entity , entityIndex , ref branchComponents , taskComponents , ref evaluatedTasks , evaluationComponent . EvaluationType , evaluationComponent . MaxEvaluationCount , EntityCommandBuffer , Results ) ;
evaluationComponent . EvaluatedTasks = evaluatedTasks ;
}
}
/// <summary>
/// Job which determine if the system should stay active. If any behavior tree should stay active then the entire system must remain active.
/// </summary>
[BurstCompile]
public partial struct DetermineEvaluationJob128 : IJobEntity
{
[Tooltip("CommandBuffer which sets the component data.")]
public EntityCommandBuffer . ParallelWriter EntityCommandBuffer ;
[Tooltip("The computed results.")]
[NativeDisableParallelForRestriction] public NativeArray < bool > Results ;
/// <summary>
/// Executes the job.
/// </summary>
/// <param name="entity">The entity that is being acted upon.</param>
/// <param name="entityIndex">The index of the entity.</param>
/// <param name="branchComponents">An array of branch components.</param>
/// <param name="taskComponents">An array of task components.</param>
/// <param name="evaluationComponent">The EvaluationComponent that belongs to the entity.</param>
[BurstCompile]
private void Execute ( Entity entity , [ EntityIndexInQuery ] int entityIndex , ref DynamicBuffer < BranchComponent > branchComponents , in DynamicBuffer < TaskComponent > taskComponents , ref EvaluationComponent128 evaluationComponent )
{
var evaluatedTasks = evaluationComponent . EvaluatedTasks ;
EvaluationUtility . DetermineEvaluation ( entity , entityIndex , ref branchComponents , taskComponents , ref evaluatedTasks , evaluationComponent . EvaluationType , evaluationComponent . MaxEvaluationCount , EntityCommandBuffer , Results ) ;
evaluationComponent . EvaluatedTasks = evaluatedTasks ;
}
}
/// <summary>
/// Job which determine if the system should stay active. If any behavior tree should stay active then the entire system must remain active.
/// </summary>
[BurstCompile]
public partial struct DetermineEvaluationJob512 : IJobEntity
{
[Tooltip("CommandBuffer which sets the component data.")]
public EntityCommandBuffer . ParallelWriter EntityCommandBuffer ;
[Tooltip("The computed results.")]
[NativeDisableParallelForRestriction] public NativeArray < bool > Results ;
/// <summary>
/// Executes the job.
/// </summary>
/// <param name="entity">The entity that is being acted upon.</param>
/// <param name="entityIndex">The index of the entity.</param>
/// <param name="branchComponents">An array of branch components.</param>
/// <param name="taskComponents">An array of task components.</param>
/// <param name="evaluationComponent">The EvaluationComponent that belongs to the entity.</param>
[BurstCompile]
private void Execute ( Entity entity , [ EntityIndexInQuery ] int entityIndex , ref DynamicBuffer < BranchComponent > branchComponents , in DynamicBuffer < TaskComponent > taskComponents , ref EvaluationComponent512 evaluationComponent )
{
var evaluatedTasks = evaluationComponent . EvaluatedTasks ;
EvaluationUtility . DetermineEvaluation ( entity , entityIndex , ref branchComponents , taskComponents , ref evaluatedTasks , evaluationComponent . EvaluationType , evaluationComponent . MaxEvaluationCount , EntityCommandBuffer , Results ) ;
evaluationComponent . EvaluatedTasks = evaluatedTasks ;
}
}
/// <summary>
/// Job which determine if the system should stay active. If any behavior tree should stay active then the entire system must remain active.
/// </summary>
[BurstCompile]
public partial struct DetermineEvaluationJob4096 : IJobEntity
{
[Tooltip("CommandBuffer which sets the component data.")]
public EntityCommandBuffer . ParallelWriter EntityCommandBuffer ;
[Tooltip("The computed results.")]
[NativeDisableParallelForRestriction] public NativeArray < bool > Results ;
/// <summary>
/// Executes the job.
/// </summary>
/// <param name="entity">The entity that is being acted upon.</param>
/// <param name="entityIndex">The index of the entity.</param>
/// <param name="branchComponents">An array of branch components.</param>
/// <param name="taskComponents">An array of task components.</param>
/// <param name="evaluationComponent">The EvaluationComponent that belongs to the entity.</param>
[BurstCompile]
private void Execute ( Entity entity , [ EntityIndexInQuery ] int entityIndex , ref DynamicBuffer < BranchComponent > branchComponents , in DynamicBuffer < TaskComponent > taskComponents , ref EvaluationComponent4096 evaluationComponent )
{
var evaluatedTasks = evaluationComponent . EvaluatedTasks ;
EvaluationUtility . DetermineEvaluation ( entity , entityIndex , ref branchComponents , taskComponents , ref evaluatedTasks , evaluationComponent . EvaluationType , evaluationComponent . MaxEvaluationCount , EntityCommandBuffer , Results ) ;
evaluationComponent . EvaluatedTasks = evaluatedTasks ;
}
}
}
/// <summary>
/// Utility functions for the task evaluation.
/// </summary>
[BurstCompile]
public struct EvaluationUtility
{
/// <summary>
/// Core evaluation logic that works with any FixedList type for EvaluatedTasks.
/// </summary>
/// <typeparam name="TFixedList">The type of FixedList for EvaluatedTasks.</typeparam>
/// <param name="entity">The entity that is being acted upon.</param>
/// <param name="entityIndex">The index of the entity.</param>
/// <param name="branchComponents">An array of branch components.</param>
/// <param name="taskComponents">An array of task components.</param>
/// <param name="evaluatedTasks">The evaluated tasks list.</param>
/// <param name="evaluationType">The evaluation type.</param>
/// <param name="maxEvaluationCount">The maximum evaluation count.</param>
/// <param name="entityCommandBuffer">The command buffer for setting component data.</param>
/// <param name="results">The computed results array.</param>
[BurstCompile]
public static void DetermineEvaluation < TFixedList > ( Entity entity , int entityIndex , ref DynamicBuffer < BranchComponent > branchComponents , DynamicBuffer < TaskComponent > taskComponents , ref TFixedList evaluatedTasks , EvaluationType evaluationType , ushort maxEvaluationCount , EntityCommandBuffer . ParallelWriter entityCommandBuffer , NativeArray < bool > results ) where TFixedList : struct , INativeList < ulong >
{
results [ 0 ] = true ; // The first element indicates that the job has been executed.
// No branches may be active.
var active = false ;
var evaluate = false ;
var evaluatedMask = new FixedList4096Bytes < ulong > ( ) ;
for ( int i = 0 ; i < branchComponents . Length ; + + i ) {
var branchComponent = branchComponents [ i ] ;
2026-01-03 18:19:39 -05:00
if ( branchComponent . ActiveIndex = = ushort . MaxValue | | ! branchComponent . CanExecute ) {
2025-11-25 08:19:33 -05:00
continue ;
}
active = true ;
// Interrupts are processed in a separate system that is run outside of the task execution system. As a result the branch should not continue to evaluate.
if ( branchComponent . InterruptType ! = InterruptType . None ) {
continue ;
}
var taskComponent = taskComponents [ branchComponent . ActiveIndex ] ;
2026-01-03 18:19:39 -05:00
var isParentTask = EvaluationUtility . IsParentTask ( ref taskComponents , branchComponent . ActiveIndex ) ;
2026-05-10 11:47:55 -04:00
var branchComponentBuffer = branchComponents ;
2025-11-25 08:19:33 -05:00
// The branch can evaluate if the active task is an outer node (action or conditional) and is not running OR
// the task is an inner node (composite or decorator), is running, and is not a parallel task. Parent tasks cannot run without an active child.
if ( ( ! isParentTask & & taskComponent . Status ! = TaskStatus . Running & & taskComponent . ParentIndex ! = ushort . MaxValue ) | |
2026-01-03 18:19:39 -05:00
( isParentTask & & ( taskComponent . Status = = TaskStatus . Queued | | taskComponent . Status = = TaskStatus . Running ) ) ) {
2025-11-25 08:19:33 -05:00
2026-05-10 11:47:55 -04:00
// Prevent evaluating the same task again within the same tick.
if ( branchComponent . ActiveIndex = = branchComponent . LastActiveIndex ) {
branchComponent . CanExecute = false ;
branchComponentBuffer [ i ] = branchComponent ;
continue ;
}
2026-02-13 09:22:11 -05:00
// Compute active task bit positions.
var bitIndex = branchComponent . ActiveIndex + 1 ;
var arrayIndex = bitIndex / ComponentUtility . ulongBitSize ;
var bitInUlong = bitIndex % ComponentUtility . ulongBitSize ;
while ( evaluatedMask . Length < = arrayIndex ) evaluatedMask . Add ( 0 UL ) ;
2026-05-10 11:47:55 -04:00
if ( ! isParentTask | | branchComponent . LastActiveIndex < branchComponent . ActiveIndex ) {
evaluatedMask [ arrayIndex ] | = ( 1 UL < < bitInUlong ) ;
2026-02-13 09:22:11 -05:00
}
2026-01-03 18:19:39 -05:00
2026-02-13 09:22:11 -05:00
// Check if the task has already been evaluated this tick.
var alreadyEvaluated = ( evaluatedTasks [ arrayIndex ] & ( 1 UL < < bitInUlong ) ) ! = 0 ;
// Decision to evaluate:
2026-05-10 11:47:55 -04:00
// - For parent tasks: Evaluate as long as the branch is making progress.
2026-02-13 09:22:11 -05:00
// - For non-parent tasks: evaluate if this task hasn't been evaluated yet.
2026-05-10 11:47:55 -04:00
if ( ( isParentTask & & branchComponent . ActiveIndex < branchComponent . LastActiveIndex ) | | ! alreadyEvaluated ) {
2025-11-25 08:19:33 -05:00
evaluate = true ;
2026-02-13 09:22:11 -05:00
branchComponent . LastActiveIndex = branchComponent . ActiveIndex ;
} else {
branchComponent . CanExecute = false ;
2025-11-25 08:19:33 -05:00
}
2026-05-10 11:47:55 -04:00
branchComponentBuffer [ i ] = branchComponent ;
2026-02-13 09:22:11 -05:00
evaluatedTasks [ arrayIndex ] | = evaluatedMask [ arrayIndex ] ;
} else {
branchComponent . CanExecute = false ;
2026-05-10 11:47:55 -04:00
branchComponentBuffer [ i ] = branchComponent ;
2025-11-25 08:19:33 -05:00
}
}
// If a branch is active then at least one task within that branch is active.
if ( active ) {
results [ 1 ] = true ; // Active result.
if ( evaluate ) {
if ( evaluationType = = EvaluationType . Count ) {
2026-02-13 09:22:11 -05:00
// Use the last element of EvaluatedTasks as the counter.
evaluatedTasks [ evaluatedTasks . Length - 1 ] + + ;
if ( evaluatedTasks [ evaluatedTasks . Length - 1 ] > = maxEvaluationCount ) {
// Reset the counter and bitmask elements.
for ( int i = 0 ; i < evaluatedTasks . Length ; + + i ) {
evaluatedTasks [ i ] = 0 ;
}
2025-11-25 08:19:33 -05:00
entityCommandBuffer . SetComponentEnabled < EvaluateFlag > ( entityIndex , entity , false ) ;
2026-02-13 09:22:11 -05:00
// Set the bitmask for current active tasks to prevent one extra task from being executed on subsequent frames.
SetActiveBranchBits ( ref branchComponents , ref evaluatedTasks ) ;
2025-11-25 08:19:33 -05:00
} else {
results [ 2 ] = true ; // Evaluate result.
}
} else {
2026-01-03 18:19:39 -05:00
results [ 2 ] = true ; // Evaluate result - continue the loop.
2025-11-25 08:19:33 -05:00
}
} else {
entityCommandBuffer . SetComponentEnabled < EvaluateFlag > ( entityIndex , entity , false ) ;
2026-02-13 09:22:11 -05:00
// Reset the evaluated tasks bitmask.
for ( int i = 0 ; i < evaluatedTasks . Length ; + + i ) {
evaluatedTasks [ i ] = 0 ;
2025-11-25 08:19:33 -05:00
}
2026-02-13 09:22:11 -05:00
// The system is going to stop evaluating this entity. It will be resumed immediately the next update. Because the DetermineEvaluationJob is run after the tasks
// update the EvaluatedTasks value should be set to the next active task. If this value is set to 0 then one extra task will always be executed with subsequent frames.
SetActiveBranchBits ( ref branchComponents , ref evaluatedTasks ) ;
}
}
}
2026-05-10 11:47:55 -04:00
/// <summary>
/// Is the task at the specified index a parent task.
/// </summary>
/// <param name="taskComponents">An array of task components.</param>
/// <param name="index">The index to check if it is a parent.</param>
/// <returns>True if the task at the specified index is a parent task.</returns>
[BurstCompile]
public static bool IsParentTask ( ref DynamicBuffer < TaskComponent > taskComponents , int index )
{
// The last task cannot be a parent.
if ( index = = taskComponents . Length - 1 ) {
return false ;
}
// The next child will have a parent of the current task.
if ( taskComponents [ index + 1 ] . ParentIndex = = index ) {
return true ;
}
// The parent index is different - the current task is not a parent.
return false ;
}
2026-02-13 09:22:11 -05:00
/// <summary>
/// Sets the bitmask bits for all active branches. This prevents one extra task from being executed on subsequent frames.
/// </summary>
/// <param name="branchComponents">An array of branch components.</param>
/// <param name="evaluatedTasks">The evaluated tasks list to update.</param>
[BurstCompile]
private static void SetActiveBranchBits < TFixedList > ( ref DynamicBuffer < BranchComponent > branchComponents , ref TFixedList evaluatedTasks ) where TFixedList : struct , INativeList < ulong >
{
for ( int i = 0 ; i < branchComponents . Length ; + + i ) {
var branchComponent = branchComponents [ i ] ;
if ( branchComponent . ActiveIndex = = ushort . MaxValue ) {
continue ;
2025-11-25 08:19:33 -05:00
}
2026-02-13 09:22:11 -05:00
// Compute active task bit positions.
var bitIndex = branchComponent . ActiveIndex + 1 ;
var arrayIndex = bitIndex / ComponentUtility . ulongBitSize ;
var bitInUlong = bitIndex % ComponentUtility . ulongBitSize ;
evaluatedTasks [ arrayIndex ] | = ( 1 UL < < bitInUlong ) ;
2025-11-25 08:19:33 -05:00
}
}
}
}
#endif