Files
2026-05-10 11:47:55 -04:00

2262 lines
120 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 partial 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 ITreeLogicNode[] 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;
[System.NonSerialized] private ResizableArray<InjectedGraphReference> m_InjectedGraphReferences;
[System.NonSerialized] private HashSet<IEventNode> m_InjectedSubtreeEventNodes;
public ITreeLogicNode[] LogicNodes
{
get => m_Tasks;
set {
if (value == null) {
m_Tasks = null;
} else {
if (m_Tasks == null) {
m_Tasks = new ITreeLogicNode[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_UniqueID; }
public int RuntimeUniqueID { get => m_RuntimeUniqueID; internal set => m_RuntimeUniqueID = value; }
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; }
internal ResizableArray<InjectedGraphReference> InjectedGraphReferences { get => m_InjectedGraphReferences; }
#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;
[Tooltip("The serialized shared variables group data.")]
[SerializeField] private Serialization[] m_SharedVariableGroupsData;
private LogicNodeProperties[] m_LogicNodeProperties;
private NodeProperties[] m_EventNodeProperties;
private GroupProperties[] m_GroupProperties;
[System.NonSerialized] private SharedVariableGroup[] m_SharedVariableGroups;
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; }
public SharedVariableGroup[] SharedVariableGroups { get => m_SharedVariableGroups; set => m_SharedVariableGroups = value; }
#endif
private ResizableArray<InjectedSubtreeReference> m_InjectedSubtreeReference;
private ResizableArray<VariableField> m_VariableFields;
[System.NonSerialized] private bool m_Deserializing;
internal ResizableArray<InjectedSubtreeReference> InjectedSubtreeReferences { get => m_InjectedSubtreeReference; set => m_InjectedSubtreeReference = 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(ITreeLogicNode node)
{
if (m_Tasks == null) {
m_Tasks = new ITreeLogicNode[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(ITreeLogicNode node)
{
if (m_Tasks == null || node.Index >= m_Tasks.Length) {
return false;
}
var dest = new ITreeLogicNode[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);
}
eventNode.Index = (ushort)(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()
{
if (Application.isPlaying) {
return;
}
m_TaskData = Serialization.Serialize<ITreeLogicNode>(m_Tasks, ValidateSerializedObject);
m_EventTaskData = Serialization.Serialize<IEventNode>(m_EventTasks, ValidateSerializedObject);
SerializeSharedVariables();
// Disabled array removed in version 3.0.
m_DisabledLogicNodesData = null;
m_DisabledEventNodesData = null;
m_UniqueID = Guid.NewGuid().GetHashCode();
#if UNITY_EDITOR
// Ensure the node data is up to date.
if (m_LogicNodeProperties != null && m_Tasks != null && m_LogicNodeProperties.Length <= m_Tasks.Length) {
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()
{
if (Application.isPlaying) {
return;
}
m_SharedVariableData = Serialization.Serialize<SharedVariable>(m_SharedVariables);
#if UNITY_EDITOR
m_SharedVariableGroupsData = Serialization.Serialize<SharedVariableGroup>(m_SharedVariableGroups);
#endif
// 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) {
if (m_SharedVariables[i] == null) {
continue;
}
m_VariableByNameMap.Add(new VariableAssignment(m_SharedVariables[i].Name, m_SharedVariables[i].Scope), m_SharedVariables[i]);
}
}
}
/// <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="forceSharedVariables">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 forceSharedVariables, bool injectSubtrees, bool canDeepCopyVariables = true, SharedVariableOverride[] sharedVariableOverrides = null)
{
// No need to deserialize if the data is already deserialized.
if (!force && ((m_Tasks != null && m_TaskData != null) || (m_EventTasks != null && m_EventTaskData != null))) {
// SharedVariables may still need to be deserialized separately.
DeserializeSharedVariables(graph, false, canDeepCopyVariables, sharedVariableOverrides);
if (Application.isPlaying && m_RuntimeUniqueID == 0) {
m_RuntimeUniqueID = m_UniqueID;
}
return true;
}
var deserialized = DeserializeInternal(graphComponent, graph, force, forceSharedVariables, injectSubtrees, canDeepCopyVariables, sharedVariableOverrides);
#if UNITY_EDITOR
if (deserialized) {
UpdateInjectedGraphReferences();
} else {
m_InjectedGraphReferences = null;
}
#endif
return deserialized;
}
/// <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="forceSharedVariables">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>
private bool DeserializeInternal(IGraphComponent graphComponent, IGraph graph, bool force, bool forceSharedVariables, bool injectSubtrees, bool canDeepCopyVariables, SharedVariableOverride[] sharedVariableOverrides = null)
{
// Prevent the tree from being deserialized recursively.
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;
m_VariableFields = null;
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(graph, forceSharedVariables, canDeepCopyVariables, sharedVariableOverrides);
// 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_InjectedSubtreeReference != null) {
m_InjectedSubtreeReference.Clear();
}
if (m_TaskData != null && m_TaskData.Length > 0) {
m_Tasks = new ITreeLogicNode[m_TaskData.Length];
for (int i = 0; i < m_TaskData.Length; ++i) {
try {
var task = m_TaskData[i].DeserializeFields(MemberVisibility.Public, ValidateDeserializedTypeObject, (object fieldInfoObj, object task, object value) =>
{
var validatedValue = ValidateDeserializedObject(fieldInfoObj, task, value, ref m_VariableByNameMap, ref taskReferences, sharedVariableOverrides);
if (validatedValue != null && validatedValue is SharedVariable sharedVariable && sharedVariable.Scope == SharedVariable.SharingScope.Graph) {
if (m_VariableFields == null) { m_VariableFields = new ResizableArray<VariableField>(); }
m_VariableFields.Add(new VariableField() { Field = fieldInfoObj as FieldInfo, Task = task, Name = sharedVariable.Name });
}
return validatedValue;
}) as ILogicNode;
if (task is ITreeLogicNode treeLogicNode) {
m_Tasks[i] = treeLogicNode;
} else if (task is ILogicNode) {
Debug.LogError($"Error: The task {m_TaskData[i].ObjectType} at index {i} must implement ITreeLogicNode.");
}
} 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) {
// Check if the type has moved using the MovedFrom attribute.
var taskType = TypeUtility.GetType(m_TaskData[i].ObjectType);
if (taskType != null) {
// The type was found (possibly via MovedFrom), try to deserialize again.
try {
m_TaskData[i].ObjectType = taskType.FullName;
var task = m_TaskData[i].DeserializeFields(MemberVisibility.Public, ValidateDeserializedTypeObject, (object fieldInfoObj, object taskObj, object value) =>
{
var validatedValue = ValidateDeserializedObject(fieldInfoObj, taskObj, value, ref m_VariableByNameMap, ref taskReferences, sharedVariableOverrides);
if (validatedValue != null && validatedValue is SharedVariable sharedVariable && sharedVariable.Scope == SharedVariable.SharingScope.Graph) {
if (m_VariableFields == null) { m_VariableFields = new ResizableArray<VariableField>(); }
m_VariableFields.Add(new VariableField() { Field = fieldInfoObj as FieldInfo, Task = taskObj, Name = sharedVariable.Name });
}
return validatedValue;
}) as ILogicNode;
if (task is ITreeLogicNode treeLogicNode) {
m_Tasks[i] = treeLogicNode;
} else if (task is ILogicNode) {
Debug.LogError($"Error: The task {m_TaskData[i].ObjectType} at index {i} must implement ITreeLogicNode.");
}
} catch (Exception e) {
Debug.LogError($"Error: Unable to load task {m_TaskData[i].ObjectType} at index {i} after MovedFrom resolution due to exception:\n{e}");
}
}
// If still null, create an unknown task.
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;
m_Tasks[i].ParentIndex = ushort.MaxValue;
m_Tasks[i].SiblingIndex = ushort.MaxValue;
#endif
Debug.LogError($"Error: Unable to deserialize task of type {m_TaskData[i].ObjectType}. Use the [MovedFrom] attribute for refactoring.");
}
}
// 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
// Migrate from the deprecated disabled array to the Enabled property.
if (m_DisabledLogicNodes != null && m_DisabledLogicNodes.Length > 0) {
for (int j = 0; j < m_DisabledLogicNodes.Length; ++j) {
if (m_DisabledLogicNodes[j] == i) {
m_Tasks[i].Enabled = false;
break;
}
}
}
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 ISubtreeReferenceNode subtreeReference) {
// Subtrees can be nested.
subtreeReference.EvaluateSubgraphs(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 ITreeLogicNode[subtrees.Length][];
for (int j = 0; j < subtrees.Length; ++j) {
if (subtrees[j] == null) {
continue;
}
if (!subtrees[j].Deserialize(graphComponent, force && !subtrees[j].Pooled, forceSharedVariables && !subtrees[j].Pooled, true, true, subtreeReference.SharedVariableOverrides)) {
errorState = true;
break;
};
if (subtrees[j].Data.m_VariableFields != null && subtrees[j].Data.m_VariableFields.Count > 0) {
if (m_VariableFields == null) { m_VariableFields = new ResizableArray<VariableField>(); ; }
m_VariableFields.AddRange(subtrees[j].Data.m_VariableFields);
}
// Keep a reference to the deserialized nodes. This will ensure they are unique and do not get overwritten.
deserializedNodes[j] = subtrees[j].TreeLogicNodes;
// 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_InjectedSubtreeReference == null) { m_InjectedSubtreeReference = new ResizableArray<InjectedSubtreeReference>(); }
m_InjectedSubtreeReference.Add(new InjectedSubtreeReference()
{
GraphReference = subtreeReference,
NodeIndex = (ushort)i,
Subtrees = subtrees,
Nodes = deserializedNodes,
#if UNITY_EDITOR
GraphReferenceNodeProperties = m_LogicNodeProperties[i],
#endif
});
}
}
}
}
}
// Migrate from the deprecated disabled array to the Enabled property.
m_DisabledLogicNodes = null;
} else {
m_Tasks = null;
}
// Add the event tasks before the subtrees are injected. Connected indices will be adjusted after injection.
var baseEventTaskCount = 0;
if (m_EventTaskData != null && m_EventTaskData.Length > 0) {
m_EventTasks = new IEventNode[m_EventTaskData.Length];
for (int i = 0; i < m_EventTaskData.Length; ++i) {
try {
var eventTaskObj = m_EventTaskData[i].DeserializeFields(MemberVisibility.Public, ValidateDeserializedTypeObject, (object fieldInfoObj, object task, object value) =>
{
var validatedValue = ValidateDeserializedObject(fieldInfoObj, task, value, ref m_VariableByNameMap, ref taskReferences, sharedVariableOverrides);
if (validatedValue != null && validatedValue is SharedVariable sharedVariable && sharedVariable.Scope == SharedVariable.SharingScope.Graph) {
if (m_VariableFields == null) { m_VariableFields = new ResizableArray<VariableField>(); }
m_VariableFields.Add(new VariableField() { Field = fieldInfoObj as FieldInfo, Task = task, Name = sharedVariable.Name });
}
return validatedValue;
});
if (eventTaskObj is IEventNode eventNode) {
m_EventTasks[i] = eventNode;
} else if (eventTaskObj != null) {
Debug.LogError($"Error: The event task {m_EventTaskData[i].ObjectType} at index {i} must implement IEventNode.");
}
} catch (Exception e) {
Debug.LogError($"Error: Unable to load event task {m_EventTaskData[i].ObjectType} at index {i} due to exception:\n{e}");
}
if (m_EventTasks[i] == null) {
m_EventTasks[i] = new UnknownEventTask(m_EventTaskData[i].ObjectType);
Debug.LogError($"Error: Unable to deserialize event of type {m_EventTaskData[i].ObjectType}.");
}
m_EventTasks[i].Index = (ushort)i;
// Migrate from the deprecated disabled array to the Enabled property.
if (m_DisabledEventNodes != null && m_DisabledEventNodes.Length > 0) {
for (int j = 0; j < m_DisabledEventNodes.Length; ++j) {
if (m_DisabledEventNodes[j] == i) {
m_EventTasks[i].Enabled = false;
break;
}
}
}
}
// Migrate from the deprecated disabled array to the Enabled property.
m_DisabledEventNodes = null;
} else {
m_EventTasks = null;
}
baseEventTaskCount = m_EventTasks != null ? m_EventTasks.Length : 0;
// Subtrees should be injected into the tree.
InjectSubtrees();
// Modify the ConnectedIndex to match the injection for the base event tasks.
if (m_EventTasks != null && m_InjectedSubtreeReference != null && baseEventTaskCount > 0) {
for (int i = 0; i < baseEventTaskCount; ++i) {
// 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_InjectedSubtreeReference.Count; ++j) {
if (m_InjectedSubtreeReference[j].NodeIndex >= m_EventTasks[i].ConnectedIndex) {
break;
}
offset += m_InjectedSubtreeReference[j].NodeCount > 0 ? m_InjectedSubtreeReference[j].NodeCount - 1 : 0;
}
if (offset > 0) {
m_EventTasks[i].ConnectedIndex += (ushort)offset;
}
}
}
// 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);
}
var formerlySerializedType = field?.GetCustomAttribute<FormerlySerializedTypeAttribute>(false);
if (formerlySerializedType != null) {
return formerlySerializedType.Type;
}
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 (sharedVariable == null) {
sharedVariable = CreateLegacySharedVariable(type, value);
}
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;
}
}
if (sharedVariable != null) {
return sharedVariable;
}
}
return value;
}
/// <summary>
/// Creates a SharedVariable from a legacy non-shared serialized value.
/// </summary>
/// <param name="sharedVariableType">The current SharedVariable field type.</param>
/// <param name="value">The legacy serialized value.</param>
/// <returns>The wrapped SharedVariable, or null if the value could not be converted.</returns>
private static SharedVariable CreateLegacySharedVariable(Type sharedVariableType, object value)
{
if (sharedVariableType == null || sharedVariableType.IsAbstract || value == null) {
return null;
}
try {
var sharedVariable = Activator.CreateInstance(sharedVariableType) as SharedVariable;
if (sharedVariable == null) {
return null;
}
var valueType = sharedVariable.ElementType;
if (!valueType.IsInstanceOfType(value)) {
if (valueType.IsEnum) {
value = value is string enumName ? Enum.Parse(valueType, enumName) : Enum.ToObject(valueType, value);
} else if (valueType == typeof(string)) {
value = value.ToString();
} else {
value = Convert.ChangeType(value, valueType);
}
}
sharedVariable.Scope = SharedVariable.SharingScope.Self;
sharedVariable.SetValue(value);
return sharedVariable;
} catch {
return null;
}
}
/// <summary>
/// Deserializes the SharedVariables. This allows the SharedVariables to be deserialized independently.
/// </summary>
/// <param name="graph">The graph that is being deserialized.</param>
/// <param name="force">Should the variables be forced deserialized?</param>
/// <param name="canDeepCopy">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 SharedVariables were deserialized.</returns>
public bool DeserializeSharedVariables(IGraph graph, bool force, bool canDeepCopy, SharedVariableOverride[] sharedVariableOverrides = null)
{
// No need to deserialize if the data is already deserialized.
if (!force && (m_SharedVariables != null || m_VariableByNameMap != null
#if UNITY_EDITOR
|| m_SharedVariableGroups != null
#endif
)) {
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) {
var originalTypeName = m_SharedVariableData[i].ObjectType;
m_SharedVariableData[i].ObjectType = typeof(UnknownSharedVariable).FullName;
m_SharedVariables[i] = m_SharedVariableData[i].DeserializeFields(MemberVisibility.Public) as SharedVariable;
m_SharedVariableData[i].ObjectType = originalTypeName;
// Store the original type name in the unknown variable.
if (m_SharedVariables[i] is UnknownSharedVariable unknownVar) {
unknownVar.UnknownType = originalTypeName;
}
Debug.LogError($"Error: Unable to deserialize SharedVariable {m_SharedVariables[i].Name} of type {originalTypeName}.");
}
// 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;
}
m_VariableByNameMap = PopulateSharedVariablesMapping(graph, canDeepCopy);
#if UNITY_EDITOR
if (m_SharedVariableGroupsData != null && m_SharedVariableGroupsData.Length > 0) {
m_SharedVariableGroups = new SharedVariableGroup[m_SharedVariableGroupsData.Length];
for (int i = 0; i < m_SharedVariableGroupsData.Length; ++i) {
m_SharedVariableGroups[i] = m_SharedVariableGroupsData[i].DeserializeFields(MemberVisibility.Public) as SharedVariableGroup;
}
} else {
m_SharedVariableGroups = null;
}
#endif
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>
/// 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)
{
return PopulateSharedVariablesMapping(graph, graph.SharedVariables, canDeepCopy);
}
/// <summary>
/// Populates the SharedVariable Mapping at runtime.
/// </summary>
/// <param name="graph">The graph that is being deserialized.</param>
/// <param name="graphSharedVariables">The SharedVariables that should be used for graph scope variables.</param>
/// <param name="canDeepCopy">Can the SharedVariables be deep copied?</param>
/// <returns>A reference to the map between the VariableAssignment and SharedVariable.</returns>
private static Dictionary<VariableAssignment, SharedVariable> PopulateSharedVariablesMapping(IGraph graph, SharedVariable[] graphSharedVariables, bool canDeepCopy)
{
var variableByNameMap = new Dictionary<VariableAssignment, SharedVariable>();
PopulateSharedVariablesMapping(graph, graphSharedVariables, 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;
}
var deepCopy = canDeepCopy && graph is Subtree && scope == SharedVariable.SharingScope.Graph; // Deep copy variables so the instance is not bound to the subtree.
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 val = new VariableAssignment(sharedVariables[i].Name, scope);
variableByNameMap.Add(val, deepCopy ? CopyUtility.DeepCopy(sharedVariables[i]) as SharedVariable : sharedVariables[i]);
}
}
/// <summary>
/// Injects the subtree into the task list.
/// </summary>
private void InjectSubtrees()
{
if (m_InjectedSubtreeReference == null || m_InjectedSubtreeReference.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 eventAssignments = new ResizableArray<SubtreeAssignment>();
var lastParentIndex = m_Tasks[m_InjectedSubtreeReference[0].NodeIndex].ParentIndex;
var parentIndexOffset = 0;
for (int i = 0; i < m_InjectedSubtreeReference.Count; ++i) {
var subtreeReference = m_Tasks[m_InjectedSubtreeReference[i].NodeIndex] as ISubtreeReferenceNode;
var subtrees = subtreeReference.Subtrees;
if (subtrees != null) {
var indexOffset = (ushort)0; // The index offset is relative to each individual ISubtreeReferenceNode task.
// The parent index will change based on the number of tasks that have been added.
var parentIndex = m_Tasks[m_InjectedSubtreeReference[i].NodeIndex].ParentIndex;
if (parentIndex != ushort.MaxValue && (parentIndex > lastParentIndex || (i > 0 && lastParentIndex == ushort.MaxValue))) {
parentIndexOffset = (ushort)(taskCount - subtreeReferenceCount);
lastParentIndex = parentIndex;
} else if (parentIndex < lastParentIndex) {
parentIndexOffset = 0;
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;
}
var eventNodes = subtrees[j].EventNodes;
if (eventNodes == null) {
continue;
}
for (int k = 0; k < eventNodes.Length; ++k) {
var eventNode = eventNodes[k];
if (eventNode == null) {
continue;
}
if (eventNode.ConnectedIndex == ushort.MaxValue || !subtrees[j].IsNodeEnabled(false, k)) {
continue;
}
var sourceIndex = eventNode.ConnectedIndex;
var subtreeNodes = m_InjectedSubtreeReference[i].TreeNodes[j];
if (subtreeNodes == null || sourceIndex >= subtreeNodes.Length) {
continue;
}
var firstNode = subtreeNodes[sourceIndex];
var subtreeNodeCount = GetChildCount(firstNode, subtreeNodes) + 1; // firstNode should be included in addition to the children.
if (eventNode.GetType() == typeof(Start)) {
taskCount += subtreeNodeCount;
subtreeAssignments.Add(new SubtreeAssignment()
{
EventNodeType = eventNode.GetType(),
EventNodeIndex = (ushort)k,
SourceIndex = sourceIndex,
ReferenceIndex = i,
NodeIndex = m_InjectedSubtreeReference[i].NodeIndex,
SubtreeIndex = j,
Subtree = subtrees[j],
NodeCount = (ushort)subtreeNodeCount,
IndexOffset = indexOffset,
ParentIndex = (ushort)(parentIndex + parentIndexOffset),
SiblingIndex = m_Tasks[m_InjectedSubtreeReference[i].NodeIndex].SiblingIndex,
#if UNITY_EDITOR
NodePropertiesPosition = m_LogicNodeProperties[m_InjectedSubtreeReference[i].NodeIndex].Position,
Collapsed = m_LogicNodeProperties[m_InjectedSubtreeReference[i].NodeIndex].Collapsed
#endif
});
indexOffset += (ushort)subtreeNodeCount;
} else {
eventAssignments.Add(new SubtreeAssignment()
{
EventNodeType = eventNode.GetType(),
EventNodeIndex = (ushort)k,
SourceIndex = sourceIndex,
ReferenceIndex = i,
NodeIndex = m_InjectedSubtreeReference[i].NodeIndex,
SubtreeIndex = j,
Subtree = subtrees[j],
NodeCount = (ushort)subtreeNodeCount,
ParentIndex = ushort.MaxValue,
SiblingIndex = ushort.MaxValue
});
}
}
}
// 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_InjectedSubtreeReference[i];
subtreeNodesReferenceOrig.NodeCount = indexOffset;
m_InjectedSubtreeReference[i] = subtreeNodesReferenceOrig;
}
}
if (taskCount > 0) {
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;
}
}
subtreeAssignments[i] = new SubtreeAssignment {
EventNodeType = subtreeAssignments[i].EventNodeType,
EventNodeIndex = subtreeAssignments[i].EventNodeIndex,
SourceIndex = subtreeAssignments[i].SourceIndex,
ReferenceIndex = subtreeAssignments[i].ReferenceIndex,
NodeIndex = subtreeAssignments[i].NodeIndex,
SubtreeIndex = subtreeAssignments[i].SubtreeIndex,
Subtree = subtreeAssignments[i].Subtree,
NodeCount = subtreeAssignments[i].NodeCount,
IndexOffset = subtreeAssignments[i].IndexOffset,
ParentIndex = subtreeAssignments[i].ParentIndex,
SiblingIndex = subtreeAssignments[i].SiblingIndex,
#if UNITY_EDITOR
NodePropertiesPosition = subtreeAssignments[i].NodePropertiesPosition,
Collapsed = subtreeAssignments[i].Collapsed,
#endif
};
}
// 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;
var rootSiblingIndex = ushort.MaxValue;
if (i + 1 < subtreeAssignments.Count && subtreeAssignments[i + 1].ReferenceIndex == subtreeAssignments[i].ReferenceIndex) {
// Point to the first node of the next subtree.
rootSiblingIndex = (ushort)(subtreeAssignments[i + 1].NodeIndex + subtreeAssignments[i + 1].IndexOffset);
} else {
// Use the original SiblingIndex from the reference task.
rootSiblingIndex = subtreeAssignments[i].SiblingIndex != ushort.MaxValue ? (ushort)(subtreeIndex + subtreeAssignments[i].NodeCount) : ushort.MaxValue;
}
var subtreeReference = m_InjectedSubtreeReference[subtreeAssignments[i].ReferenceIndex];
var subtreeNodes = subtreeReference.TreeNodes[subtreeAssignments[i].SubtreeIndex];
if (subtreeNodes == null || subtreeAssignments[i].SourceIndex >= subtreeNodes.Length) {
continue;
}
InjectSubtreeLogicNodes(subtreeAssignments[i], subtreeNodes, subtreeReference.Subtrees[subtreeAssignments[i].SubtreeIndex].Pooled, subtreeIndex, subtreeParentIndex,
rootSiblingIndex, subtreeReference.GraphReference.Enabled, true, false);
}
}
InjectSubtreeEventNodes(eventAssignments);
}
/// <summary>
/// Injects subtree logic nodes into the task list.
/// </summary>
/// <param name="assignment">The subtree assignment.</param>
/// <param name="subtreeNodes">The subtree nodes to inject.</param>
/// <param name="pooled">Is the subtree pooled?</param>
/// <param name="branchStartIndex">The index to start inserting nodes at.</param>
/// <param name="rootParentIndex">The parent index for the root node.</param>
/// <param name="rootSiblingIndex">The sibling index for the root node.</param>
/// <param name="subtreeReferenceEnabled">Is the subtree reference enabled?</param>
/// <param name="applyPositionOffset">Should node properties be offset to match the reference?</param>
/// <param name="buildNodeMap">Should a node map be built for event node remapping?</param>
/// <returns>A map of original nodes to copied nodes (can be null).</returns>
private Dictionary<object, object> InjectSubtreeLogicNodes(SubtreeAssignment assignment, ITreeLogicNode[] subtreeNodes, bool pooled, ushort branchStartIndex,
ushort rootParentIndex, ushort rootSiblingIndex, bool subtreeReferenceEnabled, bool applyPositionOffset, bool buildNodeMap)
{
if (subtreeNodes == null || assignment.SourceIndex >= subtreeNodes.Length) {
return null;
}
var indexOffset = (int)branchStartIndex - assignment.SourceIndex;
Dictionary<object, object> nodeMap = null;
if (!pooled && buildNodeMap) {
nodeMap = new Dictionary<object, object>();
}
#if UNITY_EDITOR
var positionOffset = Vector2.zero;
#endif
for (int j = 0; j < assignment.NodeCount; ++j) {
var node = subtreeNodes[assignment.SourceIndex + j];
// The node needs to be copied if it isn't pooled to prevent the same node from being used in multiple trees.
if (!pooled) {
node = CopySubtreeLogicNode(node, nodeMap);
}
node.Index = (ushort)(branchStartIndex + j);
node.RuntimeIndex = ushort.MaxValue;
if (j == 0) {
node.ParentIndex = rootParentIndex;
node.SiblingIndex = rootSiblingIndex;
} else {
// Adjust the subsequent subtree tasks by the location of the insertion.
if (node.ParentIndex != ushort.MaxValue) {
node.ParentIndex = (ushort)(node.ParentIndex + indexOffset);
}
if (node.SiblingIndex != ushort.MaxValue) {
node.SiblingIndex = (ushort)(node.SiblingIndex + indexOffset);
}
}
// If the parent reference task is disabled then all subtree nodes should be disabled.
if (!subtreeReferenceEnabled) {
node.Enabled = false;
}
m_Tasks[branchStartIndex + j] = node;
#if UNITY_EDITOR
if (m_LogicNodeProperties != null && assignment.Subtree.LogicNodeProperties != null && assignment.SourceIndex + j < assignment.Subtree.LogicNodeProperties.Length) {
var nodeProperties = CopyUtility.DeepCopy(assignment.Subtree.LogicNodeProperties[assignment.SourceIndex + j]) as LogicNodeProperties;
nodeProperties.GuidString = Guid.NewGuid().ToString();
if (applyPositionOffset) {
if (j == 0) {
// Keep the tasks in the same relative position as the subtree reference.
positionOffset = assignment.NodePropertiesPosition - assignment.Subtree.LogicNodeProperties[assignment.SourceIndex + 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 = assignment.Collapsed;
}
m_LogicNodeProperties[branchStartIndex + j] = nodeProperties;
}
#endif
}
return nodeMap;
}
/// <summary>
/// Copies the subtree logic node while updating variable fields and any node maps.
/// </summary>
/// <param name="node">The node to copy.</param>
/// <param name="nodeMap">The node map to update (can be null).</param>
/// <returns>The copied node.</returns>
private ITreeLogicNode CopySubtreeLogicNode(ITreeLogicNode node, Dictionary<object, object> nodeMap)
{
var copiedNode = CopyUtility.DeepCopy(node) as ITreeLogicNode;
if ((m_VariableFields != null && m_VariableFields.Count > 0) || nodeMap != null) {
// Replace the old node reference with the updated reference.
var localMap = new Dictionary<object, object>();
localMap.Add(node, copiedNode);
if (node is IContainerNode containerNode) {
if (containerNode.Nodes != null) {
var copiedContainerNode = copiedNode as IContainerNode;
for (int k = 0; k < containerNode.Nodes.Length; ++k) {
localMap.Add(containerNode.Nodes[k], copiedContainerNode.Nodes[k]);
}
}
}
if (nodeMap != null) {
foreach (var pair in localMap) {
nodeMap.Add(pair.Key, pair.Value);
}
}
if (m_VariableFields != null && m_VariableFields.Count > 0) {
for (int k = 0; k < m_VariableFields.Count; ++k) {
if (localMap.TryGetValue(m_VariableFields[k].Task, out var copiedTask)) {
var variableField = m_VariableFields[k];
variableField.Task = copiedTask;
m_VariableFields[k] = variableField;
}
}
}
}
copiedNode.Enabled = node.Enabled;
return copiedNode;
}
/// <summary>
/// Injects subtree event nodes and their connected logic nodes at the end of the task list.
/// </summary>
/// <param name="eventAssignments">The event node assignments to inject.</param>
private void InjectSubtreeEventNodes(ResizableArray<SubtreeAssignment> eventAssignments)
{
if (eventAssignments == null || eventAssignments.Count == 0) {
return;
}
var injectAssignments = new ResizableArray<SubtreeAssignment>();
var singleInstanceEventTypes = new HashSet<Type>();
if (m_EventTasks != null && m_EventTasks.Length > 0) {
for (int i = 0; i < m_EventTasks.Length; ++i) {
var eventTask = m_EventTasks[i];
if (eventTask == null) {
continue;
}
var eventType = eventTask.GetType();
if (AllowsMultipleEventNodeTypes(eventType)) {
continue;
}
singleInstanceEventTypes.Add(eventType);
}
}
for (int i = 0; i < eventAssignments.Count; ++i) {
var assignment = eventAssignments[i];
var eventType = assignment.EventNodeType;
if (eventType == null) {
continue;
}
if (!AllowsMultipleEventNodeTypes(eventType)) {
if (singleInstanceEventTypes.Contains(eventType)) {
continue;
}
singleInstanceEventTypes.Add(eventType);
}
injectAssignments.Add(assignment);
}
if (injectAssignments.Count == 0) {
return;
}
if (m_Tasks == null) {
m_Tasks = new ITreeLogicNode[0];
}
var originalTaskCount = m_Tasks.Length;
var addedTaskCount = 0;
for (int i = 0; i < injectAssignments.Count; ++i) {
addedTaskCount += injectAssignments[i].NodeCount;
}
if (addedTaskCount > 0) {
Array.Resize(ref m_Tasks, originalTaskCount + addedTaskCount);
#if UNITY_EDITOR
Array.Resize(ref m_LogicNodeProperties, originalTaskCount + addedTaskCount);
#endif
}
var addedTasks = 0;
for (int i = 0; i < injectAssignments.Count; ++i) {
var assignment = injectAssignments[i];
var subtreeReference = m_InjectedSubtreeReference[assignment.ReferenceIndex];
var subtree = assignment.Subtree;
var subtreeNodes = subtreeReference.TreeNodes[assignment.SubtreeIndex];
if (subtreeNodes == null || assignment.SourceIndex >= subtreeNodes.Length) {
continue;
}
var pooled = subtreeReference.Subtrees[assignment.SubtreeIndex].Pooled;
var subtreeReferenceEnabled = subtreeReference.GraphReference.Enabled;
var branchStartIndex = (ushort)(originalTaskCount + addedTasks);
var nodeMap = InjectSubtreeLogicNodes(assignment, subtreeNodes, pooled, branchStartIndex, ushort.MaxValue,
ushort.MaxValue, subtreeReferenceEnabled, false, true);
var eventNodes = subtree.EventNodes;
if (eventNodes == null || assignment.EventNodeIndex >= eventNodes.Length) {
addedTasks += assignment.NodeCount;
continue;
}
var eventNode = eventNodes[assignment.EventNodeIndex];
if (eventNode == null) {
addedTasks += assignment.NodeCount;
continue;
}
var injectedEventNode = eventNode;
if (!pooled) {
injectedEventNode = CopyUtility.DeepCopy(eventNode) as IEventNode;
if (m_VariableFields != null && m_VariableFields.Count > 0) {
for (int k = 0; k < m_VariableFields.Count; ++k) {
if (ReferenceEquals(m_VariableFields[k].Task, eventNode)) {
var variableField = m_VariableFields[k];
variableField.Task = injectedEventNode;
m_VariableFields[k] = variableField;
}
}
}
RemapEventNodeReferences(injectedEventNode, nodeMap);
}
injectedEventNode.ConnectedIndex = branchStartIndex;
if (!subtreeReferenceEnabled) {
injectedEventNode.Enabled = false;
}
var eventIndex = (ushort)(m_EventTasks != null ? m_EventTasks.Length : 0);
if (m_EventTasks == null) {
m_EventTasks = new IEventNode[1];
} else {
Array.Resize(ref m_EventTasks, m_EventTasks.Length + 1);
}
m_EventTasks[eventIndex] = injectedEventNode;
injectedEventNode.Index = eventIndex;
if (m_InjectedSubtreeEventNodes == null) {
m_InjectedSubtreeEventNodes = new HashSet<IEventNode>();
}
m_InjectedSubtreeEventNodes.Add(injectedEventNode);
#if UNITY_EDITOR
if (m_EventNodeProperties != null) {
Array.Resize(ref m_EventNodeProperties, m_EventTasks.Length);
if (subtree.EventNodeProperties != null && assignment.EventNodeIndex < subtree.EventNodeProperties.Length) {
var nodeProperties = CopyUtility.DeepCopy(subtree.EventNodeProperties[assignment.EventNodeIndex]) as NodeProperties;
nodeProperties.GuidString = Guid.NewGuid().ToString();
m_EventNodeProperties[eventIndex] = nodeProperties;
}
}
#endif
addedTasks += assignment.NodeCount;
}
}
/// <summary>
/// Returns true if the event node type allows multiple nodes of that type.
/// </summary>
/// <param name="eventNodeType">The event node type.</param>
/// <returns>True if the type allows multiple nodes.</returns>
private bool AllowsMultipleEventNodeTypes(Type eventNodeType)
{
return eventNodeType != null && Attribute.IsDefined(eventNodeType, typeof(AllowMultipleTypes), true);
}
#if UNITY_EDITOR
/// <summary>
/// Updates the cached set of injected graph references.
/// </summary>
private void UpdateInjectedGraphReferences()
{
if (m_InjectedSubtreeReference == null || m_InjectedSubtreeReference.Count == 0) {
m_InjectedGraphReferences = null;
return;
}
if (m_InjectedGraphReferences == null) {
m_InjectedGraphReferences = new ResizableArray<InjectedGraphReference>(m_InjectedSubtreeReference.Count);
} else {
m_InjectedGraphReferences.Clear();
}
PopulateInjectedSubtreeReferences(ref m_InjectedGraphReferences);
}
/// <summary>
/// Retrieves all of the injected subtree references and stores the result in m_InjectedSubtreeReferences.
/// </summary>
/// <param name="injectedGraphReferences">A reference to the array that the injected graph references should be added to.</param>
private void PopulateInjectedSubtreeReferences(ref ResizableArray<InjectedGraphReference> injectedGraphReferences)
{
if (m_InjectedSubtreeReference == null || injectedGraphReferences == null) {
return;
}
var nodeCount = 0;
for (int i = 0; i < m_InjectedSubtreeReference.Count; ++i) {
var injectedSubtreeReference = m_InjectedSubtreeReference[i];
// Emit a single top-level reference entry per SubtreeReference node. Multiple selected subtrees
// are represented by Nodes[subgraphIndex] instead of additional reference nodes.
var injectedReferenceRuntimeNodeIndex = (ushort)(injectedSubtreeReference.NodeIndex + nodeCount);
LogicNodeProperties graphReferenceNodeProperties = null;
if (m_LogicNodeProperties != null && injectedReferenceRuntimeNodeIndex < m_LogicNodeProperties.Length) {
graphReferenceNodeProperties = m_LogicNodeProperties[injectedReferenceRuntimeNodeIndex];
}
IGraph injectedReferenceGraph = null;
if (injectedSubtreeReference.Graphs != null) {
for (int j = 0; j < injectedSubtreeReference.Graphs.Length; ++j) {
if (injectedSubtreeReference.Graphs[j] != null) {
injectedReferenceGraph = injectedSubtreeReference.Graphs[j];
break;
}
}
}
var injectedReference = new InjectedGraphReference() {
Graph = injectedReferenceGraph,
NodeIndex = injectedSubtreeReference.NodeIndex,
RuntimeNodeIndex = injectedReferenceRuntimeNodeIndex,
NodeCount = injectedSubtreeReference.NodeCount,
GraphReference = injectedSubtreeReference.GraphReference,
Nodes = injectedSubtreeReference.Nodes,
GraphReferenceNodeProperties = graphReferenceNodeProperties
};
injectedGraphReferences.Add(injectedReference);
// Track how far into the injected runtime span each selected subtree starts.
var subtreeRuntimeOffset = (ushort)0;
var nodeDelta = injectedSubtreeReference.NodeCount > 0 ? injectedSubtreeReference.NodeCount - 1 : 0; // The SubtreeRefrence node itself doesn't count.
if (injectedSubtreeReference.Graphs == null || injectedSubtreeReference.Nodes == null) {
nodeCount += nodeDelta;
continue;
}
for (int j = 0; j < injectedSubtreeReference.Graphs.Length; ++j) {
var graph = injectedSubtreeReference.Graphs[j];
ushort subtreeNodeCount = 0;
if (j < injectedSubtreeReference.Nodes.Length && injectedSubtreeReference.Nodes[j] != null) {
subtreeNodeCount = (ushort)injectedSubtreeReference.Nodes[j].Length;
}
if (graph != null && graph.InjectedGraphReferences != null) {
for (int k = 0; k < graph.InjectedGraphReferences.Length; ++k) {
var graphInjectedGraphReferences = graph.InjectedGraphReferences[k];
// Nested references already have a runtime-relative index within the selected subtree.
var nestedReferenceRuntimeNodeIndex = graphInjectedGraphReferences.RuntimeNodeIndex != ushort.MaxValue ? graphInjectedGraphReferences.RuntimeNodeIndex : graphInjectedGraphReferences.NodeIndex;
if (graph.LogicNodeProperties == null || nestedReferenceRuntimeNodeIndex >= graph.LogicNodeProperties.Length) {
continue;
}
var subgraphReferenceNodeProperties = graph.LogicNodeProperties[nestedReferenceRuntimeNodeIndex];
var subgraphInjectedReference = new InjectedGraphReference() {
Graph = graphInjectedGraphReferences.Graph,
NodeIndex = graphInjectedGraphReferences.NodeIndex,
// Compose runtime index in the root graph:
// root reference start + selected subtree start + nested reference offset.
RuntimeNodeIndex = (ushort)(injectedReferenceRuntimeNodeIndex + subtreeRuntimeOffset + nestedReferenceRuntimeNodeIndex),
NodeCount = graphInjectedGraphReferences.NodeCount,
GraphReference = graphInjectedGraphReferences.GraphReference,
Nodes = graphInjectedGraphReferences.Nodes,
GraphReferenceNodeProperties = subgraphReferenceNodeProperties
};
injectedGraphReferences.Add(subgraphInjectedReference);
}
}
subtreeRuntimeOffset += subtreeNodeCount;
}
nodeCount += nodeDelta; // The SubtreeRefrence node itself doesn't count.
}
}
#endif
/// <summary>
/// Remaps any ILogicNode references on the event node to the copied nodes.
/// </summary>
/// <param name="eventNode">The event node to update.</param>
/// <param name="nodeMap">A map of original nodes to copied nodes.</param>
private void RemapEventNodeReferences(IEventNode eventNode, Dictionary<object, object> nodeMap)
{
if (eventNode == null || nodeMap == null || nodeMap.Count == 0) {
return;
}
var fields = eventNode.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
for (int i = 0; i < fields.Length; ++i) {
var field = fields[i];
var fieldType = field.FieldType;
if (typeof(ILogicNode).IsAssignableFrom(fieldType)) {
var value = field.GetValue(eventNode);
if (value != null && nodeMap.TryGetValue(value, out var mapped)) {
field.SetValue(eventNode, mapped);
}
} else if (typeof(IList).IsAssignableFrom(fieldType)) {
var elementType = Serializer.GetElementType(fieldType);
if (!typeof(ILogicNode).IsAssignableFrom(elementType)) {
continue;
}
var listValue = field.GetValue(eventNode) as IList;
if (listValue == null) {
continue;
}
for (int j = 0; j < listValue.Count; ++j) {
var listItem = listValue[j];
if (listItem != null && nodeMap.TryGetValue(listItem, out var mapped)) {
listValue[j] = mapped;
}
}
}
}
}
/// <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>
/// 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 ITreeLogicNode 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(ITreeLogicNode node, ITreeLogicNode[] 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 ISubtreeReferenceNodes by calling the EvaluateSubgraphs 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_InjectedSubtreeReference == null || m_InjectedSubtreeReference.Count == 0) {
return false;
}
if (onBeforeReevaluationSwap != null) {
onBeforeReevaluationSwap();
}
RemoveInjectedSubtreeEventNodes();
var baseEventTaskCount = m_EventTasks != null ? m_EventTasks.Length : 0;
// Find the new reevaluated nodes.
for (int i = m_InjectedSubtreeReference.Count - 1; i >= 0; --i) {
var subtreeNodesReference = m_InjectedSubtreeReference[i];
var subtreeReference = m_InjectedSubtreeReference[i].GraphReference as ISubtreeReferenceNode;
subtreeReference.EvaluateSubgraphs(graphComponent);
var reevaluatedSubtrees = subtreeReference.Subtrees;
if (reevaluatedSubtrees == null) {
continue;
}
// The parent must be able to accept the number of subtrees that there are.
var parentIndex = m_Tasks[m_InjectedSubtreeReference[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;
}
var reevaluatedNodes = new ITreeLogicNode[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, 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].TreeLogicNodes;
}
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_InjectedSubtreeReference[j].NodeCount > 0 ? m_InjectedSubtreeReference[j].NodeCount - 1 : 0;
}
// All of the reevaluated nodes have been determined. Remove the old subtree nodes.
var nodeCount = m_InjectedSubtreeReference[i].NodeCount;
// Replace the first node with the subtree reference, and remove the rest of the added nodes.
m_Tasks[m_InjectedSubtreeReference[i].NodeIndex + nodeOffset] = m_InjectedSubtreeReference[i].GraphReference as ITreeLogicNode;
for (int j = m_InjectedSubtreeReference[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_InjectedSubtreeReference[i].NodeIndex + nodeOffset) {
m_Tasks[j].ParentIndex -= (ushort)(nodeCount - 1);
}
if (m_Tasks[j].SiblingIndex != ushort.MaxValue && m_Tasks[j].SiblingIndex > m_InjectedSubtreeReference[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 sibling index value for parent nodes.
parentIndex = m_Tasks[m_InjectedSubtreeReference[i].NodeIndex + nodeOffset].ParentIndex;
while (parentIndex != ushort.MaxValue) {
var parentTask = m_Tasks[parentIndex];
if (parentTask.SiblingIndex != ushort.MaxValue) {
parentTask.SiblingIndex -= (ushort)(nodeCount - 1);
m_Tasks[parentIndex] = parentTask;
}
parentIndex = parentTask.ParentIndex;
}
// Restore the original ConnectedIndex value.
if (m_EventTasks != null) {
for (int j = 0; j < m_EventTasks.Length; ++j) {
if (m_EventTasks[j].ConnectedIndex > m_InjectedSubtreeReference[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_InjectedSubtreeReference[i] = subtreeNodesReference;
}
// 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 for the base event tasks.
if (m_EventTasks != null && m_InjectedSubtreeReference != null && baseEventTaskCount > 0) {
for (int i = 0; i < baseEventTaskCount; ++i) {
var offset = 0;
for (int j = 0; j < m_InjectedSubtreeReference.Count; ++j) {
if (m_InjectedSubtreeReference[j].NodeIndex >= m_EventTasks[i].ConnectedIndex) {
break;
}
offset += m_InjectedSubtreeReference[j].NodeCount > 0 ? m_InjectedSubtreeReference[j].NodeCount - 1 : 0;
}
if (offset != 0) {
m_EventTasks[i].ConnectedIndex += (ushort)offset;
}
}
}
#if UNITY_EDITOR
UpdateInjectedGraphReferences();
#endif
return true;
}
/// <summary>
/// Removes any injected subtree event nodes and their appended logic nodes.
/// </summary>
private void RemoveInjectedSubtreeEventNodes()
{
if (m_EventTasks == null || m_EventTasks.Length == 0 || m_InjectedSubtreeEventNodes == null || m_InjectedSubtreeEventNodes.Count == 0) {
return;
}
var originalEventTasks = m_EventTasks;
#if UNITY_EDITOR
var originalEventNodeProperties = m_EventNodeProperties;
#endif
var totalBranchCount = 0;
var injectedEventTaskCount = 0;
for (int i = 0; i < originalEventTasks.Length; ++i) {
var eventTask = originalEventTasks[i];
if (eventTask == null || !m_InjectedSubtreeEventNodes.Contains(eventTask)) {
continue;
}
injectedEventTaskCount++;
if (eventTask.ConnectedIndex == ushort.MaxValue || eventTask.ConnectedIndex >= m_Tasks.Length) {
continue;
}
totalBranchCount += GetChildCount(m_Tasks[eventTask.ConnectedIndex], m_Tasks) + 1;
}
if (injectedEventTaskCount == 0) {
m_InjectedSubtreeEventNodes.Clear();
return;
}
if (totalBranchCount > 0 && totalBranchCount <= m_Tasks.Length) {
Array.Resize(ref m_Tasks, m_Tasks.Length - totalBranchCount);
#if UNITY_EDITOR
Array.Resize(ref m_LogicNodeProperties, m_LogicNodeProperties.Length - totalBranchCount);
#endif
}
var retainedEventTaskCount = originalEventTasks.Length - injectedEventTaskCount;
var retainedEventTasks = new IEventNode[retainedEventTaskCount];
var retainedIndex = 0;
for (int i = 0; i < originalEventTasks.Length; ++i) {
var eventTask = originalEventTasks[i];
if (eventTask != null && m_InjectedSubtreeEventNodes.Contains(eventTask)) {
continue;
}
if (eventTask != null) {
eventTask.Index = (ushort)retainedIndex;
}
retainedEventTasks[retainedIndex] = eventTask;
retainedIndex++;
}
m_EventTasks = retainedEventTasks;
#if UNITY_EDITOR
if (originalEventNodeProperties != null) {
var retainedEventNodeProperties = new NodeProperties[retainedEventTaskCount];
retainedIndex = 0;
for (int i = 0; i < originalEventTasks.Length && i < originalEventNodeProperties.Length; ++i) {
var eventTask = originalEventTasks[i];
if (eventTask != null && m_InjectedSubtreeEventNodes.Contains(eventTask)) {
continue;
}
retainedEventNodeProperties[retainedIndex] = originalEventNodeProperties[i];
retainedIndex++;
}
m_EventNodeProperties = retainedEventNodeProperties;
}
#endif
m_InjectedSubtreeEventNodes.Clear();
}
/// <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, PropertyName name, SharedVariable.SharingScope scope)
{
if (m_VariableByNameMap == null) {
DeserializeSharedVariables(graph, false, true, null);
}
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, PropertyName name, SharedVariable.SharingScope scope)
{
return GetVariable(graph, name, scope) as SharedVariable<T>;
}
/// <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>
/// <param name="scope">The scope of the SharedVariable that should be set.</typeparam>
/// <returns>True if the value was set.</returns>
public bool SetVariableValue<T>(IGraph graph, PropertyName name, T value, SharedVariable.SharingScope scope)
{
if (m_VariableByNameMap == null) {
DeserializeSharedVariables(graph, false, true, null);
}
if (m_VariableByNameMap == null || !m_VariableByNameMap.TryGetValue(new VariableAssignment(name, scope), out var variable)) {
return false;
}
(variable as SharedVariable<T>).Value = value;
return true;
}
/// <summary>
/// Overrides the SharedVariable binding. 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 OverrideVariableBinding(IGraph graph, SharedVariable variable)
{
if (string.IsNullOrEmpty(variable.Name)) {
return;
}
DeserializeSharedVariables(graph, false, true, null);
var dirty = false;
if (m_SharedVariables != null) {
for (int i = 0; i < m_SharedVariables.Length; ++i) {
if (m_SharedVariables[i].Name == variable.Name) {
var variableType = variable.GetType();
if (variableType.IsGenericType && variableType.GetGenericTypeDefinition().IsAssignableFrom(typeof(SharedVariableBinding<>))) {
m_SharedVariables[i] = variable.Clone() as SharedVariable;
dirty = true;
}
break;
}
}
}
if (dirty) {
// The graph may be a BehaviorTree pointing to local variables while this data belongs to a subtree.
// Rebuild graph-scope mappings from this data's variables to preserve the cloned binding instance.
m_VariableByNameMap = PopulateSharedVariablesMapping(graph, m_SharedVariables, true);
}
}
/// <summary>
/// Replaces the data with the specified BehaviorTreeData.
/// </summary>
/// <param name="graph">The graph that the current data belongs to.</param>
/// <param name="other">The data that should be replaced.</param>
/// <param name="originalSharedVariables">The SharedVariables of the current graph.</param>
internal void OverrideData(IGraph graph, BehaviorTreeData other, SharedVariable[] originalSharedVariables, bool updateFields)
{
EventNodes = other.EventNodes;
LogicNodes = other.LogicNodes;
InjectedSubtreeReferences = other.InjectedSubtreeReferences;
m_InjectedSubtreeEventNodes = other.m_InjectedSubtreeEventNodes;
m_SharedVariables = other.SharedVariables;
m_SharedVariableData = other.m_SharedVariableData;
m_VariableByNameMap = PopulateSharedVariablesMapping(graph, false);
m_DisabledLogicNodes = other.DisabledLogicNodes;
m_DisabledEventNodes = other.DisabledEventNodes;
// The other tree may be pooled. Update the variable references to point to the local graph variables.
if (updateFields && other.m_VariableFields != null) {
for (int i = 0; i < other.m_VariableFields.Count; ++i) {
var variableField = other.m_VariableFields[i];
var localVariable = GetVariable(graph, variableField.Name, SharedVariable.SharingScope.Graph);
if (localVariable != null) {
variableField.Field.SetValue(variableField.Task, localVariable);
}
}
}
// The original tree variable value should override the other variable value.
if (originalSharedVariables != null) {
for (int i = 0; i < originalSharedVariables.Length; ++i) {
OverrideVariableValue(graph, originalSharedVariables[i]);
}
}
#if UNITY_EDITOR
m_EventNodeProperties = other.EventNodeProperties;
m_LogicNodeProperties = other.LogicNodeProperties;
m_SharedVariableGroups = other.SharedVariableGroups;
m_SharedVariableGroupsData = other.m_SharedVariableGroupsData;
m_GroupProperties = other.GroupProperties;
m_InjectedGraphReferences = other.m_InjectedGraphReferences;
#endif
}
/// <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>
/// <returns>True if the value was overridden.</returns>
private bool OverrideVariableValue(IGraph graph, SharedVariable variable)
{
if (string.IsNullOrEmpty(variable.Name)) {
return false;
}
var dirty = false;
if (m_SharedVariables != null) {
for (int i = 0; i < m_SharedVariables.Length; ++i) {
if (m_SharedVariables[i].Name == variable.Name) {
var variableType = variable.GetType();
if (m_SharedVariables[i].GetType() == variableType) {
m_SharedVariables[i].SetValue(variable.GetValue());
dirty = true;
}
break;
}
}
}
return dirty;
}
/// <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;
}
// Check the Enabled property on the node directly.
if (logicNode) {
if (m_Tasks == null || index >= m_Tasks.Length) {
return true;
}
return m_Tasks[index].Enabled;
} else {
if (m_EventTasks == null || index >= m_EventTasks.Length) {
return true;
}
return m_EventTasks[index].Enabled;
}
}
}
}
#endif