#if GRAPH_DESIGNER /// --------------------------------------------- /// Behavior Designer /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.BehaviorDesigner.Runtime.Tasks { using Opsive.BehaviorDesigner.Runtime; using Opsive.GraphDesigner.Runtime; using Opsive.GraphDesigner.Runtime.Variables; using Opsive.Shared.Utility; using System; using System.Collections.Generic; using Unity.Entities; using UnityEngine; using static Opsive.BehaviorDesigner.Runtime.BehaviorTreeData; /// /// The StackedTask task allows for multiple tasks to be added to a single node. /// [HideInFilterWindow] [NodeIcon("e0a8f1df788b6274a9a24003859dfa7e")] public abstract class StackedTask : Task, ITreeLogicNode, IContainerNode { [Tooltip("The index of the node.")] [SerializeField] ushort m_Index; [Tooltip("The parent index of the node. ushort.MaxValue indicates no parent.")] [SerializeField] ushort m_ParentIndex; [Tooltip("The sibling index of the node. ushort.MaxValue indicates no sibling.")] [SerializeField] ushort m_SiblingIndex; public ushort Index { get => m_Index; set => m_Index = value; } public ushort ParentIndex { get => m_ParentIndex; set => m_ParentIndex = value; } public ushort SiblingIndex { get => m_SiblingIndex; set => m_SiblingIndex = value; } public ushort RuntimeIndex { get; set; } /// /// Specifies how the tasks should be compared. /// public enum ComparisonType { Sequence, // AND. Selector // OR. } [Tooltip("The tasks that should run.")] [SerializeField] protected Task[] m_Tasks; [Tooltip("Specifies if the tasks should be traversed with an AND (Sequence) or an OR (Selector).")] [SerializeField] protected ComparisonType m_ComparisonType; /// /// Bit flags for tracking task state. /// [Flags] private enum TaskFlag : byte { None = 0, Started = 0x01, // The task has started. Ended = 0x04 // The task has ended. } private ushort m_ActiveIndex; private TaskFlag[] m_TaskFlags; public ushort ActiveIndex { get => m_ActiveIndex; } public object[] Nodes { get => m_Tasks; } public Task[] Tasks { get => m_Tasks; set => m_Tasks = value; } /// /// Adds the object to the action array. /// /// The object that should be added. public void Add(object obj) { Task task; var resetTask = true; if (obj is System.Reflection.MethodInfo) { // A delegate action needs to be created. var methodInfo = obj as System.Reflection.MethodInfo; var parameters = methodInfo.GetParameters(); var types = new Type[(parameters != null ? parameters.Length : 0) + ((methodInfo.ReturnType != typeof(void)) ? 1 : 0)]; if (parameters != null) { for (int i = 0; i < parameters.Length; ++i) { types[i] = parameters[i].ParameterType; } } Type baseType; if (methodInfo.ReturnType == typeof(void)) { if (parameters != null && parameters.Length > 0) { if (parameters.Length == 1) { baseType = typeof(TaskDelegate<>); } else if (parameters.Length == 2) { baseType = typeof(TaskDelegate<,>); } else if (parameters.Length == 3) { baseType = typeof(TaskDelegate<,,>); } else if (parameters.Length == 4) { baseType = typeof(TaskDelegate<,,,>); } else if (parameters.Length == 5) { baseType = typeof(TaskDelegate<,,,,>); } else if (parameters.Length == 6) { baseType = typeof(TaskDelegate<,,,,,>); } else if (parameters.Length == 7) { baseType = typeof(TaskDelegate<,,,,,,>); } else if (parameters.Length == 8) { baseType = typeof(TaskDelegate<,,,,,,,>); } else if (parameters.Length == 9) { baseType = typeof(TaskDelegate<,,,,,,,,>); } else if (parameters.Length == 10) { baseType = typeof(TaskDelegate<,,,,,,,,,>); } else { Debug.LogError($"Error: Unable to create TaskDelegate with {parameters.Length}. Please send this error to support@opsive.com."); return; } } else { baseType = typeof(TaskDelegate); } } else { // The method has a returned parameter. types[types.Length - 1] = methodInfo.ReturnType; if (parameters != null && parameters.Length > 0) { if (parameters.Length == 1) { baseType = typeof(TaskValueDelegate<,>); } else if (parameters.Length == 2) { baseType = typeof(TaskValueDelegate<,,>); } else if (parameters.Length == 3) { baseType = typeof(TaskValueDelegate<,,,>); } else if (parameters.Length == 4) { baseType = typeof(TaskValueDelegate<,,,,>); } else if (parameters.Length == 5) { baseType = typeof(TaskValueDelegate<,,,,,>); } else if (parameters.Length == 6) { baseType = typeof(TaskValueDelegate<,,,,,,>); } else if (parameters.Length == 7) { baseType = typeof(TaskValueDelegate<,,,,,,,>); } else if (parameters.Length == 8) { baseType = typeof(TaskValueDelegate<,,,,,,,,>); } else if (parameters.Length == 9) { baseType = typeof(TaskValueDelegate<,,,,,,,,,>); } else if (parameters.Length == 10) { baseType = typeof(TaskValueDelegate<,,,,,,,,,>); } else { Debug.LogError($"Error: Unable to create TaskValueDelegate with {parameters.Length}. Please send this error to support@opsive.com."); return; } } else { baseType = typeof(TaskValueDelegate<>); } } Type actionDelegateType; if (types.Length > 0) { actionDelegateType = baseType.MakeGenericType(types); } else { actionDelegateType = baseType; } // The Action Delegate needs to be initialized to the method. var actionDelegate = Activator.CreateInstance(actionDelegateType) as TaskDelegateBase; actionDelegate.Bind(methodInfo); task = actionDelegate; } else if (obj is Type) { task = Activator.CreateInstance((Type)obj) as Task; } else { // Task. task = obj as Task; resetTask = false; } if (resetTask) { task.Reset(); } if (m_Tasks == null) { m_Tasks = new Task[] { task }; } else { Array.Resize(ref m_Tasks, m_Tasks.Length + 1); m_Tasks[m_Tasks.Length - 1] = task; } } /// /// Removes the action at the specified index. /// /// The index of the action that should be removed. public void Remove(int index) { if (index < 0 || index >= m_Tasks.Length) { return; } m_Tasks[index].OnDestroy(); for (int i = index; i < m_Tasks.Length - 1; ++i) { m_Tasks[i] = m_Tasks[i + 1]; } Array.Resize(ref m_Tasks, m_Tasks.Length - 1); } /// /// Resets the task values back to their default. /// public override void Reset() { if (m_Tasks == null) { return; } for (int i = 0; i < m_Tasks.Length; ++i) { m_Tasks[i].Reset(); } } /// /// Initializes the base task parameters. /// /// A reference to the owning BehaviorTree. /// The runtime index of the node. internal override void Initialize(BehaviorTree behaviorTree, ushort runtimeIndex) { if (m_Tasks != null) { m_TaskFlags = new TaskFlag[m_Tasks.Length]; string[] containedNodeTypes = null; for (int i = 0; i < m_Tasks.Length; ++i) { if (m_Tasks[i] == null) { #if UNITY_EDITOR if (containedNodeTypes == null) { containedNodeTypes = GetContainedNodeTypes(behaviorTree); } #endif var unknownTask = new UnknownTask(containedNodeTypes[i]); m_Tasks[i] = unknownTask; Debug.LogError($"Error: Unable to deserialize task of type {unknownTask.UnknownType}."); } if (m_Tasks[i] is TaskDelegateBase taskDelegate) { taskDelegate.Initialize(behaviorTree, runtimeIndex, this is IConditional); } else { m_Tasks[i].Initialize(behaviorTree, runtimeIndex); } } } base.Initialize(behaviorTree, runtimeIndex); } #if UNITY_EDITOR /// /// Returns the stored contained node types for the StackedTask. /// /// The behavior tree that owns the task. private string[] GetContainedNodeTypes(BehaviorTree behaviorTree) { if (behaviorTree == null) { return null; } var nodeProperties = behaviorTree.LogicNodeProperties; if (nodeProperties == null || m_Index >= nodeProperties.Length) { return null; } return nodeProperties[m_Index].Data.ContainedNodeTypes; } #endif /// /// Called when the task is started. /// public override void OnStart() { if (m_Tasks == null) { return; } for (int i = 0; i < m_Tasks.Length; ++i) { m_TaskFlags[i] = TaskFlag.None; } } /// /// Updates all of the child tasks. /// /// The status of the task. public override TaskStatus OnUpdate() { if (m_Tasks == null || m_Tasks.Length == 0) { return TaskStatus.Failure; } while (m_ActiveIndex < m_Tasks.Length) { // Skip disabled tasks. if (!m_Tasks[m_ActiveIndex].Enabled) { m_ActiveIndex++; continue; } // Call start when the local task is started, not when the StackedTask starts. if (((byte)m_TaskFlags[m_ActiveIndex] & (byte)TaskFlag.Started) == 0) { m_Tasks[m_ActiveIndex].OnStart(); m_TaskFlags[m_ActiveIndex] |= TaskFlag.Started; } var executionStatus = m_Tasks[m_ActiveIndex].OnUpdate(); if (executionStatus == TaskStatus.Running) { return TaskStatus.Running; } if (m_ComparisonType == ComparisonType.Sequence && executionStatus == TaskStatus.Failure) { return TaskStatus.Failure; } else if (m_ComparisonType == ComparisonType.Selector && executionStatus == TaskStatus.Success) { return TaskStatus.Success; } if (((byte)m_TaskFlags[m_ActiveIndex] & (byte)TaskFlag.Ended) == 0) { m_Tasks[m_ActiveIndex].OnEnd(); m_TaskFlags[m_ActiveIndex] |= TaskFlag.Ended; } m_ActiveIndex++; } return m_ComparisonType == ComparisonType.Sequence ? TaskStatus.Success : TaskStatus.Failure; } /// /// Called when the task stops. /// public override void OnEnd() { if (m_TaskFlags == null) { return; } for (int i = 0; i < m_Tasks.Length; ++i) { if (m_Tasks[i] == null) { continue; } if (((byte)m_TaskFlags[i] & (byte)TaskFlag.Ended) == 0 && m_Tasks[i].Enabled) { m_Tasks[i].OnEnd(); } m_TaskFlags[i] = TaskFlag.None; } m_ActiveIndex = 0; } /// /// The task has been paused. /// /// The DOTS world. /// The DOTS entity. public override void Pause(World world, Entity entity) { if (m_Tasks == null) { return; } for (int i = 0; i < m_Tasks.Length; ++i) { if (m_Tasks[i] == null) { continue; } m_Tasks[i].Pause(world, entity); } } /// /// The task has been resumed. /// /// The DOTS world. /// The DOTS entity. public override void Resume(World world, Entity entity) { if (m_Tasks == null) { return; } for (int i = 0; i < m_Tasks.Length; ++i) { if (m_Tasks[i] == null) { continue; } m_Tasks[i].Resume(world, entity); } } /// /// 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 override MemberVisibility GetSaveReflectionType(int index) { return index < 0 || index >= m_Tasks.Length ? MemberVisibility.None : m_Tasks[index].GetSaveReflectionType(index); } /// /// Returns the current task state. /// /// The DOTS world. /// The DOTS entity. /// The current task state. public override object Save(World world, Entity entity) { if (m_Tasks == null) { return null; } var saveData = new object[m_Tasks.Length + 1]; for (int i = 0; i < m_Tasks.Length; ++i) { if (m_Tasks[i] == null) { continue; } var reflectionType = m_Tasks[i].GetSaveReflectionType(i); if (reflectionType != MemberVisibility.None) { saveData[i] = Serialization.Serialize(m_Tasks[i], reflectionType, BehaviorTreeData.ValidateSerializedObject); } else { saveData[i] = m_Tasks[i].Save(world, entity); } } saveData[m_Tasks.Length] = m_ActiveIndex; return saveData; } /// /// Loads the previous task state. /// /// The previous task state. /// The DOTS world. /// The DOTS entity. /// A reference to the map between the VariableAssignment and SharedVariable. /// A reference to the list of task references that need to be resolved later. public override void Load(object saveData, World world, Entity entity, Dictionary variableByNameMap, ref ResizableArray taskReferences) { if (m_Tasks == null || saveData == null) { return; } var taskData = (object[])saveData; if (taskData.Length != m_Tasks.Length + 1) { Debug.LogError("Error: The save data does not match the task data length."); return; } for (int i = 0; i < m_Tasks.Length; ++i) { if (taskData[i] == null) { continue; } Load(m_Tasks[i], i, taskData[i], world, entity, variableByNameMap, ref taskReferences); } m_ActiveIndex = (ushort)taskData[m_Tasks.Length]; } /// /// Loads the previous task state. /// /// The task that the saveData belongs to. /// The index of the task within the Tasks array. /// The previous task state. /// The DOTS world. /// The DOTS entity. /// A reference to the map between the VariableAssignment and SharedVariable. /// A reference to the list of task references that need to be resolved later. protected virtual void Load(Task task, int index, object saveData, World world, Entity entity, Dictionary variableByNameMap, ref ResizableArray taskReferences) { var reflectionType = task.GetSaveReflectionType(index); if (reflectionType != MemberVisibility.None) { var localTaskReferences = taskReferences; (saveData as Serialization).DeserializeFields(task, reflectionType, BehaviorTreeData.ValidateDeserializedTypeObject, (object fieldInfoObj, object task, object value) => { return BehaviorTreeData.ValidateDeserializedObject(fieldInfoObj, task, value, ref variableByNameMap, ref localTaskReferences); }); taskReferences = localTaskReferences; } else { task.Load(saveData, world, entity, variableByNameMap, ref taskReferences); } } /// /// Callback when OnDrawGizmos is triggered. /// protected override void OnDrawGizmos() { if (m_Tasks == null) { return; } for (int i = 0; i < m_Tasks.Length; ++i) { if (m_Tasks[i] == null) { continue; } m_Tasks[i].OnDrawGizmos(m_BehaviorTree); } } /// /// Callback when OnDrawGizmosSelected is triggered. /// protected override void OnDrawGizmosSelected() { if (m_Tasks == null) { return; } for (int i = 0; i < m_Tasks.Length; ++i) { if (m_Tasks[i] == null) { continue; } m_Tasks[i].OnDrawGizmosSelected(m_BehaviorTree); } } } } #endif