2025-11-25 08:19:33 -05:00
#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 Opsive.GraphDesigner.Runtime ;
2026-05-10 11:47:55 -04:00
using Unity.Collections ;
2025-11-25 08:19:33 -05:00
using Unity.Entities ;
using UnityEngine ;
/// <summary>
/// Specifies that the node is an object task which can specify the next child that should run.
/// </summary>
public interface ITaskObjectParentNode
{
/// <summary>
/// Returns the index of the next child that should run. Set to ushort.MaxValue to ignore.
/// </summary>
ushort NextChildIndex { get ; }
}
/// <summary>
/// The DOTS data structure for the TaskObject class.
/// </summary>
public struct TaskObjectComponent : IBufferElementData
{
[Tooltip("The index of the task.")]
public ushort Index ;
}
/// <summary>
/// A DOTS flag indicating when an TaskObject node is active.
/// </summary>
public struct TaskObjectFlag : IComponentData , IEnableableComponent { }
2026-05-10 11:47:55 -04:00
/// <summary>
/// Utility methods for synchronizing ECS-backed SharedVariables around managed task execution.
/// </summary>
internal static class TaskObjectSharedVariableSyncUtility
{
/// <summary>
/// Syncs ECS-backed SharedVariables to the managed variables once for the specified entity.
/// </summary>
/// <param name="world">The world containing the entity.</param>
/// <param name="entity">The entity being updated.</param>
/// <param name="behaviorTree">The behavior tree associated with the entity.</param>
/// <param name="syncedEntities">The entities that have already been synchronized this pass.</param>
/// <param name="touchedEntities">The entities that need their managed values flushed back to ECS.</param>
public static void SyncToManagedIfNeeded ( World world , Entity entity , BehaviorTree behaviorTree , NativeParallelHashSet < Entity > syncedEntities , NativeList < Entity > touchedEntities )
{
if ( behaviorTree = = null | | ! behaviorTree . HasECSVariableSync ( world , entity ) | | ! syncedEntities . Add ( entity ) ) {
return ;
}
behaviorTree . SyncECSVariablesToManaged ( world , entity ) ;
touchedEntities . Add ( entity ) ;
}
/// <summary>
/// Flushes the managed SharedVariable values for the touched entities back into ECS.
/// </summary>
/// <param name="world">The world containing the entities.</param>
/// <param name="touchedEntities">The entities whose managed SharedVariables should be synced back into ECS.</param>
public static void SyncTouchedEntitiesToECS ( World world , NativeList < Entity > touchedEntities )
{
for ( int i = 0 ; i < touchedEntities . Length ; + + i ) {
var entity = touchedEntities [ i ] ;
var behaviorTree = BehaviorTree . GetBehaviorTree ( entity ) ;
if ( behaviorTree = = null ) {
continue ;
}
behaviorTree . SyncManagedVariablesToECS ( world , entity ) ;
}
}
}
2025-11-25 08:19:33 -05:00
/// <summary>
/// Runs the TaskObject logic.
/// </summary>
[DisableAutoCreation]
[UpdateInGroup(typeof(TraversalTaskSystemGroup), OrderLast = true)]
public partial struct TaskObjectSystem : ISystem
{
2026-05-10 11:47:55 -04:00
private EntityQuery m_InterruptedTaskQuery ;
private EntityQuery m_TaskObjectQuery ;
/// <summary>
/// Creates the queries used by the system.
/// </summary>
/// <param name="state">The current system state.</param>
private void OnCreate ( ref SystemState state )
{
m_InterruptedTaskQuery = SystemAPI . QueryBuilder ( ) . WithAll < InterruptedFlag , TaskObjectComponent , TaskComponent > ( ) . Build ( ) ;
m_TaskObjectQuery = SystemAPI . QueryBuilder ( ) . WithAll < TaskObjectFlag , EvaluateFlag , TaskObjectComponent , TaskComponent , BranchComponent > ( ) . Build ( ) ;
}
2025-11-25 08:19:33 -05:00
/// <summary>
/// Updates the logic.
/// </summary>
/// <param name="state">The current state of the system.</param>
private void OnUpdate ( ref SystemState state )
{
2026-05-10 11:47:55 -04:00
var entityCapacity = Mathf . Max ( 1 , m_InterruptedTaskQuery . CalculateEntityCount ( ) + m_TaskObjectQuery . CalculateEntityCount ( ) ) ;
using var syncedEntities = new NativeParallelHashSet < Entity > ( entityCapacity , Allocator . Temp ) ;
using var touchedEntities = new NativeList < Entity > ( entityCapacity , Allocator . Temp ) ;
2025-11-25 08:19:33 -05:00
// When the task is interrupted there is no callback which prevents Task.OnEnd from being called. Track the status within the referenced task object and if the status is different then
// the task was aborted and OnEnd needs to be called.
foreach ( var ( taskObjectComponents , taskComponents , entity ) in
SystemAPI . Query < DynamicBuffer < TaskObjectComponent > , DynamicBuffer < TaskComponent > > ( ) . WithAll < InterruptedFlag > ( ) . WithEntityAccess ( ) ) {
var behaviorTree = BehaviorTree . GetBehaviorTree ( entity ) ;
if ( behaviorTree = = null ) {
continue ;
}
2026-05-10 11:47:55 -04:00
TaskObjectSharedVariableSyncUtility . SyncToManagedIfNeeded ( state . World , entity , behaviorTree , syncedEntities , touchedEntities ) ;
2025-11-25 08:19:33 -05:00
for ( int i = 0 ; i < taskObjectComponents . Length ; + + i ) {
2026-02-13 09:22:11 -05:00
var taskObjectComponent = taskObjectComponents [ i ] ;
var taskComponent = taskComponents [ taskObjectComponent . Index ] ;
2026-05-10 11:47:55 -04:00
if ( taskComponent . Status ! = TaskStatus . Queued & & taskComponent . Status ! = TaskStatus . Running ) {
var task = behaviorTree . GetTaskObject ( taskObjectComponent . Index ) ;
if ( task = = null ) {
continue ;
}
2025-11-25 08:19:33 -05:00
if ( task . Status ! = taskComponent . Status ) {
task . OnEnd ( ) ;
task . Status = taskComponent . Status ;
}
}
}
}
// Update the task objects.
foreach ( var ( taskObjectComponents , taskComponents , branchComponents , entity ) in
SystemAPI . Query < DynamicBuffer < TaskObjectComponent > , DynamicBuffer < TaskComponent > , DynamicBuffer < BranchComponent > > ( ) . WithAll < TaskObjectFlag , EvaluateFlag > ( ) . WithEntityAccess ( ) ) {
var behaviorTree = BehaviorTree . GetBehaviorTree ( entity ) ;
if ( behaviorTree = = null ) {
continue ;
}
2026-05-10 11:47:55 -04:00
TaskObjectSharedVariableSyncUtility . SyncToManagedIfNeeded ( state . World , entity , behaviorTree , syncedEntities , touchedEntities ) ;
var hasInterruptComponents = SystemAPI . HasComponent < InterruptFlag > ( entity ) ;
var interruptedFlagEnabled = hasInterruptComponents & & SystemAPI . IsComponentEnabled < InterruptedFlag > ( entity ) ;
var taskComponentBuffer = taskComponents ;
var branchComponentBuffer = branchComponents ;
2025-11-25 08:19:33 -05:00
for ( int i = 0 ; i < taskObjectComponents . Length ; + + i ) {
2026-02-13 09:22:11 -05:00
var taskObjectComponent = taskObjectComponents [ i ] ;
var taskComponent = taskComponents [ taskObjectComponent . Index ] ;
var branchComponent = branchComponents [ taskComponent . BranchIndex ] ;
if ( ! branchComponent . CanExecute | | branchComponent . ActiveIndex ! = taskComponent . Index ) {
2026-01-03 18:19:39 -05:00
continue ;
}
2026-05-10 11:47:55 -04:00
var task = behaviorTree . GetTaskObject ( taskObjectComponent . Index ) ;
if ( task = = null ) {
continue ;
}
2025-11-25 08:19:33 -05:00
if ( taskComponent . Status = = TaskStatus . Queued ) {
task . Status = taskComponent . Status = TaskStatus . Running ;
2026-02-13 09:22:11 -05:00
taskComponentBuffer [ taskComponent . Index ] = taskComponent ;
2025-11-25 08:19:33 -05:00
task . OnStart ( ) ;
}
if ( taskComponent . Status ! = TaskStatus . Running ) {
continue ;
}
2026-05-10 11:47:55 -04:00
2025-11-25 08:19:33 -05:00
var status = task . OnUpdate ( ) ;
// Update the status if has changed.
if ( status ! = taskComponent . Status ) {
task . Status = taskComponent . Status = status ;
2026-02-13 09:22:11 -05:00
taskComponentBuffer [ taskComponent . Index ] = taskComponent ;
2025-11-25 08:19:33 -05:00
// End the task if it is done running.
if ( status ! = TaskStatus . Running ) {
task . OnEnd ( ) ;
2026-05-10 11:47:55 -04:00
branchComponent = branchComponentBuffer [ taskComponent . BranchIndex ] ;
2025-11-25 08:19:33 -05:00
branchComponent . NextIndex = taskComponent . ParentIndex ;
branchComponentBuffer [ taskComponent . BranchIndex ] = branchComponent ;
}
}
2026-05-10 11:47:55 -04:00
var taskObjectParentNode = behaviorTree . GetTaskObjectParent ( taskObjectComponent . Index ) ;
if ( taskObjectParentNode ! = null ) {
2025-11-25 08:19:33 -05:00
if ( status = = TaskStatus . Running ) {
// Parent object tasks do not have a direct way to set the next child. Use the ITaskObjectParentNode to switch the child task.
2026-05-10 11:47:55 -04:00
var nextChildIndex = taskObjectParentNode . NextChildIndex ;
if ( nextChildIndex ! = ushort . MaxValue & & nextChildIndex < taskComponents . Length ) {
var nextTaskComponent = taskComponents [ nextChildIndex ] ;
if ( nextTaskComponent . Status ! = TaskStatus . Running ) {
branchComponent = branchComponentBuffer [ nextTaskComponent . BranchIndex ] ;
if ( branchComponent . NextIndex ! = nextChildIndex ) {
branchComponent . NextIndex = nextChildIndex ;
branchComponentBuffer [ nextTaskComponent . BranchIndex ] = branchComponent ;
}
if ( nextTaskComponent . Status ! = TaskStatus . Queued ) {
nextTaskComponent . Status = TaskStatus . Queued ;
taskComponentBuffer [ nextChildIndex ] = nextTaskComponent ;
}
}
2025-11-25 08:19:33 -05:00
}
} else if ( status = = TaskStatus . Success | | status = = TaskStatus . Failure ) {
// An interrupt should occur if the parent returns a success or failure status before the children.
var childCount = TraversalUtility . GetChildCount ( taskComponent . Index , ref taskComponentBuffer ) ;
2026-05-10 11:47:55 -04:00
var startIndex = taskComponent . Index + 1 ;
var endIndex = Mathf . Min ( startIndex + childCount , taskComponentBuffer . Length ) ;
for ( int j = startIndex ; j < endIndex ; + + j ) {
2026-02-13 09:22:11 -05:00
var childTaskComponent = taskComponentBuffer [ j ] ;
2025-11-25 08:19:33 -05:00
if ( childTaskComponent . Status = = TaskStatus . Running | | childTaskComponent . Status = = TaskStatus . Queued ) {
childTaskComponent . Status = status ;
taskComponentBuffer [ j ] = childTaskComponent ;
2026-02-13 09:22:11 -05:00
branchComponent = branchComponentBuffer [ childTaskComponent . BranchIndex ] ;
if ( ! hasInterruptComponents ) {
2025-11-25 08:19:33 -05:00
ComponentUtility . AddInterruptComponents ( behaviorTree . World . EntityManager , entity ) ;
2026-02-13 09:22:11 -05:00
hasInterruptComponents = true ;
}
if ( ! interruptedFlagEnabled ) {
SystemAPI . SetComponentEnabled < InterruptedFlag > ( entity , true ) ;
interruptedFlagEnabled = true ;
2025-11-25 08:19:33 -05:00
}
if ( branchComponent . ActiveIndex = = childTaskComponent . Index ) {
branchComponent . NextIndex = ushort . MaxValue ;
branchComponentBuffer [ childTaskComponent . BranchIndex ] = branchComponent ;
}
}
}
2026-05-10 11:47:55 -04:00
}
2025-11-25 08:19:33 -05:00
}
}
}
2026-05-10 11:47:55 -04:00
TaskObjectSharedVariableSyncUtility . SyncTouchedEntitiesToECS ( state . World , touchedEntities ) ;
2025-11-25 08:19:33 -05:00
}
}
/// <summary>
/// A DOTS tag indicating when an TaskObject node needs to be reevaluated.
/// </summary>
public struct TaskObjectReevaluateFlag : IComponentData , IEnableableComponent
{
}
/// <summary>
/// Runs the TaskObject reevaluation logic.
/// </summary>
[DisableAutoCreation]
public partial struct TaskObjectReevaluateSystem : ISystem
{
2026-05-10 11:47:55 -04:00
private EntityQuery m_ReevaluateTaskQuery ;
/// <summary>
/// Creates the queries used by the system.
/// </summary>
/// <param name="state">The current system state.</param>
private void OnCreate ( ref SystemState state )
{
m_ReevaluateTaskQuery = SystemAPI . QueryBuilder ( ) . WithAll < TaskObjectReevaluateFlag , EvaluateFlag , TaskObjectComponent , TaskComponent > ( ) . Build ( ) ;
}
2025-11-25 08:19:33 -05:00
/// <summary>
/// Updates the reevaluation logic.
/// </summary>
/// <param name="state">The current state of the system.</param>
private void OnUpdate ( ref SystemState state )
{
2026-05-10 11:47:55 -04:00
var entityCapacity = Mathf . Max ( 1 , m_ReevaluateTaskQuery . CalculateEntityCount ( ) ) ;
using var syncedEntities = new NativeParallelHashSet < Entity > ( entityCapacity , Allocator . Temp ) ;
using var touchedEntities = new NativeList < Entity > ( entityCapacity , Allocator . Temp ) ;
2025-11-25 08:19:33 -05:00
foreach ( var ( taskComponents , taskObjectComponents , entity ) in
SystemAPI . Query < DynamicBuffer < TaskComponent > , DynamicBuffer < TaskObjectComponent > > ( ) . WithAll < TaskObjectReevaluateFlag , EvaluateFlag > ( ) . WithEntityAccess ( ) ) {
2026-05-10 11:47:55 -04:00
var behaviorTree = BehaviorTree . GetBehaviorTree ( entity ) ;
if ( behaviorTree = = null ) {
continue ;
}
TaskObjectSharedVariableSyncUtility . SyncToManagedIfNeeded ( state . World , entity , behaviorTree , syncedEntities , touchedEntities ) ;
2025-11-25 08:19:33 -05:00
for ( int i = 0 ; i < taskObjectComponents . Length ; + + i ) {
var taskObjectComponent = taskObjectComponents [ i ] ;
var taskComponent = taskComponents [ taskObjectComponent . Index ] ;
if ( ! taskComponent . Reevaluate ) {
continue ;
}
2026-05-10 11:47:55 -04:00
var task = behaviorTree . GetConditionalReevaluationTaskObject ( taskObjectComponent . Index ) ;
if ( task = = null ) {
2025-11-25 08:19:33 -05:00
continue ;
}
var status = task . OnReevaluateUpdate ( ) ;
if ( status ! = taskComponent . Status ) {
taskComponent . Status = status ;
var buffer = taskComponents ;
buffer [ taskComponent . Index ] = taskComponent ;
}
}
}
2026-05-10 11:47:55 -04:00
TaskObjectSharedVariableSyncUtility . SyncTouchedEntitiesToECS ( state . World , touchedEntities ) ;
2025-11-25 08:19:33 -05:00
}
}
}
#endif