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.Components
{
using Opsive.BehaviorDesigner.Runtime.Groups ;
2026-01-03 18:19:39 -05:00
using Opsive.BehaviorDesigner.Runtime.Systems ;
2026-05-10 11:47:55 -04:00
using Opsive.BehaviorDesigner.Runtime.Utility ;
2025-11-25 08:19:33 -05:00
using Unity.Entities ;
using UnityEngine ;
/// <summary>
/// Indicates that the behavior tree was baked.
/// </summary>
public class BakedBehaviorTree : IComponentData
{
[Tooltip("The index of the connected start task.")]
public int StartEventConnectedIndex ;
2026-05-10 11:47:55 -04:00
[Tooltip("Should the behavior tree be started after it has been initialized?")]
public bool StartWhenEnabled ;
2025-11-25 08:19:33 -05:00
[Tooltip("Should the behavior tree be started after it has been baked?")]
public bool StartEvaluation ;
[Tooltip("The indicies of the reevaluate task systems.")]
public string [ ] ReevaluateTaskSystems ;
[Tooltip("The indicies of the interrupt task systems.")]
public string [ ] InterruptTaskSystems ;
[Tooltip("The indicies of the traversal task systems.")]
public string [ ] TraversalTaskSystems ;
[Tooltip("The hashes that correspond to the TaskComponent's ComponentType.")]
public ulong [ ] TagStableTypeHashes ;
[Tooltip("The hashes that correspond to the ReevaluateTaskComponent's ComponentType.")]
public ulong [ ] ReevaluateFlagStableTypeHashes ;
}
2026-05-10 11:47:55 -04:00
/// <summary>
/// Baked editor-only metadata used to link a baked entity back to its authoring BehaviorTree for runtime debugging.
/// Populated by BehaviorTreeBaker only in editor builds and stripped from entity scenes before they are packaged into a player build by StripEditorBehaviorTreeReferenceSystem.
/// </summary>
public class BakedEditorReference : IComponentData
{
[Tooltip("The GlobalObjectId string for the authoring BehaviorTree component.")]
public string AuthoringBehaviorTreeGlobalObjectId ;
[Tooltip("The design-time graph unique ID.")]
public int DesignGraphUniqueID ;
[Tooltip("Maps design-time logic node index to runtime task index.")]
public ushort [ ] LogicNodeRuntimeIndices ;
}
/// <summary>
/// Stores the start data for a baked behavior tree that should be started manually.
/// </summary>
public struct DeferredBakedBehaviorTreeStart : IComponentData
{
[Tooltip("The index of the connected start task.")]
public ushort StartEventConnectedIndex ;
[Tooltip("Should the behavior tree start evaluation after the branch has started?")]
public bool StartEvaluation ;
}
2025-11-25 08:19:33 -05:00
/// <summary>
/// The behavior tree has been baked. Start the tree using the baked data.
/// </summary>
public partial struct StartBakedBehaviorTreeSystem : ISystem
{
/// <summary>
/// Restricts when the system should run.
/// </summary>
/// <param name="state">The current SystemState.</param>
private void OnCreate ( ref SystemState state )
{
state . RequireForUpdate < BakedBehaviorTree > ( ) ;
}
/// <summary>
/// Starts the baked behavior tree.
/// </summary>
/// <param name="state">The current SystemState.</param>
private void OnUpdate ( ref SystemState state )
{
// The components are baked, but systems are not baked. Create the required systems within the current world.
var reevaluateTaskSystemGroup = state . World . GetOrCreateSystemManaged < ReevaluateTaskSystemGroup > ( ) ;
var interruptTaskSystemGroup = state . World . GetOrCreateSystemManaged < InterruptTaskSystemGroup > ( ) ;
var traversalTaskSystemGroup = state . World . GetOrCreateSystemManaged < TraversalTaskSystemGroup > ( ) ;
2026-01-03 18:19:39 -05:00
// Add the necessary cleanup systems.
var behaviorTreeSystemGroup = state . World . GetOrCreateSystemManaged < BehaviorTreeSystemGroup > ( ) ;
behaviorTreeSystemGroup . AddSystemToUpdateList ( state . World . GetOrCreateSystem < EvaluationCleanupSystem > ( ) ) ;
behaviorTreeSystemGroup . AddSystemToUpdateList ( state . World . GetOrCreateSystem < InterruptedCleanupSystem > ( ) ) ;
2026-02-13 09:22:11 -05:00
var canReevaluate = false ;
2025-11-25 08:19:33 -05:00
var ecb = new EntityCommandBuffer ( state . WorldUpdateAllocator ) ;
2026-05-10 11:47:55 -04:00
#if UNITY_EDITOR
// Collected during the foreach and applied after ecb.Playback() because ECB.AddComponentObject only supports EntityQuery, not individual Entity.
var editorGraphReferences = new System . Collections . Generic . List < ( Entity entity , EditorBehaviorTreeGraphReference reference ) > ( ) ;
#endif
2025-11-25 08:19:33 -05:00
foreach ( var ( bakedBehaviorTree , entity ) in SystemAPI . Query < BakedBehaviorTree > ( ) . WithEntityAccess ( ) ) {
AddSystems ( state . World , reevaluateTaskSystemGroup , bakedBehaviorTree . ReevaluateTaskSystems ) ;
AddSystems ( state . World , interruptTaskSystemGroup , bakedBehaviorTree . InterruptTaskSystems ) ;
AddSystems ( state . World , traversalTaskSystemGroup , bakedBehaviorTree . TraversalTaskSystems ) ;
// ComponentTypes cannot be serialized. Convert the StableTypeHash to a ComponentType.
var taskComponents = state . World . EntityManager . GetBuffer < TaskComponent > ( entity ) ;
for ( int i = 0 ; i < taskComponents . Length ; + + i ) {
var taskComponent = taskComponents [ i ] ;
taskComponent . FlagComponentType = ComponentType . FromTypeIndex ( TypeManager . GetTypeIndexFromStableTypeHash ( bakedBehaviorTree . TagStableTypeHashes [ i ] ) ) ;
taskComponents [ i ] = taskComponent ;
}
2026-05-10 11:47:55 -04:00
TraversalUtility . PopulateChildUpperIndices ( ref taskComponents ) ;
2025-11-25 08:19:33 -05:00
if ( state . World . EntityManager . HasBuffer < ReevaluateTaskComponent > ( entity ) ) {
var reevaluateComponents = state . World . EntityManager . GetBuffer < ReevaluateTaskComponent > ( entity ) ;
2026-02-13 09:22:11 -05:00
canReevaluate = true ;
2025-11-25 08:19:33 -05:00
for ( int i = 0 ; i < reevaluateComponents . Length ; + + i ) {
var reevaluateComponent = reevaluateComponents [ i ] ;
reevaluateComponent . ReevaluateFlagComponentType = ComponentType . FromTypeIndex ( TypeManager . GetTypeIndexFromStableTypeHash ( bakedBehaviorTree . ReevaluateFlagStableTypeHashes [ i ] ) ) ;
reevaluateComponents [ i ] = reevaluateComponent ;
}
}
2026-05-10 11:47:55 -04:00
// All of the systems have been added. Start the behavior tree or defer the start.
if ( bakedBehaviorTree . StartWhenEnabled ) {
BehaviorTree . StartBranch ( state . World , entity , ( ushort ) bakedBehaviorTree . StartEventConnectedIndex , bakedBehaviorTree . StartEvaluation ) ;
} else {
ecb . AddComponent ( entity , new DeferredBakedBehaviorTreeStart
{
StartEventConnectedIndex = ( ushort ) bakedBehaviorTree . StartEventConnectedIndex ,
StartEvaluation = bakedBehaviorTree . StartEvaluation
} ) ;
}
#if UNITY_EDITOR
// BakedEditorReference is stripped from player build entity scenes by StripEditorBehaviorTreeReferenceSystem before serialization, so it only ever reaches
// this system during editor play mode.
if ( state . EntityManager . HasComponent < BakedEditorReference > ( entity ) ) {
var editorRef = state . EntityManager . GetComponentObject < BakedEditorReference > ( entity ) ;
if ( ! string . IsNullOrEmpty ( editorRef . AuthoringBehaviorTreeGlobalObjectId ) ) {
editorGraphReferences . Add ( ( entity , new EditorBehaviorTreeGraphReference
{
AuthoringBehaviorTreeGlobalObjectId = editorRef . AuthoringBehaviorTreeGlobalObjectId ,
DesignGraphUniqueID = editorRef . DesignGraphUniqueID ,
LogicNodeRuntimeIndices = editorRef . LogicNodeRuntimeIndices ,
} ) ) ;
}
ecb . RemoveComponent < BakedEditorReference > ( entity ) ;
}
#endif
2025-11-25 08:19:33 -05:00
ecb . RemoveComponent < BakedBehaviorTree > ( entity ) ;
}
2026-02-13 09:22:11 -05:00
if ( canReevaluate ) {
2026-05-10 11:47:55 -04:00
state . World . GetOrCreateSystemManaged < BeforeTraversalSystemGroup > ( ) . AddSystemToUpdateList ( state . World . GetOrCreateSystem ( typeof ( ReevaluateSystem ) ) ) ;
2026-02-13 09:22:11 -05:00
}
2026-05-10 11:47:55 -04:00
reevaluateTaskSystemGroup . SortSystems ( ) ;
interruptTaskSystemGroup . SortSystems ( ) ;
traversalTaskSystemGroup . SortSystems ( ) ;
2025-11-25 08:19:33 -05:00
ecb . Playback ( state . EntityManager ) ;
ecb . Dispose ( ) ;
2026-05-10 11:47:55 -04:00
#if UNITY_EDITOR
foreach ( var ( entity , reference ) in editorGraphReferences ) {
state . EntityManager . AddComponentObject ( entity , reference ) ;
}
#endif
2025-11-25 08:19:33 -05:00
}
/// <summary>
/// Adds the systems indicated by the SystemTypeIndex to the specified group.
/// </summary>
/// <param name="world">The current World.</param>
/// <param name="group">The group that the systems should be added to.</param>
/// <param name="systemTypes">The types of systems that should be added.</param>
private void AddSystems ( World world , ComponentSystemGroup group , string [ ] systemTypes )
{
if ( systemTypes = = null ) { return ; }
for ( int i = 0 ; i < systemTypes . Length ; + + i ) {
group . AddSystemToUpdateList ( world . GetOrCreateSystem ( Shared . Utility . TypeUtility . GetType ( systemTypes [ i ] ) ) ) ;
}
}
}
2026-05-10 11:47:55 -04:00
#if UNITY_EDITOR
/// <summary>
/// Strips BakedEditorReference from all entities during the entity scene optimization pass, which runs
/// after baking but before the .entities file is serialized to disk for player builds. This ensures the
/// type hash for BakedEditorReference never appears in a standalone build's entity scene, while the
/// component remains available during editor play mode for StartBakedBehaviorTreeSystem to consume.
/// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.EntitySceneOptimizations)]
public partial class StripEditorBehaviorTreeReferenceSystem : SystemBase
{
protected override void OnUpdate ( )
{
var query = GetEntityQuery ( ComponentType . ReadOnly < BakedEditorReference > ( ) ) ;
EntityManager . RemoveComponent < BakedEditorReference > ( query ) ;
}
}
#endif
2025-11-25 08:19:33 -05:00
}
#endif