#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, ILogicNode, IStackedNode { [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; private ushort m_ActiveIndex; private bool[] m_TaskStarted; private bool[] m_TaskEnded; 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; 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; } 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_TaskStarted = new bool[m_Tasks.Length]; m_TaskEnded = new bool[m_Tasks.Length]; for (int i = 0; i < m_Tasks.Length; ++i) { if (m_Tasks[i] == null) { continue; } 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); } /// /// Called when the task is started. /// public override void OnStart() { if (m_Tasks == null) { return; } for (int i = 0; i < m_Tasks.Length; ++i) { if (m_Tasks[i] == null) { continue; } m_TaskStarted[i] = false; m_TaskEnded[i] = false; } } /// /// 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) { if (m_Tasks[m_ActiveIndex] == null) { continue; } // Call start when the local task is started, not when the StackedTask starts. if (!m_TaskStarted[m_ActiveIndex]) { m_Tasks[m_ActiveIndex].OnStart(); m_TaskStarted[m_ActiveIndex] = true; } 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 (!m_TaskEnded[m_ActiveIndex]) { m_Tasks[m_ActiveIndex].OnEnd(); m_TaskEnded[m_ActiveIndex] = true; } m_ActiveIndex++; } return m_ComparisonType == ComparisonType.Sequence ? TaskStatus.Success : TaskStatus.Failure; } /// /// Called when the task stops. /// public override void OnEnd() { if (m_TaskEnded == null) { return; } for (int i = 0; i < m_Tasks.Length; ++i) { if (m_Tasks[i] == null) { continue; } if (!m_TaskEnded[i]) { m_Tasks[i].OnEnd(); } m_TaskStarted[i] = false; m_TaskEnded[i] = false; } m_ActiveIndex = 0; } /// /// 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