#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;
///
/// Storage class for the graph data.
///
[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 m_VariableByNameMap;
private int m_RuntimeUniqueID;
[System.NonSerialized] private ResizableArray m_InjectedGraphReferences;
[System.NonSerialized] private HashSet 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 VariableByNameMap { get => m_VariableByNameMap; set => m_VariableByNameMap = value; }
internal ResizableArray 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 m_InjectedSubtreeReference;
private ResizableArray m_VariableFields;
[System.NonSerialized] private bool m_Deserializing;
internal ResizableArray InjectedSubtreeReferences { get => m_InjectedSubtreeReference; set => m_InjectedSubtreeReference = value; }
///
/// Default constructor.
///
public BehaviorTreeData()
{
m_UniqueID = Guid.NewGuid().GetHashCode();
}
///
/// Adds the specified node.
///
/// The node that should be added.
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;
}
///
/// Removes the specified logic node.
///
/// The node that should be removed.
/// True if the node was removed.
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;
}
///
/// Adds the specified event node.
///
/// The event node that should be added.
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;
}
///
/// Removes the specified event node.
///
/// The event node that should be removed.
/// True if the event node was removed.
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;
}
///
/// Serializes the behavior tree.
///
public void Serialize()
{
if (Application.isPlaying) {
return;
}
m_TaskData = Serialization.Serialize(m_Tasks, ValidateSerializedObject);
m_EventTaskData = Serialization.Serialize(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(m_LogicNodeProperties);
m_EventNodePropertiesData = Serialization.Serialize(m_EventNodeProperties);
m_GroupPropertiesData = Serialization.Serialize(m_GroupProperties);
#endif
}
///
/// Validates the serialized object.
///
/// The type of object.
/// The field that the object belongs to.
/// The value of the object
/// The validated object.
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() == 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() == 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 };
}
///
/// Serializes the SharedVariables. This allows the SharedVariables to be serialized independently.
///
public void SerializeSharedVariables()
{
if (Application.isPlaying) {
return;
}
m_SharedVariableData = Serialization.Serialize(m_SharedVariables);
#if UNITY_EDITOR
m_SharedVariableGroupsData = Serialization.Serialize(m_SharedVariableGroups);
#endif
// Update the mapping for any variable name changes.
if (m_VariableByNameMap == null) {
m_VariableByNameMap = new Dictionary();
} 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]);
}
}
}
///
/// Deserialize the behavior tree.
///
/// The component that the graph is being deserialized from.
/// The graph that is being deserialized.
/// Should the behavior tree be force deserialized?
/// Should the shared variables be force deserialized?
/// Should the subtrees be injected into the behavior tree?
/// Can the SharedVariables be deep copied?
/// A list of SharedVariables that should override the current SharedVariable value.
/// True if the tree was deserialized.
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;
}
///
/// Internal method which deserialize the behavior tree.
///
/// The component that the graph is being deserialized from.
/// The graph that is being deserialized.
/// Should the behavior tree be force deserialized?
/// Should the shared variables be force deserialized?
/// Should the subtrees be injected into the behavior tree?
/// Can the SharedVariables be deep copied?
/// A list of SharedVariables that should override the current SharedVariable value.
/// True if the tree was deserialized.
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 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(); }
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(); }
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(); ; }
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();
} 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(); }
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(); }
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;
}
///
/// Validates the object type when deserializing.
///
/// The type of object that should be validated.
/// The field that contains the object.
/// The validated type.
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() == null)) {
return typeof(ushort[]);
}
} else if (typeof(ILogicNode).IsAssignableFrom(type) && (field == null || field.GetCustomAttribute() == null)) {
return typeof(ushort);
}
var formerlySerializedType = field?.GetCustomAttribute(false);
if (formerlySerializedType != null) {
return formerlySerializedType.Type;
}
return type;
}
///
/// Validates the object when deserializing.
///
/// The FieldInfo that is being deserialized.
/// The object being deserialized.
/// The value of the field.
/// A reference to the map between the VariableAssignment and SharedVariable.
/// A reference to the list of task references that need to be resolved later.
/// A list of SharedVariables that should override the current SharedVariable value.
/// The validated object.
public static object ValidateDeserializedObject(object fieldInfoObj, object target, object value, ref Dictionary variableByNameMap,
ref ResizableArray 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() == null) {
// The task reference will be assigned after all of the tasks have been deserialized.
if (taskReferences == null) { taskReferences = new ResizableArray(); }
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() == null) {
// The task reference will be assigned after all of the tasks have been deserialized.
if (taskReferences == null) { taskReferences = new ResizableArray(); }
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;
}
///
/// Creates a SharedVariable from a legacy non-shared serialized value.
///
/// The current SharedVariable field type.
/// The legacy serialized value.
/// The wrapped SharedVariable, or null if the value could not be converted.
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;
}
}
///
/// Deserializes the SharedVariables. This allows the SharedVariables to be deserialized independently.
///
/// The graph that is being deserialized.
/// Should the variables be forced deserialized?
/// Can the SharedVariables be deep copied?
/// A list of SharedVariables that should override the current SharedVariable value.
/// True if the SharedVariables were deserialized.
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;
}
///
/// Returns the override SharedVariable from the source SharedVariable.
///
/// The list of override SharedVariables.
/// The variable that should be overridden.
/// Is the method being called when the variables are being deserialized?
/// The override SharedVariable (can be null).
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;
}
///
/// Populates the SharedVariable Mapping at runtime.
///
/// The graph that is being deserialized.
/// Can the SharedVariables be deep copied?
/// A reference to the map between the VariableAssignment and SharedVariable.
public static Dictionary PopulateSharedVariablesMapping(IGraph graph, bool canDeepCopy)
{
return PopulateSharedVariablesMapping(graph, graph.SharedVariables, canDeepCopy);
}
///
/// Populates the SharedVariable Mapping at runtime.
///
/// The graph that is being deserialized.
/// The SharedVariables that should be used for graph scope variables.
/// Can the SharedVariables be deep copied?
/// A reference to the map between the VariableAssignment and SharedVariable.
private static Dictionary PopulateSharedVariablesMapping(IGraph graph, SharedVariable[] graphSharedVariables, bool canDeepCopy)
{
var variableByNameMap = new Dictionary();
PopulateSharedVariablesMapping(graph, graphSharedVariables, SharedVariable.SharingScope.Graph, canDeepCopy, ref variableByNameMap);
if (graph.Parent is GameObject parentGameObject) {
var gameObjectSharedVariablesContainer = parentGameObject.GetComponent();
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;
}
///
/// Populates the name variables mapping with the specified SharedVariables.
///
/// The graph that is being deserialized.
/// The SharedVariables that should be populated.
/// The scope of SharedVariables.
/// Can the SharedVariables be deep copied?
/// A reference to the map between the VariableAssignment and SharedVariable.
private static void PopulateSharedVariablesMapping(IGraph graph, SharedVariable[] sharedVariables, SharedVariable.SharingScope scope, bool canDeepCopy, ref Dictionary 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]);
}
}
///
/// Injects the subtree into the task list.
///
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();
var eventAssignments = new ResizableArray();
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);
}
///
/// Injects subtree logic nodes into the task list.
///
/// The subtree assignment.
/// The subtree nodes to inject.
/// Is the subtree pooled?
/// The index to start inserting nodes at.
/// The parent index for the root node.
/// The sibling index for the root node.
/// Is the subtree reference enabled?
/// Should node properties be offset to match the reference?
/// Should a node map be built for event node remapping?
/// A map of original nodes to copied nodes (can be null).
private Dictionary