#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; /// /// Loops through the active tasks to determine if the system should stay active for the current tick. /// [UpdateInGroup(typeof(TraversalSystemGroup))] [UpdateBefore(typeof(TraversalTaskSystemGroup))] public partial struct DetermineEvaluationSystem : ISystem { private bool m_JobScheduled; private EntityQuery m_Query; private JobHandle m_Dependency; private EntityCommandBuffer m_EntityCommandBuffer; private NativeArray m_Results; [Tooltip("Should the group stay active? An inactive tree does not run.")] public bool Active { get; private set; } [Tooltip("Should the group be evaluate? 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; } /// /// The system has been created. /// /// The state of the system. private void OnCreate(ref SystemState state) { Active = Evaluate = true; m_JobScheduled = false; m_Query = SystemAPI.QueryBuilder().WithAllRW().WithAll().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_EntityCommandBuffer = 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; } state.Dependency = new DetermineEvaluationJob() { EntityCommandBuffer = m_EntityCommandBuffer.AsParallelWriter(), Results = m_Results }.ScheduleParallel(m_Query, state.Dependency); m_Dependency = state.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_EntityCommandBuffer.Playback(entityManager); m_EntityCommandBuffer.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 DetermineEvaluationJob : 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, ref DynamicBuffer taskComponents, ref EvaluationComponent evaluationComponent) { Results[0] = true; // The first element indicates that the job has been executed. // No branches may be active. var active = false; for (int i = 0; i < branchComponents.Length; ++i) { var branchComponent = branchComponents[i]; if (branchComponent.ActiveIndex == ushort.MaxValue) { continue; } active = true; break; } // If a branch is active then at least one task within that branch is active. var evaluate = false; if (active) { var continueEvaluation = false; for (int i = 0; i < branchComponents.Length; ++i) { var branchComponent = branchComponents[i]; if (branchComponent.ActiveIndex == ushort.MaxValue) { continue; } var taskComponent = taskComponents[branchComponent.ActiveIndex]; var taskComponentBuffer = taskComponents; var isParentTask = EvaluationUtility.IsParentTask(ref taskComponentBuffer, branchComponent.ActiveIndex); // Continue evaluation 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) && !EvaluationUtility.IsParallelTask(ref taskComponentBuffer, branchComponent.ActiveIndex))) { continueEvaluation = true; break; } } if (continueEvaluation) { evaluationComponent.EvaluationCount++; // Disable the evaluation if too many tasks have run. if ((evaluationComponent.EvaluationType == EvaluationType.EntireTree && evaluationComponent.EvaluationCount >= taskComponents.Length * 2) || (evaluationComponent.EvaluationType == EvaluationType.Count && evaluationComponent.EvaluationCount >= evaluationComponent.MaxEvaluationCount)) { // Reset the count when the component has been disabled. evaluationComponent.EvaluationCount = 0; EntityCommandBuffer.SetComponentEnabled(entityIndex, entity, false); } else { evaluate = true; } } else { evaluationComponent.EvaluationCount = 0; EntityCommandBuffer.SetComponentEnabled(entityIndex, entity, false); } } // Sets the results. These results are not thread safe but that's ok because the value will only be read after the job is complete. if (active) { Results[1] = true; } if (evaluate) { Results[2] = true; } } } } /// /// Traverses and ensures the correct tasks are active. /// [UpdateInGroup(typeof(TraversalSystemGroup), OrderFirst = true)] [UpdateBefore(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().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); 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) { EvaluationUtility.Traverse(ref entity, ref EntityCommandBuffer, entityIndex, ref branchComponents, ref taskComponents); } } } /// /// 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; } /// /// Is the task at the specified index a parallel task. /// /// An array of task components. /// The index to check if it is parallel. /// True if the task at the specified index is a parallel task. [BurstCompile] public static bool IsParallelTask(ref DynamicBuffer taskComponents, int index) { // The task is a parent task, but is it parallel? return taskComponents[index].BranchIndex != taskComponents[index + 1].BranchIndex; } /// /// Traverses through the TaskComponents to determine the active index. This method will update the corresponding tag. /// /// The entity being traversed. /// The EntityCommandBuffer used to update the tag. /// The index of the entity. /// An array of branch components. /// An array of task components. [BurstCompile] public static void Traverse(ref Entity entity, ref EntityCommandBuffer.ParallelWriter entityCommandBuffer, 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]; nextTaskComponent.Status = nextTaskComponent.Status == TaskStatus.Running ? TaskStatus.Running : TaskStatus.Queued; taskComponentBuffer[branchComponent.NextIndex] = nextTaskComponent; 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; } } branchComponent.ActiveIndex = branchComponent.NextIndex; // Change the component tag if the task type is different. var componentType = branchComponent.ActiveIndex != ushort.MaxValue ? taskComponents[branchComponent.ActiveIndex].TagComponentType : new ComponentType(); if (componentType != branchComponent.ActiveTagComponentType) { if (branchComponent.ActiveTagComponentType.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.ActiveTagComponentType == branchComponents[j].ActiveTagComponentType) { 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.ActiveTagComponentType, false); } } // A new system type should start. if (branchComponent.ActiveIndex != ushort.MaxValue) { var taskComponent = taskComponents[branchComponent.ActiveIndex]; entityCommandBuffer.SetComponentEnabled(entityIndex, entity, taskComponent.TagComponentType, true); } branchComponent.ActiveTagComponentType = componentType; } } var branchComponentBuffer = branchComponents; branchComponentBuffer[i] = branchComponent; } } } } } #endif