#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; /// /// Traverses and ensures the correct tasks are active. /// [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; /// /// The system has been created. /// /// The state of the system. private void OnCreate(ref SystemState state) { m_JobScheduled = false; m_Query = SystemAPI.QueryBuilder().WithAllRW().WithAbsent().Build(); } /// /// Starts the job which traverses the tree. /// /// The current state of the system. 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); } /// /// Completes the job and releases any memory. /// /// The running EntityManager. /// Has the system been stopped? [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; } /// /// Job which traverses the tree. /// [BurstCompile] public partial struct EvaluationJob : IJobEntity { [Tooltip("CommandBuffer which sets the component data.")] public EntityCommandBuffer.ParallelWriter EntityCommandBuffer; /// /// Executes the job. /// /// The entity that is being acted upon. /// The index of the entity.An array of branch components. /// An array of task components. [BurstCompile] public void Execute(Entity entity, [EntityIndexInQuery] int entityIndex, ref DynamicBuffer branchComponents, ref DynamicBuffer taskComponents) { 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. if (branchComponent.NextIndex != ushort.MaxValue && taskComponents[branchComponent.NextIndex].Disabled) { 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) { if (branchComponent.ActiveFlagComponentType.TypeIndex != TypeIndex.Null) { var deactivateTag = true; for (int j = 0; j < branchComponents.Length; ++j) { // The tag should be deactivated if no other tasks have the same tag type. if (i != j && branchComponents[j].ActiveIndex != ushort.MaxValue && branchComponent.ActiveFlagComponentType == branchComponents[j].ActiveFlagComponentType) { deactivateTag = false; break; } } // The task of that type is no longer active - disable the system to prevent it from running. if (deactivateTag) { EntityCommandBuffer.SetComponentEnabled(entityIndex, entity, branchComponent.ActiveFlagComponentType, false); } } // A new system type should start. if (branchComponent.ActiveIndex != ushort.MaxValue) { var taskComponent = taskComponents[branchComponent.ActiveIndex]; EntityCommandBuffer.SetComponentEnabled(entityIndex, entity, taskComponent.FlagComponentType, true); } branchComponent.ActiveFlagComponentType = componentType; } } var branchComponentBuffer = branchComponents; branchComponentBuffer[i] = branchComponent; } } } } } /// /// Loops through the active tasks to determine if the system should stay active for the current tick. /// [UpdateInGroup(typeof(TraversalSystemGroup))] [UpdateAfter(typeof(EvaluationSystem))] public partial struct DetermineEvaluationSystem : ISystem { [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; } 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; private EntityCommandBuffer m_EntityCommandBuffer32; private EntityCommandBuffer m_EntityCommandBuffer64; private EntityCommandBuffer m_EntityCommandBuffer128; private EntityCommandBuffer m_EntityCommandBuffer512; private EntityCommandBuffer m_EntityCommandBuffer4096; private NativeArray m_Results; /// /// The system has been created. /// /// The state of the system. private void OnCreate(ref SystemState state) { Active = Evaluate = true; m_JobScheduled = false; m_Query32 = SystemAPI.QueryBuilder().WithAllRW().WithAll().WithAbsent().Build(); m_Query64 = SystemAPI.QueryBuilder().WithAllRW().WithAll().WithAbsent().Build(); m_Query128 = SystemAPI.QueryBuilder().WithAllRW().WithAll().WithAbsent().Build(); m_Query512 = SystemAPI.QueryBuilder().WithAllRW().WithAll().WithAbsent().Build(); m_Query4096 = SystemAPI.QueryBuilder().WithAllRW().WithAll().WithAbsent().Build(); } /// /// Executes the job to determine if the system should stay active and evaluating. /// /// The current state of the system. [BurstCompile] private void OnUpdate(ref SystemState state) { Active = Evaluate = true; m_JobScheduled = true; m_EntityCommandBuffer32 = new EntityCommandBuffer(Allocator.TempJob); m_EntityCommandBuffer64 = new EntityCommandBuffer(Allocator.TempJob); m_EntityCommandBuffer128 = new EntityCommandBuffer(Allocator.TempJob); m_EntityCommandBuffer512 = new EntityCommandBuffer(Allocator.TempJob); m_EntityCommandBuffer4096 = new EntityCommandBuffer(Allocator.TempJob); m_Results = new NativeArray(3, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); for (int i = 0; i < m_Results.Length; ++i) { m_Results[i] = false; } // Chain jobs sequentially since they all write to the shared Results array. m_Dependency = new DetermineEvaluationJob32() { EntityCommandBuffer = m_EntityCommandBuffer32.AsParallelWriter(), Results = m_Results }.ScheduleParallel(m_Query32, state.Dependency); m_Dependency = new DetermineEvaluationJob64() { EntityCommandBuffer = m_EntityCommandBuffer64.AsParallelWriter(), Results = m_Results }.ScheduleParallel(m_Query64, m_Dependency); m_Dependency = new DetermineEvaluationJob128() { EntityCommandBuffer = m_EntityCommandBuffer128.AsParallelWriter(), Results = m_Results }.ScheduleParallel(m_Query128, m_Dependency); m_Dependency = new DetermineEvaluationJob512() { EntityCommandBuffer = m_EntityCommandBuffer512.AsParallelWriter(), Results = m_Results }.ScheduleParallel(m_Query512, m_Dependency); m_Dependency = new DetermineEvaluationJob4096() { EntityCommandBuffer = m_EntityCommandBuffer4096.AsParallelWriter(), Results = m_Results }.ScheduleParallel(m_Query4096, m_Dependency); state.Dependency = m_Dependency; } /// /// Completes the job and releases any memory. /// /// The running EntityManager. [BurstCompile] public void Complete(EntityManager entityManager) { if (!m_JobScheduled) { return; } m_Dependency.Complete(); m_EntityCommandBuffer32.Playback(entityManager); m_EntityCommandBuffer32.Dispose(); m_EntityCommandBuffer64.Playback(entityManager); m_EntityCommandBuffer64.Dispose(); m_EntityCommandBuffer128.Playback(entityManager); m_EntityCommandBuffer128.Dispose(); m_EntityCommandBuffer512.Playback(entityManager); m_EntityCommandBuffer512.Dispose(); m_EntityCommandBuffer4096.Playback(entityManager); m_EntityCommandBuffer4096.Dispose(); 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(); } m_JobScheduled = false; } /// /// The system has been destroyed. /// /// The current state of the system. private void OnDestroy(ref SystemState state) { if (m_Dependency.IsCompleted) { return; } m_Results.Dispose(); } /// /// Job which determine if the system should stay active. If any behavior tree should stay active then the entire system must remain active. /// [BurstCompile] public partial struct DetermineEvaluationJob32 : IJobEntity { [Tooltip("CommandBuffer which sets the component data.")] public EntityCommandBuffer.ParallelWriter EntityCommandBuffer; [Tooltip("The computed results.")] [NativeDisableParallelForRestriction] public NativeArray Results; /// /// Executes the job. /// /// The entity that is being acted upon. /// The index of the entity. /// An array of branch components. /// An array of task components. /// The EvaluationComponent that belongs to the entity. [BurstCompile] private void Execute(Entity entity, [EntityIndexInQuery] int entityIndex, ref DynamicBuffer branchComponents, in DynamicBuffer 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; } } /// /// Job which determine if the system should stay active. If any behavior tree should stay active then the entire system must remain active. /// [BurstCompile] public partial struct DetermineEvaluationJob64 : IJobEntity { [Tooltip("CommandBuffer which sets the component data.")] public EntityCommandBuffer.ParallelWriter EntityCommandBuffer; [Tooltip("The computed results.")] [NativeDisableParallelForRestriction] public NativeArray Results; /// /// Executes the job. /// /// The entity that is being acted upon. /// The index of the entity. /// An array of branch components. /// An array of task components. /// The EvaluationComponent that belongs to the entity. [BurstCompile] private void Execute(Entity entity, [EntityIndexInQuery] int entityIndex, ref DynamicBuffer branchComponents, in DynamicBuffer 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; } } /// /// Job which determine if the system should stay active. If any behavior tree should stay active then the entire system must remain active. /// [BurstCompile] public partial struct DetermineEvaluationJob128 : IJobEntity { [Tooltip("CommandBuffer which sets the component data.")] public EntityCommandBuffer.ParallelWriter EntityCommandBuffer; [Tooltip("The computed results.")] [NativeDisableParallelForRestriction] public NativeArray Results; /// /// Executes the job. /// /// The entity that is being acted upon. /// The index of the entity. /// An array of branch components. /// An array of task components. /// The EvaluationComponent that belongs to the entity. [BurstCompile] private void Execute(Entity entity, [EntityIndexInQuery] int entityIndex, ref DynamicBuffer branchComponents, in DynamicBuffer 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; } } /// /// Job which determine if the system should stay active. If any behavior tree should stay active then the entire system must remain active. /// [BurstCompile] public partial struct DetermineEvaluationJob512 : IJobEntity { [Tooltip("CommandBuffer which sets the component data.")] public EntityCommandBuffer.ParallelWriter EntityCommandBuffer; [Tooltip("The computed results.")] [NativeDisableParallelForRestriction] public NativeArray Results; /// /// Executes the job. /// /// The entity that is being acted upon. /// The index of the entity. /// An array of branch components. /// An array of task components. /// The EvaluationComponent that belongs to the entity. [BurstCompile] private void Execute(Entity entity, [EntityIndexInQuery] int entityIndex, ref DynamicBuffer branchComponents, in DynamicBuffer 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; } } /// /// Job which determine if the system should stay active. If any behavior tree should stay active then the entire system must remain active. /// [BurstCompile] public partial struct DetermineEvaluationJob4096 : IJobEntity { [Tooltip("CommandBuffer which sets the component data.")] public EntityCommandBuffer.ParallelWriter EntityCommandBuffer; [Tooltip("The computed results.")] [NativeDisableParallelForRestriction] public NativeArray Results; /// /// Executes the job. /// /// The entity that is being acted upon. /// The index of the entity. /// An array of branch components. /// An array of task components. /// The EvaluationComponent that belongs to the entity. [BurstCompile] private void Execute(Entity entity, [EntityIndexInQuery] int entityIndex, ref DynamicBuffer branchComponents, in DynamicBuffer 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; } } } /// /// Utility functions for the task evaluation. /// [BurstCompile] public struct EvaluationUtility { /// /// Is the task at the specified index a parent task. /// /// An array of task components. /// The index to check if it is a parent. /// True if the task at the specified index is a parent task. [BurstCompile] public static bool IsParentTask(ref DynamicBuffer 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; } /// /// Core evaluation logic that works with any FixedList type for EvaluatedTasks. /// /// The type of FixedList for EvaluatedTasks. /// The entity that is being acted upon. /// The index of the entity. /// An array of branch components. /// An array of task components. /// The evaluated tasks list. /// The evaluation type. /// The maximum evaluation count. /// The command buffer for setting component data. /// The computed results array. [BurstCompile] public static void DetermineEvaluation(Entity entity, int entityIndex, ref DynamicBuffer branchComponents, DynamicBuffer taskComponents, ref TFixedList evaluatedTasks, EvaluationType evaluationType, ushort maxEvaluationCount, EntityCommandBuffer.ParallelWriter entityCommandBuffer, NativeArray results) where TFixedList : struct, INativeList { 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(); for (int i = 0; i < branchComponents.Length; ++i) { var branchComponent = branchComponents[i]; if (branchComponent.ActiveIndex == ushort.MaxValue || !branchComponent.CanExecute) { 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]; var isParentTask = EvaluationUtility.IsParentTask(ref taskComponents, branchComponent.ActiveIndex); // 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) || (isParentTask && (taskComponent.Status == TaskStatus.Queued || taskComponent.Status == TaskStatus.Running))) { // 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(0UL); evaluatedMask[arrayIndex] |= (1UL << bitInUlong); // Prevent evaluating the same task again within the same tick. if (branchComponent.ActiveIndex == branchComponent.LastActiveIndex) { branchComponent.CanExecute = false; branchComponents.ElementAt(i) = branchComponent; continue; } // Check if the task has already been evaluated this tick. var alreadyEvaluated = (evaluatedTasks[arrayIndex] & (1UL << bitInUlong)) != 0; // Decision to evaluate: // - For parent tasks: always evaluate. The parent task should never be the last executing task. // - For non-parent tasks: evaluate if this task hasn't been evaluated yet. if (isParentTask || !alreadyEvaluated) { evaluate = true; branchComponent.LastActiveIndex = branchComponent.ActiveIndex; } else { branchComponent.CanExecute = false; } branchComponents.ElementAt(i) = branchComponent; evaluatedTasks[arrayIndex] |= evaluatedMask[arrayIndex]; } else { branchComponent.CanExecute = false; branchComponents.ElementAt(i) = branchComponent; } } // 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) { // 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; } entityCommandBuffer.SetComponentEnabled(entityIndex, entity, false); // Set the bitmask for current active tasks to prevent one extra task from being executed on subsequent frames. SetActiveBranchBits(ref branchComponents, ref evaluatedTasks); } else { results[2] = true; // Evaluate result. } } else { results[2] = true; // Evaluate result - continue the loop. } } else { entityCommandBuffer.SetComponentEnabled(entityIndex, entity, false); // Reset the evaluated tasks bitmask. for (int i = 0; i < evaluatedTasks.Length; ++i) { evaluatedTasks[i] = 0; } // 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); } } } /// /// Sets the bitmask bits for all active branches. This prevents one extra task from being executed on subsequent frames. /// /// An array of branch components. /// The evaluated tasks list to update. [BurstCompile] private static void SetActiveBranchBits(ref DynamicBuffer branchComponents, ref TFixedList evaluatedTasks) where TFixedList : struct, INativeList { for (int i = 0; i < branchComponents.Length; ++i) { var branchComponent = branchComponents[i]; if (branchComponent.ActiveIndex == ushort.MaxValue) { continue; } // Compute active task bit positions. var bitIndex = branchComponent.ActiveIndex + 1; var arrayIndex = bitIndex / ComponentUtility.ulongBitSize; var bitInUlong = bitIndex % ComponentUtility.ulongBitSize; evaluatedTasks[arrayIndex] |= (1UL << bitInUlong); } } } } #endif