#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.Variables.ECS; using Opsive.GraphDesigner.Runtime; using Opsive.Shared.Utility; using System; using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using UnityEngine; /// /// A node representation of the priority selector task. /// [NodeIcon("cea0f2b6cee06a742bb35dcc40202e8e", "744afc2640950e045961296f1d5800d7")] [Opsive.Shared.Utility.Description("Similar to the selector task, the priority selector task will return success as soon as a child task returns success. " + "Instead of running the tasks sequentially from left to right within the tree, the priority selector will ask the task what its priority is to determine the order. " + "The higher priority tasks have a higher chance at being run first.")] public class PrioritySelector : ECSCompositeTask, IParentNode, 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 PrioritySelectorComponent GetBufferElement() { return new PrioritySelectorComponent() { Index = RuntimeIndex, }; } /// /// 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); 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 prioritySelectorComponents = world.EntityManager.GetBuffer(entity); var prioritySelectorComponent = prioritySelectorComponents[m_ComponentIndex]; // Save the active child index. return prioritySelectorComponent.ActiveRelativeChildIndex; } /// /// 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 prioritySelectorComponents = world.EntityManager.GetBuffer(entity); var prioritySelectorComponent = prioritySelectorComponents[m_ComponentIndex]; prioritySelectorComponent.ActiveRelativeChildIndex = (ushort)saveData; prioritySelectorComponents[m_ComponentIndex] = prioritySelectorComponent; } /// /// 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; } } /// /// Immutable blob entry mapping a task index to its PriorityValueComponent index. /// public struct PriorityItemBlobEntry { [Tooltip("The index of the task.")] public ushort TaskIndex; [Tooltip("The index of the PriorityValueComponent. ushort.MaxValue indicates no corresponding component.")] public ushort PriorityValueIndex; } /// /// Blob asset storing the priority item entries (task index and priority value index per child). /// public struct PriorityItemsBlob { [Tooltip("The priority item entries.")] public BlobArray Items; } /// /// The DOTS data structure for the PrioritySelector class. /// public struct PrioritySelectorComponent : IBufferElementData { [Tooltip("The index of the node.")] public ushort Index; [Tooltip("The relative index of the child that is currently active.")] public ushort ActiveRelativeChildIndex; [Tooltip("The blob containing task-to-priority-value mapping per child.")] public BlobAssetReference PriorityItems; [Tooltip("Task indices in sorted order by priority (highest first).")] public UnsafeList SortedOrder; } /// /// DOTS structure that contains the most recently priority of the task. /// public struct PriorityValueComponent : IBufferElementData { [Tooltip("The index of the task.")] public ushort Index; [Tooltip("The current priority value. The higher the value the more likely it will be selected.")] public float Value; } /// /// A DOTS tag indicating when a PrioritySelector node is active. /// public struct PrioritySelectorFlag : IComponentData, IEnableableComponent { } /// /// Runs the PrioritySelector logic. /// [DisableAutoCreation] public partial struct PrioritySelectorTaskSystem : ISystem { private EntityQuery m_WithValuesQuery; private EntityQuery m_WithoutValuesQuery; /// /// Builds the queries. /// /// The current state of the system. private void OnCreate(ref SystemState state) { m_WithValuesQuery = SystemAPI.QueryBuilder() .WithAllRW() .WithAllRW() .WithAllRW() .WithAllRW() .WithAll() .Build(); // Special case where there is no PriorityValueComponent buffer. m_WithoutValuesQuery = SystemAPI.QueryBuilder() .WithAllRW() .WithAllRW() .WithAllRW() .WithAll() .WithNone() .Build(); } /// /// Creates the jobs. /// /// The current state of the system. [BurstCompile] private void OnUpdate(ref SystemState state) { state.Dependency = new PrioritySelectorWithValuesJob().ScheduleParallel(m_WithValuesQuery, state.Dependency); state.Dependency = new PrioritySelectorWithoutValuesJob().ScheduleParallel(m_WithoutValuesQuery, state.Dependency); } /// /// Job which executes the PrioritySelector logic when PriorityValueComponent exists. /// [BurstCompile] private partial struct PrioritySelectorWithValuesJob : IJobEntity { /// /// Executes the PrioritySelector logic. /// /// An array of BranchComponents. /// An array of TaskComponents. /// An array of PrioritySelectorComponents. /// An array of PriorityValueComponents. [BurstCompile] public void Execute(ref DynamicBuffer branchComponents, ref DynamicBuffer taskComponents, ref DynamicBuffer prioritySelectorComponents, ref DynamicBuffer priorityValueComponents) { for (int i = 0; i < prioritySelectorComponents.Length; ++i) { var prioritySelectorComponent = prioritySelectorComponents[i]; var taskComponent = taskComponents[prioritySelectorComponent.Index]; var taskStatus = taskComponent.Status; // Skip inactive tasks before 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) { taskComponent.Status = TaskStatus.Running; taskComponents[taskComponent.Index] = taskComponent; // Build the priority items blob when first run. if (!prioritySelectorComponent.PriorityItems.IsCreated) { var childCount = TraversalUtility.GetImmediateChildCount(ref taskComponent, ref taskComponents); var builder = new BlobBuilder(Allocator.Temp); ref var root = ref builder.ConstructRoot(); var itemsArray = builder.Allocate(ref root.Items, childCount); var childIndex = (ushort)(taskComponent.Index + 1); for (ushort j = 0; j < childCount; ++j) { itemsArray[j] = new PriorityItemBlobEntry() { TaskIndex = childIndex, PriorityValueIndex = ushort.MaxValue }; for (ushort k = 0; k < priorityValueComponents.Length; ++k) { if (priorityValueComponents[k].Index == childIndex) { itemsArray[j].PriorityValueIndex = k; break; } } childIndex = taskComponents[childIndex].SiblingIndex; } prioritySelectorComponent.PriorityItems = builder.CreateBlobAssetReference(Allocator.Persistent); builder.Dispose(); } // Build sorted order by current priority values (reuse storage). ref var priorityItems = ref prioritySelectorComponent.PriorityItems.Value.Items; var orderCount = priorityItems.Length; if (!prioritySelectorComponent.SortedOrder.IsCreated || prioritySelectorComponent.SortedOrder.Length != orderCount) { if (prioritySelectorComponent.SortedOrder.IsCreated) { prioritySelectorComponent.SortedOrder.Dispose(); } prioritySelectorComponent.SortedOrder = new UnsafeList(orderCount, Allocator.Persistent); prioritySelectorComponent.SortedOrder.Resize(orderCount); } for (ushort j = 0; j < orderCount; ++j) { prioritySelectorComponent.SortedOrder[j] = j; } for (int a = 0; a < orderCount; ++a) { for (int b = a + 1; b < orderCount; ++b) { ref var entryA = ref priorityItems[prioritySelectorComponent.SortedOrder[a]]; ref var entryB = ref priorityItems[prioritySelectorComponent.SortedOrder[b]]; var valueA = GetPriorityValue(ref entryA, ref priorityValueComponents); var valueB = GetPriorityValue(ref entryB, ref priorityValueComponents); if (valueB > valueA) { var t = prioritySelectorComponent.SortedOrder[a]; prioritySelectorComponent.SortedOrder[a] = prioritySelectorComponent.SortedOrder[b]; prioritySelectorComponent.SortedOrder[b] = t; } } } for (int j = 0; j < orderCount; ++j) { var relIdx = prioritySelectorComponent.SortedOrder[j]; prioritySelectorComponent.SortedOrder[j] = priorityItems[relIdx].TaskIndex; } prioritySelectorComponent.ActiveRelativeChildIndex = 0; prioritySelectorComponents[i] = prioritySelectorComponent; branchComponent.NextIndex = prioritySelectorComponent.SortedOrder[prioritySelectorComponent.ActiveRelativeChildIndex]; branchComponents[taskComponent.BranchIndex] = branchComponent; // Start the child. var nextChildTaskComponent = taskComponents[branchComponent.NextIndex]; nextChildTaskComponent.Status = TaskStatus.Queued; taskComponents[branchComponent.NextIndex] = nextChildTaskComponent; } // The prioritySelector task is currently active. Check the active child. var childTaskComponent = taskComponents[prioritySelectorComponent.SortedOrder[prioritySelectorComponent.ActiveRelativeChildIndex]]; if (childTaskComponent.Status == TaskStatus.Queued || childTaskComponent.Status == TaskStatus.Running) { // The child should keep running. continue; } // Switch to the next highest priority. If no more priority values exist the task should act as a normal selector. if (prioritySelectorComponent.ActiveRelativeChildIndex == prioritySelectorComponent.SortedOrder.Length - 1 || childTaskComponent.Status == TaskStatus.Success) { // There are no more children or the child succeeded. The selector task should end. taskComponent.Status = childTaskComponent.Status; prioritySelectorComponent.ActiveRelativeChildIndex = 0; taskComponents[prioritySelectorComponent.Index] = taskComponent; branchComponent.NextIndex = taskComponent.ParentIndex; branchComponents[taskComponent.BranchIndex] = branchComponent; } else { // The child task returned failure. Move onto the next task. prioritySelectorComponent.ActiveRelativeChildIndex++; var nextIndex = prioritySelectorComponent.SortedOrder[prioritySelectorComponent.ActiveRelativeChildIndex]; var nextTaskComponent = taskComponents[nextIndex]; nextTaskComponent.Status = TaskStatus.Queued; taskComponents[nextIndex] = nextTaskComponent; branchComponent.NextIndex = nextIndex; branchComponents[taskComponent.BranchIndex] = branchComponent; } prioritySelectorComponents[i] = prioritySelectorComponent; } } } /// /// Job which executes the special case where the PrioritySelector has no PriorityValueComponent buffer. /// [BurstCompile] private partial struct PrioritySelectorWithoutValuesJob : IJobEntity { /// /// Executes the no-priority-values fallback logic. /// /// An array of PrioritySelectorComponents. /// An array of TaskComponents. /// An array of BranchComponents. [BurstCompile] public void Execute(ref DynamicBuffer prioritySelectorComponents, ref DynamicBuffer taskComponents, ref DynamicBuffer branchComponents) { for (int i = 0; i < prioritySelectorComponents.Length; ++i) { var prioritySelectorComponent = prioritySelectorComponents[i]; var taskComponent = taskComponents[prioritySelectorComponent.Index]; // If there are no values then the selector should return failure. if (taskComponent.Status == TaskStatus.Queued && !prioritySelectorComponent.PriorityItems.IsCreated) { taskComponent.Status = TaskStatus.Failure; taskComponents[prioritySelectorComponent.Index] = taskComponent; var branchComponent = branchComponents[taskComponent.BranchIndex]; branchComponent.NextIndex = taskComponent.ParentIndex; branchComponents[taskComponent.BranchIndex] = branchComponent; } } } } /// /// Returns the priority value for a blob entry, or float.MinValue if no component. /// /// The priority item blob entry. /// The priority value components buffer. /// The priority value. [BurstCompile] private static float GetPriorityValue(ref PriorityItemBlobEntry entry, ref DynamicBuffer priorityValueComponents) { if (entry.PriorityValueIndex == ushort.MaxValue) { return float.MinValue; } return priorityValueComponents[entry.PriorityValueIndex].Value; } /// /// Disposes blob assets when the system is destroyed. /// /// The current state of the system. private void OnDestroy(ref SystemState state) { foreach (var prioritySelectorComponents in SystemAPI.Query>()) { for (int i = 0; i < prioritySelectorComponents.Length; ++i) { var prioritySelectorComponent = prioritySelectorComponents[i]; if (prioritySelectorComponent.PriorityItems.IsCreated) { prioritySelectorComponent.PriorityItems.Dispose(); } if (prioritySelectorComponent.SortedOrder.IsCreated) { prioritySelectorComponent.SortedOrder.Dispose(); } } } } } } #endif