#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 UnityEngine;
///
/// System which checks for any tasks that should be reevaluated with conditional aborts. This system only marks the tasks, it does not do
/// the actual reevaluation or interruption.
///
[UpdateInGroup(typeof(BeforeTraversalSystemGroup), OrderFirst = true)]
public partial struct ReevaluateSystem : ISystem
{
private EntityQuery m_Query;
///
/// Builds the query.
///
/// THe current SystemState.
private void OnCreate(ref SystemState state)
{
m_Query = SystemAPI.QueryBuilder().WithAllRW().WithAllRW().WithAll().WithAbsent().Build();
}
///
/// Creates the ReevaluateJob.
///
/// The current SystemState.
private void OnUpdate(ref SystemState state)
{
var ecb = new EntityCommandBuffer(Allocator.TempJob);
state.Dependency = new ReevaluateJob()
{
EntityCommandBuffer = ecb.AsParallelWriter(),
}.ScheduleParallel(m_Query, state.Dependency);
// The job must run immediately for the next systems.
state.Dependency.Complete();
ecb.Playback(state.EntityManager);
ecb.Dispose();
}
///
/// Job which checks for any tasks that should be reevaluated with conditional aborts. This job only flags the tasks, it does not do
/// the actual reevaluation or interruption.
///
[BurstCompile]
private partial struct ReevaluateJob : 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.
/// An array of reevaluate task components.
[BurstCompile]
public void Execute(Entity entity, [EntityIndexInQuery] int entityIndex, in DynamicBuffer branchComponents, ref DynamicBuffer taskComponents, ref DynamicBuffer reevaluateTaskComponents)
{
for (int i = 0; i < reevaluateTaskComponents.Length; ++i) {
var reevaluateTaskComponent = reevaluateTaskComponents[i];
// The task may not be able to reevaluate.
var taskComponent = taskComponents[reevaluateTaskComponent.Index];
if (!taskComponent.CanReevaluate || taskComponent.Disabled) {
continue;
}
// The branch may not be active.
var branchComponent = branchComponents[taskComponent.BranchIndex];
if (branchComponent.ActiveIndex == ushort.MaxValue) {
if (taskComponent.Reevaluate) {
taskComponent.Reevaluate = false;
taskComponents[reevaluateTaskComponent.Index] = taskComponent;
reevaluateTaskComponent.ReevaluateStatus = ReevaluateStatus.Inactive;
reevaluateTaskComponents[i] = reevaluateTaskComponent;
}
continue;
}
var reevaluate = false;
if (reevaluateTaskComponent.AbortType == ConditionalAbortType.Self || reevaluateTaskComponent.AbortType == ConditionalAbortType.Both) {
if (branchComponent.ActiveIndex > taskComponent.Index && branchComponent.ActiveIndex <= reevaluateTaskComponent.SelfPriorityUpperIndex) {
// Reevaluate.
reevaluate = true;
if (reevaluateTaskComponent.ReevaluateStatus == ReevaluateStatus.Inactive) {
reevaluateTaskComponent.ReevaluateStatus = ReevaluateStatus.Active;
EntityCommandBuffer.SetComponentEnabled(entityIndex, entity, reevaluateTaskComponent.ReevaluateTagComponentType, true);
}
}
}
if (!reevaluate && (reevaluateTaskComponent.AbortType == ConditionalAbortType.LowerPriority || reevaluateTaskComponent.AbortType == ConditionalAbortType.Both)) {
if (branchComponent.ActiveIndex > reevaluateTaskComponent.LowerPriorityLowerIndex && branchComponent.ActiveIndex <= reevaluateTaskComponent.LowerPriorityUpperIndex) {
// Reevaluate.
reevaluate = true;
if (reevaluateTaskComponent.ReevaluateStatus == ReevaluateStatus.Inactive) {
reevaluateTaskComponent.ReevaluateStatus = ReevaluateStatus.Active;
EntityCommandBuffer.SetComponentEnabled(entityIndex, entity, reevaluateTaskComponent.ReevaluateTagComponentType, true);
}
}
}
// The task should no longer reevaluate.
if (!reevaluate && (taskComponent.Reevaluate || reevaluateTaskComponent.ReevaluateStatus == ReevaluateStatus.Dirty)) {
// The system needs to be kept active if there are other tasks with the same reevaluate tag.
var keepSystemActive = false;
for (int j = 0; j < reevaluateTaskComponents.Length; ++j) {
if (i == j) {
continue;
}
if (reevaluateTaskComponents[j].ReevaluateStatus == ReevaluateStatus.Active &&
reevaluateTaskComponent.ReevaluateTagComponentType == reevaluateTaskComponents[j].ReevaluateTagComponentType) {
keepSystemActive = true;
break;
}
}
if (!keepSystemActive) {
EntityCommandBuffer.SetComponentEnabled(entityIndex, entity, reevaluateTaskComponent.ReevaluateTagComponentType, false);
}
// The task should always disable itself.
taskComponent.Reevaluate = false;
reevaluateTaskComponent.ReevaluateStatus = ReevaluateStatus.Inactive;
} else {
// Store the current status of the task. This status will be compared after the task is reevaluated within DetermineInterruptSystem.
reevaluateTaskComponent.OriginalStatus = taskComponent.Status;
}
reevaluateTaskComponents[i] = reevaluateTaskComponent;
taskComponent.Reevaluate = reevaluate;
taskComponents[reevaluateTaskComponent.Index] = taskComponent;
}
}
}
}
///
/// The tasks have been reevaluated. Compare the status to determine if an interrupt should occur.
///
[UpdateInGroup(typeof(InterruptSystemGroup))]
[UpdateBefore(typeof(InterruptSystem))]
public partial struct ConditionalAbortsInvokerSystem : ISystem
{
private EntityQuery m_Query;
///
/// Builds the query.
///
/// THe current SystemState.
private void OnCreate(ref SystemState state)
{
m_Query = SystemAPI.QueryBuilder().WithAllRW().WithAllRW().WithAllRW().WithAbsent().Build();
}
///
/// Creates the jobs necessary for conditional aborts.
///
/// The current SystemState.
[BurstCompile]
private void OnUpdate(ref SystemState state)
{
var ecb = new EntityCommandBuffer(Allocator.TempJob);
state.Dependency = new ConditionalAbortsJob()
{
EntityCommandBuffer = ecb.AsParallelWriter()
}.ScheduleParallel(m_Query, state.Dependency);
// The jobs must be run immediately for the next systems.
state.Dependency.Complete();
ecb.Playback(state.EntityManager);
ecb.Dispose();
}
///
/// Job which checks for any tasks that should be reevaluated with conditional aborts. This job only flags the tasks, it does not do
/// the actual reevaluation or interruption.
///
[BurstCompile]
private partial struct ConditionalAbortsJob : 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.
/// An array of reevaluate task components.
[BurstCompile]
public void Execute(Entity entity, [EntityIndexInQuery] int entityIndex, ref DynamicBuffer branchComponents, ref DynamicBuffer taskComponents, ref DynamicBuffer reevaluateTaskComponents)
{
for (int i = 0; i < reevaluateTaskComponents.Length; ++i) {
var reevaluateTaskComponent = reevaluateTaskComponents[i];
var taskComponent = taskComponents[reevaluateTaskComponent.Index];
if (taskComponent.Reevaluate) {
if (reevaluateTaskComponent.OriginalStatus != taskComponent.Status) {
// The status is different. This will cause an interrupt.
var branchComponent = branchComponents[taskComponent.BranchIndex];
// The task with the highest priority should cause the abort.
if (branchComponent.InterruptType == InterruptType.None || taskComponent.Index < branchComponent.InterruptIndex) {
branchComponent.InterruptIndex = taskComponent.Index;
branchComponent.InterruptType = InterruptType.Branch;
branchComponents[taskComponent.BranchIndex] = branchComponent;
} else {
taskComponent.Status = TaskStatus.Inactive;
}
taskComponent.Reevaluate = false;
taskComponents[reevaluateTaskComponent.Index] = taskComponent;
EntityCommandBuffer.SetComponentEnabled(entityIndex, entity, true);
reevaluateTaskComponent.ReevaluateStatus = ReevaluateStatus.Dirty;
var reevaluateTaskComponentsBuffer = reevaluateTaskComponents;
reevaluateTaskComponentsBuffer[i] = reevaluateTaskComponent;
}
}
}
}
}
}
///
/// Processes any interrupts.
///
[UpdateInGroup(typeof(InterruptSystemGroup))]
[UpdateAfter(typeof(ConditionalAbortsInvokerSystem))]
public partial struct InterruptSystem : ISystem
{
private EntityQuery m_Query;
///
/// Builds the query.
///
/// THe current SystemState.
private void OnCreate(ref SystemState state)
{
m_Query = SystemAPI.QueryBuilder().WithAllRW().WithAllRW().WithAll().Build();
}
///
/// Creates the InterruptJob.
///
/// The current SystemState.
[BurstCompile]
private void OnUpdate(ref SystemState state)
{
var ecb = new EntityCommandBuffer(Allocator.TempJob);
state.Dependency = new InterruptJob()
{
EntityCommandBuffer = ecb.AsParallelWriter(),
}.ScheduleParallel(m_Query, state.Dependency);
// The job must run immediately for the next systems.
state.Dependency.Complete();
ecb.Playback(state.EntityManager);
ecb.Dispose();
}
///
/// Triggers the interrupts.
///
[BurstCompile]
private partial struct InterruptJob : 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, DynamicBuffer branchComponents, DynamicBuffer taskComponents)
{
for (ushort i = 0; i < branchComponents.Length; ++i) {
var branchComponent = branchComponents[i];
if (branchComponent.InterruptType != InterruptType.None) {
var targetTaskComponent = taskComponents[branchComponent.InterruptIndex];
var parentIndex = targetTaskComponent.ParentIndex;
TaskStatus prevActiveNewStatus;
if (branchComponent.InterruptType == InterruptType.Branch) {
branchComponent.NextIndex = branchComponent.InterruptIndex;
branchComponents[i] = branchComponent;
// Set the target branch tasks to running.
targetTaskComponent.Status = TaskStatus.Running;
while (parentIndex != ushort.MaxValue && taskComponents[parentIndex].Status != TaskStatus.Running) {
var parentTaskComponent = taskComponents[parentIndex];
parentTaskComponent.Status = TaskStatus.Running;
taskComponents[parentIndex] = parentTaskComponent;
parentIndex = parentTaskComponent.ParentIndex;
}
prevActiveNewStatus = TaskStatus.Failure;
} else { // InterruptType.ImmediateSuccess/Failure.
targetTaskComponent.Status = branchComponent.InterruptType == InterruptType.ImmediateSuccess ? TaskStatus.Success : TaskStatus.Failure;
var targetBranchComponent = branchComponents[targetTaskComponent.BranchIndex];
targetBranchComponent.NextIndex = targetTaskComponent.ParentIndex;
branchComponents[targetTaskComponent.BranchIndex] = targetBranchComponent;
prevActiveNewStatus = targetTaskComponent.Status;
}
// Determine if any other branches need to be interrupted.
for (ushort j = i; j < branchComponents.Length; ++j) {
if (i == j || TraversalUtility.IsParent((ushort)branchComponents[j].ActiveIndex, parentIndex, ref taskComponents)) {
AbortChildren((ushort)branchComponents[j].ActiveIndex, parentIndex, ref taskComponents, prevActiveNewStatus);
// Reset any queued children.
var taskComponentBuffer = taskComponents;
var childCount = TraversalUtility.GetChildCount(branchComponent.ActiveIndex, ref taskComponentBuffer);
for (int k = 0; k < childCount; ++k) {
var childTaskComponent = taskComponents[branchComponent.ActiveIndex + k + 1];
if (childTaskComponent.Status == TaskStatus.Queued) {
childTaskComponent.Status = TaskStatus.Inactive;
taskComponentBuffer[branchComponent.ActiveIndex + k + 1] = childTaskComponent;
}
}
// If the branch is a parallel branch then reset the NextIndex. The current branch (i) will be interrupted normally above.
var localBranchComponent = branchComponents[j];
if (localBranchComponent.InterruptType == InterruptType.None) {
localBranchComponent.NextIndex = ushort.MaxValue;
branchComponents[j] = localBranchComponent;
}
EntityCommandBuffer.SetComponentEnabled(entityIndex, entity, true);
} else {
break;
}
}
taskComponents[targetTaskComponent.Index] = targetTaskComponent;
}
}
}
///
/// Aborts all of the children within the specified branch.
///
/// The index of the active task within the branch.
/// Aborts the tasks up to the specified parent index.
/// All of the tasks.
/// The abort status.
[BurstCompile]
private void AbortChildren(ushort activeIndex, ushort parentIndex, ref DynamicBuffer taskComponents, TaskStatus status)
{
while (activeIndex != ushort.MaxValue && activeIndex != parentIndex) {
var activeTask = taskComponents[activeIndex];
activeTask.Status = status;
taskComponents[activeIndex] = activeTask;
activeIndex = activeTask.ParentIndex;
}
}
}
}
///
/// Cleanup the interrupts after they have run.
///
[UpdateInGroup(typeof(InterruptSystemGroup), OrderLast = true)]
public partial struct InterruptCleanupSystem : ISystem
{
///
/// Executes the system.
///
/// The current SystemState.
[BurstCompile]
private void OnUpdate(ref SystemState state)
{
foreach (var (branchComponents, entity) in
SystemAPI.Query>().WithAll().WithEntityAccess()) {
for (int i = 0; i < branchComponents.Length; ++i) {
var branchComponent = branchComponents[i];
if (branchComponent.InterruptType != InterruptType.None) {
// Reset the interruption.
branchComponent.InterruptType = InterruptType.None;
branchComponent.InterruptIndex = 0;
var branchComponentBuffer = branchComponents;
branchComponentBuffer[i] = branchComponent;
state.EntityManager.SetComponentEnabled(entity, false);
}
}
}
}
}
}
#endif