#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