ProjectDDD/Packages/com.opsive.behaviordesigner/Runtime/BehaviorTreeData.cs
2025-08-19 18:53:26 +09:00

1595 lines
84 KiB
C#

#if GRAPH_DESIGNER
/// ---------------------------------------------
/// Behavior Designer
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.BehaviorDesigner.Runtime
{
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Events;
using Opsive.GraphDesigner.Runtime;
using Opsive.GraphDesigner.Runtime.Utility;
using Opsive.GraphDesigner.Runtime.Variables;
using Opsive.Shared.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
/// <summary>
/// Storage class for the graph data.
/// </summary>
[System.Serializable]
public class BehaviorTreeData
{
[Tooltip("The serialized Task data.")]
[SerializeField] private Serialization[] m_TaskData;
[Tooltip("The serialized EventTask data.")]
[SerializeField] private Serialization[] m_EventTaskData;
[Tooltip("The serialized SharedVariable data.")]
[SerializeField] private Serialization[] m_SharedVariableData;
[Tooltip("The serialized disabled event nodes data.")]
[SerializeField] private Serialization[] m_DisabledEventNodesData;
[Tooltip("The serialized disabled logic nodes data.")]
[SerializeField] private Serialization[] m_DisabledLogicNodesData;
[Tooltip("The unique ID of the data.")]
[SerializeField] private int m_UniqueID;
private ILogicNode[] m_Tasks;
private IEventNode[] m_EventTasks;
private SharedVariable[] m_SharedVariables;
private ushort[] m_DisabledLogicNodes;
private ushort[] m_DisabledEventNodes;
private Dictionary<VariableAssignment, SharedVariable> m_VariableByNameMap;
private int m_RuntimeUniqueID;
public ILogicNode[] LogicNodes
{
get => m_Tasks;
set {
if (value == null) {
m_Tasks = null;
} else {
if (m_Tasks == null) {
m_Tasks = new ILogicNode[value.Length];
} else if (m_Tasks.Length != value.Length) {
Array.Resize(ref m_Tasks, value.Length);
}
for (int i = 0; i < value.Length; ++i) {
m_Tasks[i] = value[i];
}
}
}
}
public IEventNode[] EventNodes
{
get => m_EventTasks;
set {
if (value == null) {
m_EventTasks = null;
} else {
if (m_EventTasks == null) {
m_EventTasks = new IEventNode[value.Length];
} else if (m_EventTasks.Length != value.Length) {
Array.Resize(ref m_EventTasks, value.Length);
}
for (int i = 0; i < value.Length; ++i) {
m_EventTasks[i] = value[i];
}
}
}
}
public SharedVariable[] SharedVariables { get => m_SharedVariables; set => m_SharedVariables = value; }
public int UniqueID { get => m_RuntimeUniqueID != 0 ? m_RuntimeUniqueID : m_UniqueID; }
public ushort[] DisabledLogicNodes { get => m_DisabledLogicNodes; set => m_DisabledLogicNodes = value; }
public ushort[] DisabledEventNodes { get => m_DisabledEventNodes; set => m_DisabledEventNodes = value; }
internal Dictionary<VariableAssignment, SharedVariable> VariableByNameMap { get => m_VariableByNameMap; set => m_VariableByNameMap = value; }
#if UNITY_EDITOR
[Tooltip("The serialized logic node properties data.")]
[SerializeField] private Serialization[] m_LogicNodePropertiesData;
[Tooltip("The serialized event node properties data.")]
[SerializeField] private Serialization[] m_EventNodePropertiesData;
[Tooltip("The serialized group properties data.")]
[SerializeField] private Serialization[] m_GroupPropertiesData;
private LogicNodeProperties[] m_LogicNodeProperties;
private NodeProperties[] m_EventNodeProperties;
private GroupProperties[] m_GroupProperties;
public LogicNodeProperties[] LogicNodeProperties { get => m_LogicNodeProperties; set { m_LogicNodeProperties = value; } }
public NodeProperties[] EventNodeProperties { get => m_EventNodeProperties; set { m_EventNodeProperties = value; } }
public GroupProperties[] GroupProperties { get => m_GroupProperties; set => m_GroupProperties = value; }
#endif
private ResizableArray<SubtreeNodesReference> m_SubtreeNodesReference;
[System.NonSerialized] private bool m_Deserializing;
internal ResizableArray<SubtreeNodesReference> SubtreeNodesReferences { get => m_SubtreeNodesReference; set => m_SubtreeNodesReference = value; }
/// <summary>
/// Default constructor.
/// </summary>
public BehaviorTreeData()
{
m_UniqueID = Guid.NewGuid().GetHashCode();
}
/// <summary>
/// Adds the specified node.
/// </summary>
/// <param name="node">The node that should be added.</param>
public void AddNode(ILogicNode node)
{
if (m_Tasks == null) {
m_Tasks = new ILogicNode[1];
} else {
Array.Resize(ref m_Tasks, m_Tasks.Length + 1);
}
node.Index = (ushort)(m_Tasks.Length - 1);
node.ParentIndex = ushort.MaxValue;
node.SiblingIndex = ushort.MaxValue;
node.RuntimeIndex = ushort.MaxValue;
m_Tasks[m_Tasks.Length - 1] = node;
}
/// <summary>
/// Removes the specified logic node.
/// </summary>
/// <param name="node">The node that should be removed.</param>
/// <returns>True if the node was removed.</returns>
public bool RemoveNode(ILogicNode node)
{
if (m_Tasks == null || node.Index >= m_Tasks.Length) {
return false;
}
var dest = new ILogicNode[m_Tasks.Length - 1];
Array.Copy(m_Tasks, dest, node.Index);
Array.Copy(m_Tasks, node.Index + 1, dest, node.Index, m_Tasks.Length - node.Index - 1);
m_Tasks = dest;
return true;
}
/// <summary>
/// Adds the specified event node.
/// </summary>
/// <param name="eventNode">The event node that should be added.</param>
public void AddNode(IEventNode eventNode)
{
if (m_EventTasks == null) {
m_EventTasks = new IEventNode[1];
} else {
Array.Resize(ref m_EventTasks, m_EventTasks.Length + 1);
}
m_EventTasks[m_EventTasks.Length - 1] = eventNode;
}
/// <summary>
/// Removes the specified event node.
/// </summary>
/// <param name="eventNode">The event node that should be removed.</param>
/// <returns>True if the event node was removed.</returns>
public bool RemoveNode(IEventNode eventNode)
{
if (m_EventTasks == null) {
return false;
}
var index = m_EventTasks.IndexOf(eventNode);
if (index == -1) {
return false;
}
var dest = new IEventNode[m_EventTasks.Length - 1];
Array.Copy(m_EventTasks, dest, index);
Array.Copy(m_EventTasks, index + 1, dest, index, m_EventTasks.Length - index - 1);
m_EventTasks = dest;
return true;
}
/// <summary>
/// Serializes the behavior tree.
/// </summary>
public void Serialize()
{
m_TaskData = Serialization.Serialize<ILogicNode>(m_Tasks, ValidateSerializedObject);
m_EventTaskData = Serialization.Serialize<IEventNode>(m_EventTasks, ValidateSerializedObject);
SerializeSharedVariables();
m_DisabledEventNodesData = Serialization.Serialize<ushort>(m_DisabledEventNodes);
m_DisabledLogicNodesData = Serialization.Serialize<ushort>(m_DisabledLogicNodes);
m_UniqueID = Guid.NewGuid().GetHashCode();
#if UNITY_EDITOR
// Ensure the node data is up to date.
if (m_LogicNodeProperties != null) {
for (int i = 0; i < m_LogicNodeProperties.Length; ++i) {
var nodeData = m_LogicNodeProperties[i].Data;
nodeData.ParentIndex = m_Tasks[i].ParentIndex;
nodeData.SiblingIndex = m_Tasks[i].SiblingIndex;
nodeData.IsParent = m_Tasks[i] is IParentNode;
m_LogicNodeProperties[i].Data = nodeData;
}
}
m_LogicNodePropertiesData = Serialization.Serialize<LogicNodeProperties>(m_LogicNodeProperties);
m_EventNodePropertiesData = Serialization.Serialize<NodeProperties>(m_EventNodeProperties);
m_GroupPropertiesData = Serialization.Serialize<GroupProperties>(m_GroupProperties);
#endif
}
/// <summary>
/// Validates the serialized object.
/// </summary>
/// <param name="type">The type of object.</param>
/// <param name="field">The field that the object belongs to.</param>
/// <param name="value">The value of the object</param>
/// <returns>The validated object.</returns>
public static Serialization.ValidatedObject ValidateSerializedObject(Type type, FieldInfo field, object value)
{
if (value == null) {
return new Serialization.ValidatedObject() { Type = type, Obj = value };
}
// Replace ILogicNode with ushort index values.
if (typeof(IList).IsAssignableFrom(type)) {
var elementType = Serializer.GetElementType(type);
if (typeof(ILogicNode).IsAssignableFrom(elementType)) {
if (field == null || field.GetCustomAttribute<InspectNodeAttribute>() == null) {
var tasks = value as IList;
if (tasks == null) {
return new Serialization.ValidatedObject() { Type = type, Obj = value };
}
var indexValues = new ushort[tasks.Count];
for (int i = 0; i < indexValues.Length; ++i) {
indexValues[i] = ((ILogicNode)tasks[i]).Index;
}
return new Serialization.ValidatedObject() { Type = typeof(ushort[]), Obj = indexValues };
}
} else if (Application.isPlaying && (typeof(GameObject).IsAssignableFrom(elementType) || typeof(Component).IsAssignableFrom(elementType))) { // Scene objects cannot be serialized at runtime.
var listValue = value as IList;
if (listValue != null) {
IList objects;
if (type.IsArray) {
objects = Array.CreateInstance(elementType, listValue.Count);
} else {
if (type.IsGenericType) {
objects = Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)) as IList;
} else {
objects = Activator.CreateInstance(type) as IList;
}
}
for (int i = 0; i < listValue.Count; ++i) {
GameObject gameObjectValue = null;
if (listValue[i] is Component componentValue) {
gameObjectValue = componentValue.gameObject;
} else {
gameObjectValue = listValue[i] as GameObject;
}
if (gameObjectValue != null && gameObjectValue.scene.IsValid()) {
if (type.IsArray) {
objects[i] = null;
} else {
objects.Add(null);
}
} else {
if (type.IsArray) {
objects[i] = listValue[i];
} else {
objects.Add(listValue[i]);
}
}
}
listValue = objects;
}
return new Serialization.ValidatedObject() { Type = type, Obj = listValue };
}
} else if (typeof(ILogicNode).IsAssignableFrom(type)) {
if (field == null || field.GetCustomAttribute<InspectNodeAttribute>() == null) {
return new Serialization.ValidatedObject() { Type = typeof(ushort), Obj = ((ILogicNode)value).Index };
}
} else if (Application.isPlaying && (typeof(GameObject).IsAssignableFrom(type) || typeof(Component).IsAssignableFrom(type))) { // Scene objects cannot be serialized at runtime.
GameObject gameObjectValue = null;
if (value is Component componentValue) {
gameObjectValue = componentValue.gameObject;
} else {
gameObjectValue = value as GameObject;
}
if (gameObjectValue != null && gameObjectValue.scene.IsValid()) {
return new Serialization.ValidatedObject() { Type = type, Obj = null };
}
}
return new Serialization.ValidatedObject() { Type = type, Obj = value };
}
/// <summary>
/// Serializes the SharedVariables. This allows the SharedVariables to be serialized independently.
/// </summary>
public void SerializeSharedVariables()
{
m_SharedVariableData = Serialization.Serialize<SharedVariable>(m_SharedVariables);
// Update the mapping for any variable name changes.
if (m_VariableByNameMap == null) {
m_VariableByNameMap = new Dictionary<VariableAssignment, SharedVariable>();
} else {
m_VariableByNameMap.Clear();
}
if (m_SharedVariables != null) {
for (int i = 0; i < m_SharedVariables.Length; ++i) {
m_VariableByNameMap.Add(new VariableAssignment(m_SharedVariables[i].Name, m_SharedVariables[i].Scope), m_SharedVariables[i]);
}
}
}
/// <summary>
/// Internal data structure for restoring a task reference after it has been deserialized.
/// </summary>
public struct TaskAssignment
{
[Tooltip("The field of the task.")]
public FieldInfo Field;
[Tooltip("The task that the field belongs to.")]
public object Target;
[Tooltip("The value of the field. This will be the task object that should be assigned after the tree has been loaded.")]
public object Value;
}
/// <summary>
/// Deserialize the behavior tree.
/// </summary>
/// <param name="graphComponent">The component that the graph is being deserialized from.</param>
/// <param name="graph">The graph that is being deserialized.</param>
/// <param name="force">Should the behavior tree be force deserialized?</param>
/// <param name="variableForce">Should the shared variables be force deserialized?</param>
/// <param name="injectSubtrees">Should the subtrees be injected into the behavior tree?</param>
/// <param name="canDeepCopyVariables">Can the SharedVariables be deep copied?</param>
/// <param name="sharedVariableOverrides">A list of SharedVariables that should override the current SharedVariable value.</param>
/// <returns>True if the tree was deserialized.</returns>
public bool Deserialize(IGraphComponent graphComponent, IGraph graph, bool force, bool injectSubtrees, bool canDeepCopyVariables = true, SharedVariableOverride[] sharedVariableOverrides = null)
{
// No need to deserialize if the data is already deserialized.
if ((m_Tasks != null || m_EventTasks != null || m_SharedVariables != null) && !force) {
if (Application.isPlaying && m_RuntimeUniqueID == 0) {
m_RuntimeUniqueID = m_UniqueID;
}
return true;
}
return DeserializeInternal(graphComponent, graph, force, injectSubtrees, canDeepCopyVariables, sharedVariableOverrides);
}
/// <summary>
/// Internal method which deserialize the behavior tree.
/// </summary>
/// <param name="graphComponent">The component that the graph is being deserialized from.</param>
/// <param name="graph">The graph that is being deserialized.</param>
/// <param name="force">Should the behavior tree be force deserialized?</param>
/// <param name="injectSubtrees">Should the subtrees be injected into the behavior tree?</param>
/// <param name="canDeepCopyVariables">Can the SharedVariables be deep copied?</param>
/// <param name="sharedVariableOverrides">A list of SharedVariables that should override the current SharedVariable value.</param>
/// <returns>True if the tree was deserialized.</returns>
private bool DeserializeInternal(IGraphComponent graphComponent, IGraph graph, bool force, bool injectSubtrees, bool canDeepCopyVariables, SharedVariableOverride[] sharedVariableOverrides = null)
{
// Prevent the tree from being deserialized recusrively.
if (m_Deserializing) {
Debug.LogError($"Error: Unable to deserialize {graph}. This can be caused by recursive subtree references.");
return false;
}
m_Deserializing = true;
m_RuntimeUniqueID = Application.isPlaying ? m_UniqueID : 0;
var errorState = false;
#if UNITY_EDITOR
// Deserialize the properties first so it can be used elsewhere.
if (m_LogicNodePropertiesData != null && m_LogicNodePropertiesData.Length > 0) {
m_LogicNodeProperties = new LogicNodeProperties[m_LogicNodePropertiesData.Length];
for (int i = 0; i < m_LogicNodePropertiesData.Length; ++i) {
try {
m_LogicNodeProperties[i] = m_LogicNodePropertiesData[i].DeserializeFields(MemberVisibility.Public) as LogicNodeProperties;
} catch (Exception e) {
m_LogicNodeProperties[i] = new LogicNodeProperties();
Debug.LogError($"Error: Unable to load task editor data at index {i} due to exception:\n{e}");
}
}
} else {
m_LogicNodeProperties = null;
}
if (m_EventNodePropertiesData != null && m_EventNodePropertiesData.Length > 0) {
m_EventNodeProperties = new NodeProperties[m_EventNodePropertiesData.Length];
for (int i = 0; i < m_EventNodePropertiesData.Length; ++i) {
m_EventNodeProperties[i] = m_EventNodePropertiesData[i].DeserializeFields(MemberVisibility.Public) as NodeProperties;
}
} else {
m_EventNodeProperties = null;
}
if (m_GroupPropertiesData != null && m_GroupPropertiesData.Length > 0) {
m_GroupProperties = new GroupProperties[m_GroupPropertiesData.Length];
for (int i = 0; i < m_GroupPropertiesData.Length; ++i) {
m_GroupProperties[i] = m_GroupPropertiesData[i].DeserializeFields(MemberVisibility.Public) as GroupProperties;
}
} else {
m_GroupProperties = null;
}
#endif
DeserializeSharedVariables(force, sharedVariableOverrides);
m_VariableByNameMap = PopulateSharedVariablesMapping(graph, canDeepCopyVariables);
// The disabled node indicies need to be deserialized before the nodes.
if (m_DisabledEventNodesData != null && m_DisabledEventNodesData.Length > 0 && m_EventTaskData != null) {
m_DisabledEventNodes = new ushort[m_DisabledEventNodesData.Length];
var offset = 0;
for (int i = 0; i < m_DisabledEventNodesData.Length; ++i) {
m_DisabledEventNodes[i] = (ushort)m_DisabledEventNodesData[i].DeserializeFields(MemberVisibility.Public);
// The node index may no longer be valid.
if (m_DisabledEventNodes[i - offset] >= m_EventTaskData.Length) {
offset++;
}
}
if (offset > 0) {
Array.Resize(ref m_DisabledEventNodes, m_DisabledEventNodes.Length - offset);
}
} else {
m_DisabledEventNodes = null;
}
if (m_DisabledLogicNodesData != null && m_DisabledLogicNodesData.Length > 0 && m_TaskData != null) {
m_DisabledLogicNodes = new ushort[m_DisabledLogicNodesData.Length];
var offset = 0;
for (int i = 0; i < m_DisabledLogicNodesData.Length; ++i) {
m_DisabledLogicNodes[i - offset] = (ushort)m_DisabledLogicNodesData[i].DeserializeFields(MemberVisibility.Public);
// The node index may no longer be valid.
if (m_DisabledLogicNodes[i - offset] >= m_TaskData.Length) {
offset++;
}
}
if (offset > 0) {
Array.Resize(ref m_DisabledLogicNodes, m_DisabledLogicNodes.Length - offset);
}
} else {
m_DisabledLogicNodes = null;
}
ResizableArray<TaskAssignment> taskReferences = null;
if (m_SubtreeNodesReference != null) {
m_SubtreeNodesReference.Clear();
}
if (m_TaskData != null && m_TaskData.Length > 0) {
m_Tasks = new ILogicNode[m_TaskData.Length];
for (int i = 0; i < m_TaskData.Length; ++i) {
try {
m_Tasks[i] = m_TaskData[i].DeserializeFields(MemberVisibility.Public, ValidateDeserializedTypeObject, (object fieldInfoObj, object task, object value) =>
{
return ValidateDeserializedObject(fieldInfoObj, task, value, ref m_VariableByNameMap, ref taskReferences, sharedVariableOverrides);
}) as ILogicNode;
} catch (Exception e) {
Debug.LogError($"Error: Unable to load task {m_TaskData[i].ObjectType} at index {i} due to exception:\n{e}");
}
// Account for tasks where the object no longer exists.
if (m_Tasks[i] == null) {
#if UNITY_EDITOR
if (m_LogicNodeProperties[i].Data.IsParent) {
m_Tasks[i] = new UnknownParentTaskNode(m_TaskData[i].ObjectType);
} else {
m_Tasks[i] = new UnknownTaskNode(m_TaskData[i].ObjectType);
}
m_Tasks[i].Index = (ushort)i;
m_Tasks[i].ParentIndex = m_LogicNodeProperties[i].Data.ParentIndex;
m_Tasks[i].SiblingIndex = m_LogicNodeProperties[i].Data.SiblingIndex;
#else
if (i + 1 < m_Tasks.Length && m_Tasks[i + 1] != null && m_Tasks[i + 1].ParentIndex == i) {
m_Tasks[i] = new UnknownParentTaskNode(m_TaskData[i].ObjectType);
} else {
m_Tasks[i] = new UnknownTaskNode(m_TaskData[i].ObjectType);
}
m_Tasks[i].Index = (ushort)i;
#endif
}
// The RuntimeIndex is assigned later when the tree is initialized.
m_Tasks[i].RuntimeIndex = ushort.MaxValue;
#if UNITY_EDITOR
// Sanity checks.
if (m_Tasks[i].Index >= m_TaskData.Length) { m_Tasks[i].Index = (ushort)i; }
if (m_Tasks[i].ParentIndex != ushort.MaxValue && m_Tasks[i].ParentIndex >= m_TaskData.Length) { m_Tasks[i].ParentIndex = ushort.MaxValue; }
if (m_Tasks[i].SiblingIndex != ushort.MaxValue && m_Tasks[i].SiblingIndex >= m_TaskData.Length) { m_Tasks[i].SiblingIndex = ushort.MaxValue; }
#endif
if (injectSubtrees) {
// If the previous task is a parent the current task has to be a child otherwise the tree is in an error state. The error will also occur
// if there is only one task and that task is a parent task.
if ((m_Tasks[i].ParentIndex != ushort.MaxValue && (i > 0 && m_Tasks[i - 1] is IParentNode && m_Tasks[i].ParentIndex != m_Tasks[i - 1].Index)) || (m_Tasks[i] is IParentNode && i + 1 == m_Tasks.Length)) {
Debug.LogError($"Error: {graph} contains the parent task {m_Tasks[i].GetType().Name} which does not have any children. All parent tasks must contain at least one child.", graph.Parent);
errorState = true;
continue;
}
// Subtrees will be evaluated after all tasks are assigned.
if (m_Tasks[i] is ISubtreeReference subtreeReference) {
// Subtrees can be nested.
subtreeReference.EvaluateSubtrees(graphComponent);
var subtrees = subtreeReference.Subtrees;
if (subtrees != null) {
// The parent must be able to accept the number of subtrees that there are.
var parentIndex = m_Tasks[i].ParentIndex;
IParentNode parentNode = null;
if (parentIndex != ushort.MaxValue) {
parentNode = m_Tasks[parentIndex] as IParentNode;
}
if ((parentNode == null && subtrees.Length > 1) || (parentNode != null && subtrees.Length > parentNode.MaxChildCount)) {
Debug.LogError($"Error: {graph} on object {graph.Parent} contains multiple subtrees as the starting task or as a child of a parent task which cannot contain so many children (such as a decorator).", graph.Parent);
errorState = true;
continue;
}
var deserializedNodes = new ILogicNode[subtrees.Length][];
for (int j = 0; j < subtrees.Length; ++j) {
if (subtrees[j] == null) {
continue;
}
if (!subtrees[j].Deserialize(graphComponent, force, true, true, subtreeReference.SharedVariableOverrides)) {
errorState = true;
break;
};
// Keep a reference to the deserialized nodes. This will ensure they are unique and do not get overwritten.
deserializedNodes[j] = subtrees[j].LogicNodes;
// Add any new subtree variables to the current tree.
if (subtrees[j].SharedVariables != null) {
// In order to reduce allocations the first loop will determine the number of variables that need to be added.
var length = subtrees[j].SharedVariables.Length;
var variableCount = 0;
for (int k = 0; k < length; ++k) {
var subtreeVariable = subtrees[j].SharedVariables[k];
if (GetVariable(graph, subtreeVariable.Name, SharedVariable.SharingScope.Graph) == null) {
variableCount++;
}
}
// And the second loop will actually add the variables.
if (variableCount > 0) {
var insertIndex = 0;
if (m_SharedVariables == null) {
m_SharedVariables = new SharedVariable[variableCount];
m_VariableByNameMap = new Dictionary<VariableAssignment, SharedVariable>();
} else {
insertIndex = m_SharedVariables.Length;
Array.Resize(ref m_SharedVariables, m_SharedVariables.Length + variableCount);
}
for (int k = 0; k < length; ++k) {
var subtreeVariable = subtrees[j].SharedVariables[k];
if (!m_VariableByNameMap.ContainsKey(new VariableAssignment(subtreeVariable.Name, SharedVariable.SharingScope.Graph))) {
m_SharedVariables[insertIndex] = subtreeVariable;
m_VariableByNameMap.Add(new VariableAssignment(subtreeVariable.Name, SharedVariable.SharingScope.Graph), subtreeVariable);
insertIndex++;
}
}
}
}
}
// Do not add the subtree if it causes an error.
if (!errorState) {
if (m_SubtreeNodesReference == null) { m_SubtreeNodesReference = new ResizableArray<SubtreeNodesReference>(); }
m_SubtreeNodesReference.Add(new SubtreeNodesReference()
{
SubtreeReference = subtreeReference,
NodeIndex = (ushort)i,
Subtrees = subtrees,
Nodes = deserializedNodes
});
}
}
}
}
}
} else {
m_Tasks = null;
}
// Subtrees should be injected into the tree.
InjectSubtrees();
// Add the event tasks after the subtrees have been injected to ensure the connected index is correct.
if (m_EventTaskData != null && m_EventTaskData.Length > 0) {
m_EventTasks = new IEventNode[m_EventTaskData.Length];
for (int i = 0; i < m_EventTaskData.Length; ++i) {
m_EventTasks[i] = m_EventTaskData[i].DeserializeFields(MemberVisibility.Public, ValidateDeserializedTypeObject, (object fieldInfoObj, object task, object value) =>
{
return ValidateDeserializedObject(fieldInfoObj, task, value, ref m_VariableByNameMap, ref taskReferences);
}) as IEventNode;
if (m_SubtreeNodesReference != null) {
// A subtree may have injected nodes before the originally connected index. Modify the index to match the injection.
var offset = 0;
for (int j = 0; j < m_SubtreeNodesReference.Count; ++j) {
if (m_SubtreeNodesReference[j].NodeIndex >= m_EventTasks[i].ConnectedIndex) {
break;
}
offset += m_SubtreeNodesReference[j].NodeCount - 1;
}
if (offset != 0) {
m_EventTasks[i].ConnectedIndex += (ushort)offset;
}
}
if (m_EventTasks[i] == null) {
m_EventTasks[i] = new UnknownEventTask();
}
}
} else {
m_EventTasks = null;
}
// After the tree has been deserialized the task references need to be assigned.
AssignTaskReferences(m_Tasks, taskReferences);
m_Deserializing = false;
return !errorState;
}
/// <summary>
/// Validates the object type when deserializing.
/// </summary>
/// <param name="type">The type of object that should be validated.</param>
/// <param name="field">The field that contains the object.</param>
/// <returns>The validated type.</returns>
public static Type ValidateDeserializedTypeObject(Type type, FieldInfo field)
{
if (typeof(IList).IsAssignableFrom(type)) {
var elementType = Serializer.GetElementType(type);
if (typeof(ILogicNode).IsAssignableFrom(elementType) && (field == null || field.GetCustomAttribute<InspectNodeAttribute>() == null)) {
return typeof(ushort[]);
}
} else if (typeof(ILogicNode).IsAssignableFrom(type) && (field == null || field.GetCustomAttribute<InspectNodeAttribute>() == null)) {
return typeof(ushort);
}
return type;
}
/// <summary>
/// Validates the object when deserializing.
/// </summary>
/// <param name="fieldInfoObj">The FieldInfo that is being deserialized.</param>
/// <param name="target">The object being deserialized.</param>
/// <param name="value">The value of the field.</param>
/// <param name="variableByNameMap">A reference to the map between the VariableAssignment and SharedVariable.</param>
/// <param name="taskReferences">A reference to the list of task references that need to be resolved later.</param>
/// <param name="sharedVariableOverrides">A list of SharedVariables that should override the current SharedVariable value.</param>
/// <returns>The validated object.</returns>
public static object ValidateDeserializedObject(object fieldInfoObj, object target, object value, ref Dictionary<VariableAssignment, SharedVariable> variableByNameMap,
ref ResizableArray<TaskAssignment> taskReferences, SharedVariableOverride[] sharedVariableOverrides = null)
{
var fieldInfo = fieldInfoObj as FieldInfo;
if (fieldInfo == null) {
return value;
}
var type = fieldInfo.FieldType;
if (value == null) {
// A SharedVariable object should always exist.
if (!type.IsAbstract && typeof(SharedVariable).IsAssignableFrom(type)) {
return Activator.CreateInstance(type);
}
return null;
}
if (typeof(IList).IsAssignableFrom(type)) {
var elementType = Serializer.GetElementType(type);
if (typeof(ILogicNode).IsAssignableFrom(elementType) && fieldInfo.GetCustomAttribute<InspectNodeAttribute>() == null) {
// The task reference will be assigned after all of the tasks have been deserialized.
if (taskReferences == null) { taskReferences = new ResizableArray<TaskAssignment>(); }
taskReferences.Add(new TaskAssignment() { Field = fieldInfo, Target = target, Value = value });
} else if (typeof(SharedVariable).IsAssignableFrom(elementType)) {
var listValue = value as IList;
if (listValue != null) {
for (int i = 0; i < listValue.Count; ++i) {
var sharedVariableElement = listValue[i] as SharedVariable;
if (variableByNameMap != null && sharedVariableElement != null && !string.IsNullOrEmpty(sharedVariableElement.Name)) {
if (variableByNameMap.TryGetValue(new VariableAssignment(sharedVariableElement.Name, sharedVariableElement.Scope), out var mappedSharedVariable)) {
if (Application.isPlaying && sharedVariableElement.Scope == SharedVariable.SharingScope.Dynamic && sharedVariableElement.GetType() != mappedSharedVariable.GetType()) {
Debug.LogError($"Error: The dynamic variables with name {sharedVariableElement.Name} have different types. All dynamic variables must have the same type.");
listValue[i] = sharedVariableElement;
} else {
listValue[i] = GetOverrideVariable(sharedVariableOverrides, mappedSharedVariable, false);
}
} else if (sharedVariableElement.Scope == SharedVariable.SharingScope.Dynamic) {
// New dynamic variables should have the default value.
var sharedVariableValueType = sharedVariableElement.GetType().GetGenericArguments()[0];
if (sharedVariableValueType.IsValueType) {
sharedVariableElement.SetValue(Activator.CreateInstance(sharedVariableValueType));
} else {
sharedVariableElement.SetValue(null);
}
// Dynamic variables are created when the task is deserialized. The variable needs to be added to the mapping so it can be reused.
variableByNameMap.Add(new VariableAssignment(sharedVariableElement.Name, sharedVariableElement.Scope), sharedVariableElement);
listValue[i] = sharedVariableElement;
}
}
}
return listValue;
}
}
} else if (typeof(ILogicNode).IsAssignableFrom(type) && fieldInfo.GetCustomAttribute<InspectNodeAttribute>() == null) {
// The task reference will be assigned after all of the tasks have been deserialized.
if (taskReferences == null) { taskReferences = new ResizableArray<TaskAssignment>(); }
taskReferences.Add(new TaskAssignment() { Field = fieldInfo, Target = target, Value = value });
} else if (typeof(SharedVariable).IsAssignableFrom(type)) {
var sharedVariable = value as SharedVariable;
if (variableByNameMap != null && sharedVariable != null && !string.IsNullOrEmpty(sharedVariable.Name)) {
if (variableByNameMap.TryGetValue(new VariableAssignment(sharedVariable.Name, sharedVariable.Scope), out var mappedSharedVariable)) {
if (Application.isPlaying && sharedVariable.Scope == SharedVariable.SharingScope.Dynamic && sharedVariable.GetType() != mappedSharedVariable.GetType()) {
Debug.LogError($"Error: The dynamic variables with name {sharedVariable.Name} have different types. Dynamic variables with the same name must have the same type.");
return sharedVariable;
}
return GetOverrideVariable(sharedVariableOverrides, mappedSharedVariable, false);
} else if (Application.isPlaying && sharedVariable.Scope == SharedVariable.SharingScope.Dynamic) {
// New dynamic variables should have the default value.
var sharedVariableValueType = sharedVariable.GetType().GetGenericArguments()[0];
if (sharedVariableValueType.IsValueType) {
sharedVariable.SetValue(Activator.CreateInstance(sharedVariableValueType));
} else {
sharedVariable.SetValue(null);
}
// Dynamic variables are created when the task is deserialized. The variable needs to be added to the mapping so it can be reused.
variableByNameMap.Add(new VariableAssignment(sharedVariable.Name, sharedVariable.Scope), sharedVariable);
return sharedVariable;
}
}
}
return value;
}
/// <summary>
/// Deserializes the SharedVariables. This allows the SharedVariables to be deserialized independently.
/// </summary>
/// <param name="force">Should the variables be forced deserialized?</param>
/// <param name="sharedVariableOverrides">A list of SharedVariables that should override the current SharedVariable value.</param>
/// <returns>True if the SharedVariables were deserialized.</returns>
public bool DeserializeSharedVariables(bool force, SharedVariableOverride[] sharedVariableOverrides = null)
{
// No need to deserialize if the data is already deserialized.
if (m_SharedVariables != null && !force) {
return false;
}
if (m_SharedVariableData != null && m_SharedVariableData.Length > 0) {
m_SharedVariables = new SharedVariable[m_SharedVariableData.Length];
for (int i = 0; i < m_SharedVariableData.Length; ++i) {
try {
m_SharedVariables[i] = m_SharedVariableData[i].DeserializeFields(MemberVisibility.Public) as SharedVariable;
} catch (Exception e) {
Debug.LogError($"Error: Unable to load variable {m_SharedVariableData[i].ObjectType} at index {i} due to exception:\n{e}");
}
if (m_SharedVariables[i] == null) {
m_SharedVariables[i] = new UnknownSharedVariable();
m_SharedVariables[i].Name = m_SharedVariableData[i].ObjectType + UnityEngine.Random.value;
Debug.LogError($"Error: Unable to deserialize SharedVariable of type {m_SharedVariableData[i].ObjectType}.");
continue;
}
// The override variable can set a value specific for the subtree.
if (Application.isPlaying) {
m_SharedVariables[i].Initialize();
var overrideVariable = GetOverrideVariable(sharedVariableOverrides, m_SharedVariables[i], true);
// If the overridden scope is self then only the value should be overridden and not the SharedVariable reference.
if (overrideVariable != null && overrideVariable.Scope == SharedVariable.SharingScope.Self) {
m_SharedVariables[i].SetValue(overrideVariable.GetValue());
}
}
}
} else {
m_SharedVariables = null;
}
return true;
}
/// <summary>
/// Returns the override SharedVariable from the source SharedVariable.
/// </summary>
/// <param name="sharedVariableOverrides">The list of override SharedVariables.</param>
/// <param name="graphVariable">The variable that should be overridden.</param>
/// <param name="deserialize">Is the method being called when the variables are being deserialized?</param>
/// <returns>The override SharedVariable (can be null).</returns>
private static SharedVariable GetOverrideVariable(SharedVariableOverride[] sharedVariableOverrides, SharedVariable graphVariable, bool deserialize)
{
if (sharedVariableOverrides == null) {
return deserialize ? null : graphVariable;
}
for (int i = 0; i < sharedVariableOverrides.Length; ++i) {
var overrideVariable = sharedVariableOverrides[i].Override;
// Empty variables indicate that the variable should not be overridden.
if (overrideVariable == null || overrideVariable.Scope == SharedVariable.SharingScope.Empty) {
continue;
}
// The override variable should be used if the name and the type matches.
var sourceVariable = sharedVariableOverrides[i].Source;
if (sourceVariable.GetType() != graphVariable.GetType() || sourceVariable.Name != graphVariable.Name) {
continue;
}
// If the scope is self then the graphVariable value should be updated instead of completely replaced.
if (overrideVariable.Scope == SharedVariable.SharingScope.Self) {
graphVariable.SetValue(overrideVariable.GetValue());
return graphVariable;
}
return overrideVariable;
}
return graphVariable;
}
/// <summary>
/// Internal data structure for referencing a SharedVariable to its name/scope.
/// </summary>
public struct VariableAssignment
{
[Tooltip("The name of the SharedVariable.")]
public string Name;
[Tooltip("The scope of the SharedVariable.")]
public SharedVariable.SharingScope Scope;
/// <summary>
/// VariableAssignment constructor.
/// </summary>
/// <param name="name">The name of the SharedVariable.</param>
/// <param name="scope">The scope of the SharedVariable.</param>
public VariableAssignment(string name, SharedVariable.SharingScope scope)
{
Name = name;
Scope = scope;
}
}
/// <summary>
/// Populates the SharedVariable Mapping at runtime.
/// </summary>
/// <param name="graph">The graph that is being deserialized.</param>
/// <param name="canDeepCopy">Can the SharedVariables be deep copied?</param>
/// <returns>A reference to the map between the VariableAssignment and SharedVariable.</returns>
public static Dictionary<VariableAssignment, SharedVariable> PopulateSharedVariablesMapping(IGraph graph, bool canDeepCopy)
{
var variableByNameMap = new Dictionary<VariableAssignment, SharedVariable>();
PopulateSharedVariablesMapping(graph, graph.SharedVariables, SharedVariable.SharingScope.Graph, canDeepCopy, ref variableByNameMap);
if (graph.Parent is GameObject parentGameObject) {
var gameObjectSharedVariablesContainer = parentGameObject.GetComponent<GameObjectSharedVariables>();
if (gameObjectSharedVariablesContainer != null) {
gameObjectSharedVariablesContainer.Deserialize(false);
PopulateSharedVariablesMapping(graph, gameObjectSharedVariablesContainer.SharedVariables, SharedVariable.SharingScope.GameObject, canDeepCopy, ref variableByNameMap);
}
}
var sceneSharedVariablesContainer = SceneSharedVariables.Instance;
if (sceneSharedVariablesContainer != null) {
sceneSharedVariablesContainer.Deserialize(false);
PopulateSharedVariablesMapping(graph, sceneSharedVariablesContainer.SharedVariables, SharedVariable.SharingScope.Scene, canDeepCopy, ref variableByNameMap);
}
var projectSharedVariablesContainer = ProjectSharedVariables.Instance;
if (projectSharedVariablesContainer != null) {
projectSharedVariablesContainer.Deserialize(false);
PopulateSharedVariablesMapping(graph, projectSharedVariablesContainer.SharedVariables, SharedVariable.SharingScope.Project, canDeepCopy, ref variableByNameMap);
}
return variableByNameMap;
}
/// <summary>
/// Populates the name variables mapping with the specified SharedVariables.
/// </summary>
/// <param name="graph">The graph that is being deserialized.</param>
/// <param name="sharedVariables">The SharedVariables that should be populated.</param>
/// <param name="scope">The scope of SharedVariables.</param>
/// <param name="canDeepCopy">Can the SharedVariables be deep copied?</param>
/// <param name="variableByNameMap">A reference to the map between the VariableAssignment and SharedVariable.</param>
private static void PopulateSharedVariablesMapping(IGraph graph, SharedVariable[] sharedVariables, SharedVariable.SharingScope scope, bool canDeepCopy, ref Dictionary<VariableAssignment, SharedVariable> variableByNameMap)
{
if (sharedVariables == null) {
return;
}
for (int i = 0; i < sharedVariables.Length; ++i) {
if (sharedVariables[i] == null) {
continue;
}
if (variableByNameMap.ContainsKey(new VariableAssignment(sharedVariables[i].Name, scope))) {
#if UNITY_EDITOR
Debug.LogWarning("Warning: Multiple SharedVariables with the same name have been added. Please email support@opsive.com with the steps to reproduce this warning. Thank you.");
#endif
continue;
}
var deepCopy = canDeepCopy && graph is Subtree && scope == SharedVariable.SharingScope.Graph; // Deep copy variables so the instance is not bound to the subtree.
variableByNameMap.Add(new VariableAssignment(sharedVariables[i].Name, scope), deepCopy ? CopyUtility.DeepCopy(sharedVariables[i]) as SharedVariable : sharedVariables[i]);
}
}
/// <summary>
/// When the behavior tree loads not all tasks will be deserialized instantly. TaskA may reference TaskB but TaskB hasn't
/// been deserialized yet. The TaskAssignment data structure will store all of the references that need to be restored after
/// the behavior tree has fully been deserialized.
/// </summary>
/// <param name="tasks">The tasks that belong to the graph.</param>
/// <param name="taskReferences">The tasks that should be referenced.</param>
public static void AssignTaskReferences(ILogicNode[] tasks, ResizableArray<TaskAssignment> taskReferences)
{
if (taskReferences == null) {
return;
}
for (int i = 0; i < taskReferences.Count; ++i) {
var taskReference = taskReferences[i];
var fieldType = taskReference.Field.FieldType;
object value = null;
// The field can be a list or single value.
if (typeof(IList).IsAssignableFrom(fieldType)) {
var elements = (IList)taskReferences[i].Value;
if (fieldType.IsArray) {
// The field type is an array. Create a new array with all of the task instances.
var array = Array.CreateInstance(Serializer.GetElementType(fieldType), elements.Count) as ILogicNode[];
for (int j = 0; j < array.Length; ++j) {
var index = (ushort)elements[j];
if (index < tasks.Length) {
array[j] = tasks[index];
}
}
value = array;
} else {
// The field type is a list. Create a new list with all of the task instances.
IList taskList;
if (fieldType.IsGenericType) {
taskList = Activator.CreateInstance(typeof(List<>).MakeGenericType(Serializer.GetElementType(fieldType))) as IList;
} else {
taskList = Activator.CreateInstance(fieldType) as IList;
}
for (int j = 0; j < elements.Count; ++j) {
var index = (ushort)elements[j];
if (index < tasks.Length) {
taskList.Add(tasks[index]);
}
}
value = taskList;
}
} else { // Single ILogicNode value.
var index = (ushort)taskReference.Value;
if (index < tasks.Length) {
value = tasks[index];
}
}
if (value != null) {
taskReference.Field.SetValue(taskReference.Target, value);
}
}
}
/// <summary>
/// Contains a reference to the subtree index and nodes.
/// </summary>
internal struct SubtreeNodesReference
{
[Tooltip("The ISubtreeReference.")]
public ISubtreeReference SubtreeReference;
[Tooltip("The index of the ISubtreeReference.")]
public ushort NodeIndex;
[Tooltip("The total number of nodes contained within the ISubtreeReference.")]
public ushort NodeCount;
[Tooltip("A reference to the subtrees that are loaded.")]
public Subtree[] Subtrees;
[Tooltip("The deserialized nodes.")]
public ILogicNode[][] Nodes;
}
/// <summary>
/// Data structure which contains the properties for a subtree that will be injected.
/// </summary>
private struct SubtreeAssignment
{
[Tooltip("The index of the SubtreeNodesReference element.")]
public int ReferenceIndex;
[Tooltip("The index of the ISubtreeReference task.")]
public ushort NodeIndex;
[Tooltip("The index of the Subtree.")]
public int SubtreeIndex;
[Tooltip("The subtree that the task references.")]
public Subtree Subtree;
[Tooltip("The offset of the index. This will change as subtrees are added.")]
public ushort IndexOffset;
[Tooltip("The original parent index of the ISubtreeReference task.")]
public ushort ParentIndex;
[Tooltip("The original sibling index of the ISubtreeReference task.")]
public ushort SiblingIndex;
[Tooltip("The number of nodes that are a child of the ISubtreeReference.")]
public ushort NodeCount;
#if UNITY_EDITOR
[Tooltip("The position of the ISubtreeReference task.")]
public Vector2 NodePropertiesPosition;
[Tooltip("Is the ISubtreeReference task collapsed?")]
public bool Collapsed;
#endif
}
/// <summary>
/// Injects the subtree into the task list.
/// </summary>
private void InjectSubtrees()
{
if (m_SubtreeNodesReference == null || m_SubtreeNodesReference.Count == 0) {
return;
}
// The behavior tree must generate a new ID when subtrees are injected.
m_RuntimeUniqueID = Guid.NewGuid().GetHashCode();
var taskCount = 0;
var subtreeReferenceCount = 0;
var subtreeAssignments = new ResizableArray<SubtreeAssignment>();
var lastParentIndex = m_Tasks[m_SubtreeNodesReference[0].NodeIndex].ParentIndex;
var parentIndexOffset = 0;
for (int i = 0; i < m_SubtreeNodesReference.Count; ++i) {
var subtreeReference = m_Tasks[m_SubtreeNodesReference[i].NodeIndex] as ISubtreeReference;
var subtrees = subtreeReference.Subtrees;
if (subtrees != null) {
var indexOffset = (ushort)0; // The index offset is relative to each individual ISubtreeReference task.
// The parent index will change based on the number of tasks that have been added.
var parentIndex = m_Tasks[m_SubtreeNodesReference[i].NodeIndex].ParentIndex;
if (parentIndex != ushort.MaxValue && (parentIndex > lastParentIndex || (i > 0 && lastParentIndex == ushort.MaxValue))) {
parentIndexOffset = (ushort)(taskCount - subtreeReferenceCount);
lastParentIndex = parentIndex;
}
// Calculate the parent index offset based on previously injected subtrees
for (int j = 0; j < subtrees.Length; ++j) {
if (subtrees[j] == null || subtrees[j].LogicNodes == null || subtrees[j].EventNodes == null) {
continue;
}
// The subtree should start from the start node.
var startNode = subtrees[j].GetEventNode(typeof(Start)); // Returns (IEventNode, index).
if (startNode.Item1 == null || startNode.Item1.ConnectedIndex == ushort.MaxValue || !subtrees[j].IsNodeEnabled(false, startNode.Item2)) {
continue;
}
var firstNode = m_SubtreeNodesReference[i].Nodes[j][startNode.Item1.ConnectedIndex];
var subtreeNodeCount = GetChildCount(firstNode, m_SubtreeNodesReference[i].Nodes[j]) + 1; // firstNode should be included in addition to the children.
taskCount += subtreeNodeCount;
subtreeAssignments.Add(new SubtreeAssignment()
{
ReferenceIndex = i,
NodeIndex = m_SubtreeNodesReference[i].NodeIndex,
SubtreeIndex = j,
Subtree = subtrees[j],
NodeCount = (ushort)subtreeNodeCount,
IndexOffset = indexOffset,
ParentIndex = (ushort)(parentIndex + parentIndexOffset),
SiblingIndex = m_Tasks[m_SubtreeNodesReference[i].NodeIndex].SiblingIndex,
#if UNITY_EDITOR
NodePropertiesPosition = m_LogicNodeProperties[m_SubtreeNodesReference[i].NodeIndex].Position,
Collapsed = m_LogicNodeProperties[m_SubtreeNodesReference[i].NodeIndex].Collapsed
#endif
});
indexOffset += (ushort)subtreeNodeCount;
}
// Update the parent index offset for the next subtree reference
if (indexOffset > 0) { // Subtree References may not contain any valid subtrees.
subtreeReferenceCount++;
}
var subtreeNodesReferenceOrig = m_SubtreeNodesReference[i];
subtreeNodesReferenceOrig.NodeCount = indexOffset;
m_SubtreeNodesReference[i] = subtreeNodesReferenceOrig;
}
}
if (taskCount == 0) {
return;
}
var targetCount = m_Tasks.Length + taskCount - subtreeReferenceCount;
var originalTaskCount = m_Tasks.Length;
if (m_Tasks.Length != targetCount) {
Array.Resize(ref m_Tasks, targetCount);
#if UNITY_EDITOR
Array.Resize(ref m_LogicNodeProperties, targetCount);
#endif
}
// Make space for all of the subtree tasks.
var addedTasks = 0;
for (int i = 0; i < subtreeAssignments.Count; ++i) {
var subtreeIndex = (ushort)(subtreeAssignments[i].NodeIndex + addedTasks);
var subtreeTaskCount = subtreeAssignments[i].NodeCount - (subtreeAssignments[i].IndexOffset == 0 ? 1 : 0);
if (subtreeTaskCount > 0) { // subtreeTaskCount will be zero if a single task replaces the reference task.
for (int j = originalTaskCount - 1 + addedTasks; j > subtreeIndex; --j) {
var node = m_Tasks[j];
node.Index += (ushort)subtreeTaskCount;
if (node.ParentIndex > subtreeIndex && node.ParentIndex != ushort.MaxValue) {
node.ParentIndex += (ushort)subtreeTaskCount;
}
if (node.SiblingIndex > subtreeIndex && node.SiblingIndex != ushort.MaxValue) {
node.SiblingIndex += (ushort)subtreeTaskCount;
}
m_Tasks[j + subtreeTaskCount] = node;
m_Tasks[j] = null;
#if UNITY_EDITOR
m_LogicNodeProperties[j + subtreeTaskCount] = m_LogicNodeProperties[j];
#endif
}
// The parents need to adjust their sibling index offsets for the newly added nodes. This should only be done with an index offset of 0
// as grouped subtrees have the same parents.
if (subtreeAssignments[i].IndexOffset == 0) {
var parentIndex = m_Tasks[subtreeIndex].ParentIndex;
while (parentIndex != ushort.MaxValue) {
var parentNode = m_Tasks[parentIndex];
if (parentNode.SiblingIndex != ushort.MaxValue) {
parentNode.SiblingIndex += (ushort)subtreeTaskCount;
m_Tasks[parentIndex] = parentNode;
}
parentIndex = parentNode.ParentIndex;
}
}
// Any disabled nodes after the insertion needs to shift.
var lastDisabledNodeIndex = 0;
if (m_DisabledLogicNodes != null) {
for (int j = 0; j < m_DisabledLogicNodes.Length; ++j) {
if (m_DisabledLogicNodes[j] > subtreeIndex) {
m_DisabledLogicNodes[j] += (ushort)subtreeTaskCount;
} else { // Remember the last index that was greater than the subtree index so any disabled subtree nodes can be inserted.
lastDisabledNodeIndex = j + 1;
}
}
}
// If the parent reference task is disabled then all subtree nodes should be disabled.
var subtreeDisabledLogicNodes = subtreeAssignments[i].Subtree.DisabledLogicNodes;
if (!IsNodeEnabled(true, m_SubtreeNodesReference[subtreeAssignments[i].ReferenceIndex].NodeIndex)) {
subtreeDisabledLogicNodes = new ushort[subtreeAssignments[i].NodeCount];
for (ushort j = 0; j < subtreeDisabledLogicNodes.Length; ++j) {
subtreeDisabledLogicNodes[j] = j;
}
}
// The subtree may have disabled tasks.
if (subtreeDisabledLogicNodes != null && subtreeDisabledLogicNodes.Length > 0) {
var subtreeDisabledLength = subtreeDisabledLogicNodes.Length;
// Ensure all of the disabled logic nodes have been transferred.
for (int j = subtreeDisabledLength - 1; j >= 0; --j) {
if (subtreeDisabledLogicNodes[j] > subtreeTaskCount) {
subtreeDisabledLength--;
}
}
if (subtreeDisabledLength > 0) {
if (m_DisabledLogicNodes == null) {
m_DisabledLogicNodes = new ushort[subtreeDisabledLength];
} else {
Array.Resize(ref m_DisabledLogicNodes, m_DisabledLogicNodes.Length + subtreeDisabledLength);
}
var originalLength = m_DisabledLogicNodes.Length - subtreeDisabledLength;
for (int j = lastDisabledNodeIndex; j < originalLength; ++j) {
m_DisabledLogicNodes[j + subtreeDisabledLength] = m_DisabledLogicNodes[j];
}
for (int j = 0; j < subtreeDisabledLength; ++j) {
if (subtreeDisabledLogicNodes[j] > subtreeTaskCount) {
continue;
}
m_DisabledLogicNodes[lastDisabledNodeIndex + j] = (ushort)(subtreeIndex + subtreeDisabledLogicNodes[j]);
}
}
}
}
// Tasks were added to the tree. Update the tree to the correct indicies.
var subtreeAssignment = subtreeAssignments[i];
subtreeAssignment.IndexOffset = (ushort)(addedTasks + (subtreeAssignments[i].IndexOffset == 0 ? 0 : 1));
subtreeAssignments[i] = subtreeAssignment;
addedTasks += subtreeTaskCount;
}
// Populate the tasks with the subtree.
for (int i = 0; i < subtreeAssignments.Count; ++i) {
var subtreeIndex = (ushort)(subtreeAssignments[i].NodeIndex + subtreeAssignments[i].IndexOffset);
var subtreeParentIndex = subtreeAssignments[i].ParentIndex;
#if UNITY_EDITOR
var positionOffset = Vector2.zero;
#endif
for (int j = 0; j < subtreeAssignments[i].NodeCount; ++j) {
var origNode = m_SubtreeNodesReference[subtreeAssignments[i].ReferenceIndex].Nodes[subtreeAssignments[i].SubtreeIndex][j];
// The node needs to be copied to prevent the same node from being used in multiple trees.
var node = CopyUtility.DeepCopy(m_SubtreeNodesReference[subtreeAssignments[i].ReferenceIndex].Nodes[subtreeAssignments[i].SubtreeIndex][j]) as ILogicNode;
node.Index = (ushort)(subtreeIndex + j);
node.RuntimeIndex = ushort.MaxValue;
if (j == 0) {
node.ParentIndex = subtreeParentIndex;
subtreeParentIndex = node.Index; // The subsequent subtree tasks should use the first subtree task as the parent reference.
node.SiblingIndex = subtreeAssignments[i].SiblingIndex != ushort.MaxValue ? (ushort)(subtreeIndex + subtreeAssignments[i].NodeCount) : ushort.MaxValue;
} else {
// Adjust the subsequent subtree tasks by the location of the insertion.
node.ParentIndex += subtreeParentIndex;
if (node.SiblingIndex != ushort.MaxValue) {
node.SiblingIndex += subtreeIndex;
}
}
m_Tasks[subtreeIndex + j] = node;
#if UNITY_EDITOR
var nodeProperties = CopyUtility.DeepCopy(subtreeAssignments[i].Subtree.LogicNodeProperties[j]) as LogicNodeProperties;
nodeProperties.GuidString = Guid.NewGuid().ToString();
if (j == 0) {
// Keep the tasks in the same relative position as the subtree reference.
positionOffset = subtreeAssignments[i].NodePropertiesPosition - subtreeAssignments[i].Subtree.LogicNodeProperties[j].Position;
} else {
// Apply a small offset for stacked subtrees so they are not directly overlapping.
positionOffset += new Vector2(2, 2);
}
nodeProperties.Position += positionOffset;
nodeProperties.Collapsed = subtreeAssignments[i].Collapsed;
m_LogicNodeProperties[subtreeIndex + j] = nodeProperties;
#endif
}
}
}
/// <summary>
/// Returns the Node of the specified type.
/// </summary>
/// <param name="type">The type of Node that should be retrieved.</typeparam>
/// <returns>The Node of the specified type (can be null).</returns>
public ILogicNode GetNode(Type type)
{
if (m_Tasks == null) {
return null;
}
for (int i = 0; i < m_Tasks.Length; ++i) {
if (m_Tasks[i].GetType() == type) {
return m_Tasks[i];
}
}
return null;
}
/// <summary>
/// Returns the event node of the specified type.
/// </summary>
/// <param name="type">The type of EventNode that should be retrieved.</typeparam>
/// <returns>The EventNode of the specified type (can be null). If the node is found the index will also be returned.</returns>
public (IEventNode, ushort) GetEventNode(Type type)
{
if (m_EventTasks == null) {
return (null, ushort.MaxValue);
}
for (ushort i = 0; i < m_EventTasks.Length; ++i) {
if (m_EventTasks[i].GetType() == type) {
return (m_EventTasks[i], i);
}
}
return (null, ushort.MaxValue);
}
/// <summary>
/// Returns the total number of children belonging to the specified node.
/// </summary>
/// <param name="node">The node to retrieve the child count of.</param>
/// <param name="nodes">All of the nodes that belong to the graph.</param>
/// <returns>The total number of children belonging to the specified node.</returns>
public int GetChildCount(ILogicNode node, ILogicNode[] nodes)
{
if (node.SiblingIndex != ushort.MaxValue) {
return node.SiblingIndex - node.Index - 1;
}
if (node.Index + 1 == nodes.Length) {
return 0;
}
var child = nodes[node.Index + 1];
if (child.ParentIndex != node.Index) {
return 0;
}
// Determine the child count based off of the sibling index.
while (child.SiblingIndex != ushort.MaxValue) {
child = nodes[child.SiblingIndex];
}
return child.Index - node.Index + GetChildCount(child, nodes);
}
/// <summary>
/// Reevaluates the SubtreeReferences by calling the EvaluateSubtrees method.
/// </summary>
/// <param name="graphComponent">The component that the graph is being deserialized from.</param>
/// <param name="graph">The graph that is being reevaluated.</param>
/// <param name="onBeforeReevaluationSwap">Action that should be done before the tasks are swapped.</param>
/// <returns>True if the subtree was reevaluated.</returns>
public bool ReevaluateSubtreeReferences(IGraphComponent graphComponent, IGraph graph, Action onBeforeReevaluationSwap)
{
// The tree must contain tasks.
if (!Application.isPlaying || m_Tasks == null || m_Tasks.Length == 0) {
return false;
}
// Subtree references must exist.
if (m_SubtreeNodesReference == null || m_SubtreeNodesReference.Count == 0) {
return false;
}
if (onBeforeReevaluationSwap != null) {
onBeforeReevaluationSwap();
}
// Find the new reevaluated nodes.
for (int i = m_SubtreeNodesReference.Count - 1; i >= 0; --i) {
var subtreeNodesReference = m_SubtreeNodesReference[i];
var subtreeReference = m_SubtreeNodesReference[i].SubtreeReference;
subtreeReference.EvaluateSubtrees(graphComponent);
var reevaluatedSubtrees = subtreeReference.Subtrees;
ILogicNode[][] reevaluatedNodes;
if (reevaluatedSubtrees == null) {
continue;
}
// The parent must be able to accept the number of subtrees that there are.
var parentIndex = m_Tasks[m_SubtreeNodesReference[i].NodeIndex].ParentIndex;
IParentNode parentNode = null;
if (parentIndex != ushort.MaxValue) {
parentNode = m_Tasks[parentIndex] as IParentNode;
}
if ((parentNode == null && reevaluatedSubtrees.Length > 1) || (parentNode != null && reevaluatedSubtrees.Length > parentNode.MaxChildCount)) {
Debug.LogError($"Error: the reevaluated graph contains multiple subtrees as the starting task or as a child of a parent task which cannot contain so many children (such as a decorator).");
continue;
}
reevaluatedNodes = new ILogicNode[reevaluatedSubtrees.Length][];
var errorState = false;
for (int j = 0; j < reevaluatedSubtrees.Length; ++j) {
if (reevaluatedSubtrees[j] == null) {
continue;
}
if (!reevaluatedSubtrees[j].Deserialize(graphComponent, true, true, true, subtreeReference.SharedVariableOverrides)) {
errorState = true;
break;
};
// Keep a reference to the deserialized nodes. This will ensure they are unique and do not get overwritten.
reevaluatedNodes[j] = reevaluatedSubtrees[j].LogicNodes;
}
if (errorState) {
continue;
}
// The subtree index will be offsetted from the original index value if there are multiple subtree references.
var nodeOffset = 0;
for (int j = i - 1; j >= 0; --j) {
nodeOffset += m_SubtreeNodesReference[j].NodeCount - 1;
}
// All of the reevaluated nodes have been determined. Remove the old subtree nodes.
var nodeCount = m_SubtreeNodesReference[i].NodeCount;
// Replace the first node with the subtree reference, and remove the rest of the added nodes.
m_Tasks[m_SubtreeNodesReference[i].NodeIndex + nodeOffset] = m_SubtreeNodesReference[i].SubtreeReference as ILogicNode;
for (int j = m_SubtreeNodesReference[i].NodeIndex + nodeOffset + 1; j < m_Tasks.Length - nodeCount + 1; ++j) {
m_Tasks[j] = m_Tasks[j + nodeCount - 1];
m_Tasks[j].Index = (ushort)j;
if (m_Tasks[j].ParentIndex != ushort.MaxValue && m_Tasks[j].ParentIndex > m_SubtreeNodesReference[i].NodeIndex + nodeOffset) {
m_Tasks[j].ParentIndex -= (ushort)(nodeCount - 1);
}
if (m_Tasks[j].SiblingIndex != ushort.MaxValue && m_Tasks[j].SiblingIndex > m_SubtreeNodesReference[i].NodeIndex + nodeOffset) {
m_Tasks[j].SiblingIndex -= (ushort)(nodeCount - 1);
}
#if UNITY_EDITOR
m_LogicNodeProperties[j] = m_LogicNodeProperties[j + nodeCount - 1];
#endif
}
// Restore the original ConnectedIndex value.
if (m_EventTasks != null) {
for (int j = 0; j < m_EventTasks.Length; ++j) {
if (m_EventTasks[j].ConnectedIndex > m_SubtreeNodesReference[i].NodeIndex) {
m_EventTasks[j].ConnectedIndex -= (ushort)(nodeCount - 1);
}
}
}
Array.Resize(ref m_Tasks, m_Tasks.Length - nodeCount + 1);
#if UNITY_EDITOR
Array.Resize(ref m_LogicNodeProperties, m_LogicNodeProperties.Length - nodeCount + 1);
#endif
// Replace the old nodes with the new nodes.
subtreeNodesReference.Nodes = reevaluatedNodes;
m_SubtreeNodesReference[i] = subtreeNodesReference;
// The disabled nodes also need to be removed.
var subtrees = m_SubtreeNodesReference[i].Subtrees;
var disabledNodesCount = 0;
for (int j = 0; j < subtrees.Length; ++j) {
if (subtrees[j].DisabledLogicNodes == null || subtrees[j].DisabledLogicNodes.Length == 0) {
continue;
}
disabledNodesCount += subtrees[j].DisabledLogicNodes.Length;
}
if (disabledNodesCount > 0) {
if (m_DisabledLogicNodes.Length > disabledNodesCount) { // The local tree may not have any disabled nodes.
for (int j = 0; j < m_DisabledLogicNodes.Length - disabledNodesCount; ++j) {
if (m_DisabledLogicNodes[j] >= m_SubtreeNodesReference[i].NodeIndex + nodeOffset + 1) {
m_DisabledLogicNodes[j] = (ushort)(m_DisabledLogicNodes[j + disabledNodesCount] - nodeCount + 1);
}
}
}
Array.Resize(ref m_DisabledLogicNodes, m_DisabledLogicNodes.Length - disabledNodesCount);
}
}
// The tasks array has been restored to the original set of nodes with the ISubtreeReference. Inject the new nodes.
InjectSubtrees();
// Modify the ConnectedIndex to match the injection.
if (m_EventTasks != null && m_SubtreeNodesReference != null) {
for (int i = 0; i < m_EventTasks.Length; ++i) {
var offset = 0;
for (int j = 0; j < m_SubtreeNodesReference.Count; ++j) {
if (m_SubtreeNodesReference[j].NodeIndex >= m_EventTasks[i].ConnectedIndex) {
break;
}
offset += m_SubtreeNodesReference[j].NodeCount - 1;
}
if (offset != 0) {
m_EventTasks[i].ConnectedIndex += (ushort)offset;
}
}
}
return true;
}
/// <summary>
/// Returns the SharedVariable with the specified name.
/// </summary>
/// <param name="graph">The graph that the data belongs to.</typeparam>
/// <param name="name">The name of the SharedVariable that should be retrieved.</typeparam>
/// <param name="scope">The scope of the SharedVariable that should be retrieved.</param>
/// <returns>The SharedVariable with the specified name (can be null).</returns>
public SharedVariable GetVariable(IGraph graph, string name, SharedVariable.SharingScope scope)
{
if (string.IsNullOrEmpty(name)) {
return null;
}
if (m_VariableByNameMap == null) {
DeserializeSharedVariables(false, null);
m_VariableByNameMap = PopulateSharedVariablesMapping(graph, true);
}
if (m_VariableByNameMap != null && m_VariableByNameMap.TryGetValue(new VariableAssignment(name, scope), out var variable)) {
return variable;
}
return null;
}
/// <summary>
/// Returns the SharedVariable of the specified type.
/// </summary>
/// <param name="graph">The graph that the data belongs to.</typeparam>
/// <param name="name">The name of the SharedVariable that should be retrieved.</typeparam>
/// <param name="scope">The scope of the SharedVariable that should be retrieved.</param>
/// <returns>The SharedVariable with the specified name (can be null).</returns>
public SharedVariable<T> GetVariable<T>(IGraph graph, string name, SharedVariable.SharingScope scope)
{
if (string.IsNullOrEmpty(name)) {
return null;
}
if (m_VariableByNameMap == null) {
DeserializeSharedVariables(false, null);
m_VariableByNameMap = PopulateSharedVariablesMapping(graph, true);
}
if (m_VariableByNameMap != null && m_VariableByNameMap.TryGetValue(new VariableAssignment(name, scope), out var variable)) {
return variable as SharedVariable<T>;
}
return null;
}
/// <summary>
/// Sets the value of the SharedVariable.
/// </summary>
/// <typeparam name="T">The type of SharedVarible.</typeparam>
/// <param name="graph">The graph that the data belongs to.</typeparam>
/// <param name="name">The name of the SharedVariable.</param>
/// <param name="value">The value of the SharedVariable.</param>
public void SetVariableValue<T>(IGraph graph, string name, T value, SharedVariable.SharingScope scope)
{
if (string.IsNullOrEmpty(name)) {
return;
}
if (m_VariableByNameMap == null) {
DeserializeSharedVariables(false, null);
m_VariableByNameMap = PopulateSharedVariablesMapping(graph, true);
}
if (m_VariableByNameMap == null || !m_VariableByNameMap.TryGetValue(new VariableAssignment(name, scope), out var variable)) {
return;
}
(variable as SharedVariable<T>).Value = value;
}
/// <summary>
/// Overrides the SharedVariable value. The name must match an exsting variable.
/// </summary>
/// <param name="graph">The graph that the data belongs to.</typeparam>
/// <param name="variable">The reference to the SharedVariable.</param>
internal void OverrideVariableValue(IGraph graph, SharedVariable variable)
{
if (string.IsNullOrEmpty(variable.Name)) {
return;
}
DeserializeSharedVariables(false, null);
var dirty = false;
if (m_SharedVariables != null) {
for (int i = 0; i < m_SharedVariables.Length; ++i) {
if (m_SharedVariables[i].Name == variable.Name && m_SharedVariables[i].GetType() == variable.GetType()) {
m_SharedVariables[i].SetValue(variable.GetValue());
dirty = true;
break;
}
}
}
if (dirty) {
m_VariableByNameMap = PopulateSharedVariablesMapping(graph, true);
}
}
/// <summary>
/// Is the node with the specified index enabled?
/// </summary>
/// <param name="logicNode">Is the node a LogicNode?</param>
/// <param name="index">The index of the node.</param>
/// <returns>True if the node with the specified index is enabled.</returns>
public bool IsNodeEnabled(bool logicNode, int index)
{
if (index == ushort.MaxValue) {
return true;
}
var disabledNodes = logicNode ? m_DisabledLogicNodes : m_DisabledEventNodes;
if (disabledNodes == null) {
return true;
}
for (int i = 0; i < disabledNodes.Length; ++i) {
if (disabledNodes[i] == index) {
return false;
}
}
return true;
}
}
}
#endif