#if GRAPH_DESIGNER /// --------------------------------------------- /// Behavior Designer /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.BehaviorDesigner.Runtime.Tasks.Composites { using Opsive.BehaviorDesigner.Runtime.Components; using Opsive.BehaviorDesigner.Runtime.Utility; using Opsive.GraphDesigner.Runtime; using Opsive.GraphDesigner.Runtime.Variables; using Opsive.Shared.Utility; using System; using Unity.Burst; using Unity.Entities; using UnityEngine; /// /// A node representation of the selector evaluator task. /// [NodeIcon("3a531955b1343524db597a112895cd7a", "35126a690fb2fba4ba6f9d1af773992f")] [Opsive.Shared.Utility.Description("The selector evaluator is a selector task which reevaluates its children every tick. It will run the highest priority child which returns a task status of running. " + "This is done each tick. If a lower priority child is running and the next frame a higher priority child wants to run it will interrupt the lower priority child. " + "The selector evaluator will return success as soon as the first child returns success otherwise it will keep trying higher priority children. This task mimics " + "the conditional abort functionality except the child tasks don't always have to be conditional tasks.")] public class SelectorEvaluator : ECSCompositeTask, IParentNode, IParallelNode, ISavableTask, ICloneable { private ushort m_ComponentIndex; /// /// Returns a new TBufferElement for use by the system. /// /// A new TBufferElement for use by the system. public override SelectorEvaluatorComponent GetBufferElement() { return new SelectorEvaluatorComponent() { Index = RuntimeIndex, ActiveChildIndex = ushort.MaxValue, ReevaluateChildIndex = ushort.MaxValue, }; } /// /// Adds the IBufferElementData to the entity. /// /// The world that the entity exists in. /// The entity that the IBufferElementData should be assigned to. /// The ECS variable registry for registering SharedVariable fields. /// The GameObject that the entity is attached to. /// The index of the element within the buffer. public override int AddBufferElement(World world, Entity entity, ECSVariableRegistry registry, GameObject gameObject) { m_ComponentIndex = (ushort)base.AddBufferElement(world, entity, registry, gameObject); ComponentUtility.AddInterruptComponents(world.EntityManager, entity); return m_ComponentIndex; } /// /// Specifies the type of reflection that should be used to save the task. /// /// The index of the sub-task. This is used for the task set allowing each contained task to have their own save type. public MemberVisibility GetSaveReflectionType(int index) { return MemberVisibility.None; } /// /// Returns the current task state. /// /// The DOTS world. /// The DOTS entity. /// The current task state. public object Save(World world, Entity entity) { var selectorEvaluatorComponents = world.EntityManager.GetBuffer(entity); var selectorEvaluatorComponent = selectorEvaluatorComponents[m_ComponentIndex]; return new object[] { selectorEvaluatorComponent.ActiveChildIndex, selectorEvaluatorComponent.ReevaluateChildIndex }; } /// /// Loads the previous task state. /// /// The previous task state. /// The DOTS world. /// The DOTS entity. public void Load(object saveData, World world, Entity entity) { var selectorEvaluatorComponents = world.EntityManager.GetBuffer(entity); var selectorEvaluatorComponent = selectorEvaluatorComponents[m_ComponentIndex]; var taskSaveData = saveData as object[]; if (taskSaveData != null && taskSaveData.Length == 2) { selectorEvaluatorComponent.ActiveChildIndex = (ushort)taskSaveData[0]; selectorEvaluatorComponent.ReevaluateChildIndex = (ushort)taskSaveData[1]; } else { selectorEvaluatorComponent.ActiveChildIndex = (ushort)saveData; selectorEvaluatorComponent.ReevaluateChildIndex = ushort.MaxValue; } selectorEvaluatorComponents[m_ComponentIndex] = selectorEvaluatorComponent; } /// /// Creates a deep clone of the component. /// /// A deep clone of the component. public object Clone() { var clone = Activator.CreateInstance(); clone.Index = Index; clone.ParentIndex = ParentIndex; clone.SiblingIndex = SiblingIndex; clone.Enabled = Enabled; return clone; } /// /// Returns true if the runtime index is within a child subtree that is only being reevaluated. /// /// The runtime index to test. /// The runtime task components. /// The selector evaluator components. /// True if the runtime index is within a reevaluation subtree. public static bool IsRuntimeIndexReevaluating(ushort runtimeIndex, DynamicBuffer taskComponents, DynamicBuffer selectorEvaluatorComponents) { for (int i = 0; i < selectorEvaluatorComponents.Length; ++i) { var selectorEvaluatorComponent = selectorEvaluatorComponents[i]; if (selectorEvaluatorComponent.ReevaluateChildIndex == ushort.MaxValue) { continue; } if (IsRuntimeIndexWithinTaskRange(runtimeIndex, selectorEvaluatorComponent.ActiveChildIndex, taskComponents)) { continue; } if (IsRuntimeIndexWithinTaskRange(runtimeIndex, selectorEvaluatorComponent.ReevaluateChildIndex, taskComponents)) { return true; } } return false; } /// /// Returns true if the runtime index is within the specified task's subtree. /// /// The runtime index to test. /// The root of the subtree. /// The runtime task components. /// True if the runtime index is within the task range. private static bool IsRuntimeIndexWithinTaskRange(ushort runtimeIndex, ushort taskRootIndex, DynamicBuffer taskComponents) { if (taskRootIndex == ushort.MaxValue || taskRootIndex >= taskComponents.Length) { return false; } var taskComponent = taskComponents[taskRootIndex]; return runtimeIndex >= taskRootIndex && runtimeIndex <= taskComponent.ChildUpperIndex; } } /// /// The DOTS data structure for the SelectorEvaluator class. /// public struct SelectorEvaluatorComponent : IBufferElementData { [Tooltip("The index of the node.")] public ushort Index; [Tooltip("The index of the child that is currently active.")] public ushort ActiveChildIndex; [Tooltip("The higher-priority child that is currently being reevaluated.")] public ushort ReevaluateChildIndex; } /// /// A DOTS tag indicating when a SelectorEvaluator node is active. /// public struct SelectorEvaluatorFlag : IComponentData, IEnableableComponent { } /// /// Runs the SelectorEvaluator logic. /// [DisableAutoCreation] public partial struct SelectorEvaluatorTaskSystem : ISystem { private EntityQuery m_Query; /// /// Builds the query. /// /// The current state of the system. private void OnCreate(ref SystemState state) { m_Query = SystemAPI.QueryBuilder().WithAllRW().WithAllRW().WithAllRW().WithAll().Build(); } /// /// Creates the job. /// /// The current state of the system. [BurstCompile] private void OnUpdate(ref SystemState state) { var ecb = SystemAPI.GetSingleton().CreateCommandBuffer(state.WorldUnmanaged); state.Dependency = new SelectorEvaluatorJob() { EntityCommandBuffer = ecb.AsParallelWriter() }.ScheduleParallel(m_Query, state.Dependency); } /// /// Job which executes the task logic. /// [BurstCompile] private partial struct SelectorEvaluatorJob : IJobEntity { [Tooltip("CommandBuffer which sets the component data.")] public EntityCommandBuffer.ParallelWriter EntityCommandBuffer; /// /// Executes the selector evaluator logic. /// /// The entity that is being acted upon. /// The index of the entity. /// An array of BranchComponents. /// An array of TaskComponents. /// An array of SelectorEvaluatorComponents. [BurstCompile] public void Execute(Entity entity, [EntityIndexInQuery] int entityIndex, ref DynamicBuffer branchComponents, ref DynamicBuffer taskComponents, ref DynamicBuffer selectorEvaluatorComponents) { for (int i = 0; i < selectorEvaluatorComponents.Length; ++i) { var selectorEvaluatorComponent = selectorEvaluatorComponents[i]; var taskComponent = taskComponents[selectorEvaluatorComponent.Index]; var taskStatus = taskComponent.Status; // Skip inactive tasks before any branch lookups. if (taskStatus != TaskStatus.Queued && taskStatus != TaskStatus.Running) { continue; } var branchComponent = branchComponents[taskComponent.BranchIndex]; // Do not continue if there will be an interrupt or the branch cannot execute. if (branchComponent.InterruptType != InterruptType.None || !branchComponent.CanExecute) { continue; } if (taskStatus == TaskStatus.Queued) { StartSelector(ref selectorEvaluatorComponent, ref taskComponent, ref branchComponent, ref taskComponents, ref branchComponents); selectorEvaluatorComponents[i] = selectorEvaluatorComponent; taskComponents[taskComponent.Index] = taskComponent; branchComponents[taskComponent.BranchIndex] = branchComponent; continue; } var interrupted = false; var ended = false; var reevaluationProcessed = false; if (selectorEvaluatorComponent.ReevaluateChildIndex != ushort.MaxValue) { ended = UpdateReevaluation(ref selectorEvaluatorComponent, ref taskComponent, ref branchComponent, ref taskComponents, ref branchComponents, ref interrupted); reevaluationProcessed = true; } if (!ended && !reevaluationProcessed) { UpdateActiveChild(ref selectorEvaluatorComponent, ref taskComponent, ref branchComponent, ref taskComponents, ref branchComponents, ref interrupted); } if (interrupted) { EntityCommandBuffer.SetComponentEnabled(entityIndex, entity, true); } selectorEvaluatorComponents[i] = selectorEvaluatorComponent; taskComponents[taskComponent.Index] = taskComponent; branchComponents[taskComponent.BranchIndex] = branchComponent; } } /// /// Starts the selector evaluator. /// /// The selector evaluator component. /// The task component. /// The branch component. /// All task components. /// All branch components. private static void StartSelector(ref SelectorEvaluatorComponent selectorEvaluatorComponent, ref TaskComponent taskComponent, ref BranchComponent branchComponent, ref DynamicBuffer taskComponents, ref DynamicBuffer branchComponents) { taskComponent.Status = TaskStatus.Running; selectorEvaluatorComponent.ActiveChildIndex = (ushort)(taskComponent.Index + 1); selectorEvaluatorComponent.ReevaluateChildIndex = ushort.MaxValue; if (selectorEvaluatorComponent.ActiveChildIndex >= taskComponents.Length || taskComponents[selectorEvaluatorComponent.ActiveChildIndex].ParentIndex != taskComponent.Index) { taskComponent.Status = TaskStatus.Failure; selectorEvaluatorComponent.ActiveChildIndex = ushort.MaxValue; branchComponent.NextIndex = taskComponent.ParentIndex; return; } QueueChild(selectorEvaluatorComponent.ActiveChildIndex, ref taskComponents, ref branchComponents); } /// /// Updates the higher-priority child that is currently being reevaluated. /// /// The selector evaluator component. /// The task component. /// The branch component. /// All task components. /// All branch components. /// Was a running child interrupted? /// True if the selector evaluator has ended. private static bool UpdateReevaluation(ref SelectorEvaluatorComponent selectorEvaluatorComponent, ref TaskComponent taskComponent, ref BranchComponent branchComponent, ref DynamicBuffer taskComponents, ref DynamicBuffer branchComponents, ref bool interrupted) { var reevaluateTaskComponent = taskComponents[selectorEvaluatorComponent.ReevaluateChildIndex]; if (reevaluateTaskComponent.Status == TaskStatus.Queued) { return false; } if (reevaluateTaskComponent.Status == TaskStatus.Running) { var previousActiveChildIndex = selectorEvaluatorComponent.ActiveChildIndex; selectorEvaluatorComponent.ActiveChildIndex = selectorEvaluatorComponent.ReevaluateChildIndex; selectorEvaluatorComponent.ReevaluateChildIndex = ushort.MaxValue; if (previousActiveChildIndex != selectorEvaluatorComponent.ActiveChildIndex) { interrupted |= AbortChildSubtree(previousActiveChildIndex, TaskStatus.Failure, ref taskComponents, ref branchComponents); } return false; } if (reevaluateTaskComponent.Status == TaskStatus.Success) { CompleteChildBranch(reevaluateTaskComponent.BranchIndex, ref branchComponents); CompleteSelector(TaskStatus.Success, ref selectorEvaluatorComponent, ref taskComponent, ref branchComponent, ref taskComponents, ref branchComponents, ref interrupted); return true; } CompleteChildBranch(reevaluateTaskComponent.BranchIndex, ref branchComponents); var nextReevaluateChildIndex = reevaluateTaskComponent.SiblingIndex; if (nextReevaluateChildIndex != ushort.MaxValue && nextReevaluateChildIndex != selectorEvaluatorComponent.ActiveChildIndex) { selectorEvaluatorComponent.ReevaluateChildIndex = nextReevaluateChildIndex; QueueChild(nextReevaluateChildIndex, ref taskComponents, ref branchComponents); } else { selectorEvaluatorComponent.ReevaluateChildIndex = ushort.MaxValue; } return false; } /// /// Updates the active child. /// /// The selector evaluator component. /// The task component. /// The branch component. /// All task components. /// All branch components. /// Was a running child interrupted? private static void UpdateActiveChild(ref SelectorEvaluatorComponent selectorEvaluatorComponent, ref TaskComponent taskComponent, ref BranchComponent branchComponent, ref DynamicBuffer taskComponents, ref DynamicBuffer branchComponents, ref bool interrupted) { if (selectorEvaluatorComponent.ActiveChildIndex == ushort.MaxValue || selectorEvaluatorComponent.ActiveChildIndex >= taskComponents.Length) { CompleteSelector(TaskStatus.Failure, ref selectorEvaluatorComponent, ref taskComponent, ref branchComponent, ref taskComponents, ref branchComponents, ref interrupted); return; } var activeTaskComponent = taskComponents[selectorEvaluatorComponent.ActiveChildIndex]; if (activeTaskComponent.Status == TaskStatus.Success) { CompleteChildBranch(activeTaskComponent.BranchIndex, ref branchComponents); CompleteSelector(TaskStatus.Success, ref selectorEvaluatorComponent, ref taskComponent, ref branchComponent, ref taskComponents, ref branchComponents, ref interrupted); return; } if (activeTaskComponent.Status == TaskStatus.Failure || activeTaskComponent.Status == TaskStatus.Inactive) { CompleteChildBranch(activeTaskComponent.BranchIndex, ref branchComponents); var nextChildIndex = activeTaskComponent.SiblingIndex; if (nextChildIndex == ushort.MaxValue) { CompleteSelector(TaskStatus.Failure, ref selectorEvaluatorComponent, ref taskComponent, ref branchComponent, ref taskComponents, ref branchComponents, ref interrupted); } else { selectorEvaluatorComponent.ActiveChildIndex = nextChildIndex; selectorEvaluatorComponent.ReevaluateChildIndex = ushort.MaxValue; QueueChild(nextChildIndex, ref taskComponents, ref branchComponents); } return; } if (activeTaskComponent.Status != TaskStatus.Running) { return; } var firstChildIndex = (ushort)(taskComponent.Index + 1); if (firstChildIndex != selectorEvaluatorComponent.ActiveChildIndex && firstChildIndex < taskComponents.Length && taskComponents[firstChildIndex].ParentIndex == taskComponent.Index) { selectorEvaluatorComponent.ReevaluateChildIndex = firstChildIndex; QueueChild(firstChildIndex, ref taskComponents, ref branchComponents); } } /// /// Queues a child task on its parallel branch. /// /// The child index to queue. /// All task components. /// All branch components. private static void QueueChild(ushort childIndex, ref DynamicBuffer taskComponents, ref DynamicBuffer branchComponents) { if (childIndex == ushort.MaxValue || childIndex >= taskComponents.Length) { return; } var childTaskComponent = taskComponents[childIndex]; childTaskComponent.Status = TaskStatus.Queued; taskComponents[childIndex] = childTaskComponent; var childBranchComponent = branchComponents[childTaskComponent.BranchIndex]; childBranchComponent.NextIndex = childIndex; branchComponents[childTaskComponent.BranchIndex] = childBranchComponent; } /// /// Marks the child branch as complete so it does not traverse back into the parallel parent. /// /// The branch index that should be completed. /// All branch components. private static void CompleteChildBranch(ushort branchIndex, ref DynamicBuffer branchComponents) { var childBranchComponent = branchComponents[branchIndex]; if (childBranchComponent.NextIndex != ushort.MaxValue) { childBranchComponent.NextIndex = ushort.MaxValue; branchComponents[branchIndex] = childBranchComponent; } } /// /// Completes the selector evaluator. /// /// The completion status. /// The selector evaluator component. /// The task component. /// The branch component. /// All task components. /// All branch components. /// Was a running child interrupted? private static void CompleteSelector(TaskStatus status, ref SelectorEvaluatorComponent selectorEvaluatorComponent, ref TaskComponent taskComponent, ref BranchComponent branchComponent, ref DynamicBuffer taskComponents, ref DynamicBuffer branchComponents, ref bool interrupted) { taskComponent.Status = status; branchComponent.NextIndex = taskComponent.ParentIndex; var maxChildIndex = taskComponent.ChildUpperIndex > taskComponent.Index ? taskComponent.ChildUpperIndex : taskComponent.Index; interrupted |= AbortTaskRange((ushort)(taskComponent.Index + 1), maxChildIndex, TaskStatus.Failure, ref taskComponents, ref branchComponents); selectorEvaluatorComponent.ActiveChildIndex = ushort.MaxValue; selectorEvaluatorComponent.ReevaluateChildIndex = ushort.MaxValue; } /// /// Aborts any running or queued tasks within the specified child subtree. /// /// The subtree root. /// The status that should be assigned to the aborted tasks. /// All task components. /// All branch components. /// True if a task was aborted. private static bool AbortChildSubtree(ushort childIndex, TaskStatus status, ref DynamicBuffer taskComponents, ref DynamicBuffer branchComponents) { if (childIndex == ushort.MaxValue || childIndex >= taskComponents.Length) { return false; } var childTaskComponent = taskComponents[childIndex]; var maxChildIndex = childTaskComponent.ChildUpperIndex > childTaskComponent.Index ? childTaskComponent.ChildUpperIndex : childTaskComponent.Index; return AbortTaskRange(childIndex, maxChildIndex, status, ref taskComponents, ref branchComponents); } /// /// Aborts any running or queued tasks within the specified index range. /// /// The first index to abort. /// The last index to abort. /// The status that should be assigned to the aborted tasks. /// All task components. /// All branch components. /// True if a task was aborted. private static bool AbortTaskRange(ushort startIndex, ushort endIndex, TaskStatus status, ref DynamicBuffer taskComponents, ref DynamicBuffer branchComponents) { var interrupted = false; for (ushort i = startIndex; i <= endIndex; ++i) { var taskComponent = taskComponents[i]; if (taskComponent.Status == TaskStatus.Queued || taskComponent.Status == TaskStatus.Running) { taskComponent.Status = status; taskComponents[i] = taskComponent; var branchComponent = branchComponents[taskComponent.BranchIndex]; if (branchComponent.NextIndex != ushort.MaxValue) { branchComponent.NextIndex = ushort.MaxValue; branchComponents[taskComponent.BranchIndex] = branchComponent; } interrupted = true; } } return interrupted; } } } } #endif