#if GRAPH_DESIGNER /// --------------------------------------------- /// Behavior Designer /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.BehaviorDesigner.Runtime.Utility { using Opsive.BehaviorDesigner.Runtime; using Opsive.BehaviorDesigner.Runtime.Components; using Opsive.BehaviorDesigner.Runtime.Tasks; using Opsive.GraphDesigner.Runtime.Variables; using Opsive.Shared.Utility; using System; using System.Collections; using System.Collections.Generic; using System.IO; using Unity.Entities; using UnityEngine; using static Opsive.BehaviorDesigner.Runtime.BehaviorTreeData; /// /// Helper class which will save and load behavior tree tasks. /// public static class SaveManager { /// /// Specifies which objects should be saved. /// public enum VariableSaveScope : byte { GameObjectVariables = 1, // Saves the GameObjectVariables. SceneVariables = 2, // Saves the SceneVariables. ProjectVariables = 4 // Saves the ProjectVariables. } /// /// The save data associated with the behavior tree and shared variables. /// [System.Serializable] public struct SaveData { [Tooltip("The behavior tree save data.")] public BehaviorTreeSaveData[] BehaviorTreeSaveData; [Tooltip("The external shared variable save data.")] public VariableSaveData[] VariableSaveData; } /// /// The save data associated with the behavior tree. /// [System.Serializable] public struct BehaviorTreeSaveData { [Tooltip("The unique ID of the save data. This will change each time the data is serialized")] public int UniqueID; [Tooltip("The loaded task components.")] public Serialization[] TaskComponents; [Tooltip("The loaded branch components.")] public Serialization[] BranchComponents; [Tooltip("The loaded reevaluate task components.")] public Serialization[] ReevaluateTaskComponents; [Tooltip("The user task data.")] public TaskSaveData[] TaskData; [Tooltip("The values of the graph SharedVariables.")] public VariableSaveData GraphSharedVariables; } /// /// The save data associated with the shared variables. /// [System.Serializable] public struct VariableSaveData { [Tooltip("The unique ID of the variable data. This will change each time the data is serialized")] public int UniqueID; [Tooltip("The values of the SharedVariables.")] public Serialization[] Values; [Tooltip("The scope of the variables.")] public SharedVariable.SharingScope Scope; } /// /// Container for the task save data. Allows for nested tasks. /// [System.Serializable] public struct TaskSaveData { [Tooltip("The save data associated with each task.")] public Serialization[] Value; } /// /// Gets the save data from the specified behavior trees. /// /// The behavior trees that should be saved. /// Specifies which variables should be saved. Graph variables will automatically be saved. /// The resulting save data. Can be null. public static SaveData? Save(BehaviorTree[] behaviorTrees, VariableSaveScope variableSaveScope = 0) { if (!Application.isPlaying) { Debug.LogWarning($"Warning: Behavior trees can only be saved at runtime."); return null; } if (behaviorTrees.Length == 0) { return null; } // Assume all behavior trees can be saved. var saveData = new SaveData(); saveData.BehaviorTreeSaveData = new BehaviorTreeSaveData[behaviorTrees.Length]; var variableSaveDataList = new List(); HashSet savedGameObjectVariables = null; var behaviorTreeSaveCount = 0; for (int i = 0; i < behaviorTrees.Length; ++i) { if (behaviorTrees[i] == null) { continue; } // Add the save data to the array if the save is valid. var behaviorTreeSaveData = Save(behaviorTrees[i]); if (behaviorTreeSaveData.HasValue) { saveData.BehaviorTreeSaveData[behaviorTreeSaveCount] = behaviorTreeSaveData.Value; behaviorTreeSaveCount++; // The associated GameObject variables may also need to be saved. if ((variableSaveScope & VariableSaveScope.GameObjectVariables) != 0) { // Only save the GameObject variables once. if (savedGameObjectVariables == null) { savedGameObjectVariables = new HashSet(); } if (savedGameObjectVariables.Contains(behaviorTrees[i].gameObject)) { continue; } savedGameObjectVariables.Add(behaviorTrees[i].gameObject); // The GameObject variables can be saved. var gameObjectSharedVariables = behaviorTrees[i].gameObject.GetComponent(); if (gameObjectSharedVariables != null) { var variableSaveData = Save(gameObjectSharedVariables); if (variableSaveData.HasValue) { variableSaveDataList.Add(variableSaveData.Value); } } } } } // Not all behavior trees can be saved. if (behaviorTrees.Length != behaviorTreeSaveCount) { var behaviorTreesSaveData = saveData.BehaviorTreeSaveData; System.Array.Resize(ref behaviorTreesSaveData, behaviorTreeSaveCount); saveData.BehaviorTreeSaveData = behaviorTreesSaveData; } // The scene variables can be saved. if ((variableSaveScope & VariableSaveScope.SceneVariables) != 0) { if (SceneSharedVariables.Instance != null) { var variableSaveData = Save(SceneSharedVariables.Instance); if (variableSaveData.HasValue) { variableSaveDataList.Add(variableSaveData.Value); } } } // The project variables can be saved. if ((variableSaveScope & VariableSaveScope.ProjectVariables) != 0) { if (ProjectSharedVariables.Instance != null) { var variableSaveData = Save(ProjectSharedVariables.Instance); if (variableSaveData.HasValue) { variableSaveDataList.Add(variableSaveData.Value); } } } // There has to be something to be saved. if (behaviorTreeSaveCount == 0 && variableSaveDataList.Count == 0) { return null; } // Persist the variable save data. if (variableSaveDataList != null && variableSaveDataList.Count > 0) { saveData.VariableSaveData = variableSaveDataList.ToArray(); } return saveData; } /// /// Saves the save data from the specified behavior trees to the specified file. /// /// The behavior trees that should be saved. /// The save data path. /// Specifies which variables should be saved. Graph variables will automatically be saved. /// True if at least one behavior tree's save data was saved. public static bool Save(BehaviorTree[] behaviorTrees, string filePath, VariableSaveScope variableSaveScope = 0) { var saveData = Save(behaviorTrees, variableSaveScope); if (!saveData.HasValue) { return false; } // Save the save data. if (File.Exists(filePath)) { File.Delete(filePath); } try { if (!Directory.Exists(Path.GetDirectoryName(filePath))) { Directory.CreateDirectory(Path.GetDirectoryName(filePath)); } var fileStream = File.Create(filePath); using (var streamWriter = new StreamWriter(fileStream)) { streamWriter.Write(JsonUtility.ToJson(saveData)); } fileStream.Close(); } catch (System.Exception e) { Debug.LogException(e); return false; } return true; } /// /// Returns the save data associated with the behavior tree. /// /// The behavior tree that should be saved. /// The save data associated with the behavior tree. private static BehaviorTreeSaveData? Save(BehaviorTree behaviorTree) { if (behaviorTree.Entity.Index == 0) { Debug.LogWarning($"Warning: The behavior tree {behaviorTree.name} must be active in order to be saved.", behaviorTree); return null; } if (behaviorTree.LogicNodes == null || behaviorTree.LogicNodes.Length == 0) { Debug.LogWarning($"Warning: The behavior tree {behaviorTree.name} must have tasks in order to be saved.", behaviorTree); return null; } // The current task status must be serialized. var taskComponents = behaviorTree.World.EntityManager.GetBuffer(behaviorTree.Entity); var serializedTaskComponents = new Serialization[taskComponents.Length]; for (int i = 0; i < taskComponents.Length; ++i) { serializedTaskComponents[i] = Serialization.Serialize(taskComponents[i]); } // The branch info must be serialized. var branchComponents = behaviorTree.World.EntityManager.GetBuffer(behaviorTree.Entity); var serializedBranchComponents = new Serialization[branchComponents.Length]; for (int i = 0; i < branchComponents.Length; ++i) { serializedBranchComponents[i] = Serialization.Serialize(branchComponents[i]); } // The reevaluation status must be serialized. Serialization[] serializedReevaluateTaskComponents = null; if (behaviorTree.World.EntityManager.HasBuffer(behaviorTree.Entity)) { var reevaluateTaskComponents = behaviorTree.World.EntityManager.GetBuffer(behaviorTree.Entity); serializedReevaluateTaskComponents = new Serialization[reevaluateTaskComponents.Length]; for (int i = 0; i < reevaluateTaskComponents.Length; ++i) { serializedReevaluateTaskComponents[i] = Serialization.Serialize(reevaluateTaskComponents[i]); } } // Each task can serialize their own data. var tasks = behaviorTree.LogicNodes; var serializedTaskData = new List(); for (int i = 0; i < tasks.Length; ++i) { if (!(tasks[i] is ISavableTask)) { continue; } var saveableTask = tasks[i] as ISavableTask; var reflectionType = saveableTask.GetSaveReflectionType(-1); if (reflectionType != MemberVisibility.None) { var serializedData = Serialization.Serialize(saveableTask, reflectionType, BehaviorTreeData.ValidateSerializedObject); serializedTaskData.Add(new TaskSaveData() { Value = new Serialization[] { serializedData } }); } else { var taskData = saveableTask.Save(behaviorTree.World, behaviorTree.Entity); if (taskData == null) { continue; } // Tasks can contain more tasks. Serialize each task element separately. if (typeof(IList).IsAssignableFrom(taskData.GetType())) { var taskDataList = taskData as IList; var taskSaveData = new Serialization[taskDataList.Count]; for (int j = 0; j < taskDataList.Count; ++j) { if (taskDataList[j] is Serialization) { taskSaveData[j] = taskDataList[j] as Serialization; } else { taskSaveData[j] = Serialization.Serialize(taskDataList[j]); } } serializedTaskData.Add(new TaskSaveData() { Value = taskSaveData }); } else { var serializedValue = new Serialization[] { (taskData is Serialization) ? (taskData as Serialization) : Serialization.Serialize(taskData) }; serializedTaskData.Add(new TaskSaveData() { Value = serializedValue }); } } } var behaviorTreeSaveData = new BehaviorTreeSaveData() { UniqueID = behaviorTree.UniqueID, TaskComponents = serializedTaskComponents, BranchComponents = serializedBranchComponents, ReevaluateTaskComponents = serializedReevaluateTaskComponents, TaskData = serializedTaskData.ToArray(), }; var variableSaveData = Save(behaviorTree as ISharedVariableContainer); if (variableSaveData.HasValue) { behaviorTreeSaveData.GraphSharedVariables = variableSaveData.Value; } return behaviorTreeSaveData; } /// /// Returns the save data associated with the variable container. /// /// The variable container that should be saved. /// The save data associated with the variable container. private static VariableSaveData? Save(ISharedVariableContainer variableContainer) { if (variableContainer == null) { return null; } var sharedVariables = variableContainer.SharedVariables; if (sharedVariables == null || sharedVariables.Length == 0) { return null; } Serialization[] serializedSharedVariablesData = null; if (sharedVariables != null) { serializedSharedVariablesData = new Serialization[sharedVariables.Length]; for (int i = 0; i < sharedVariables.Length; ++i) { serializedSharedVariablesData[i] = Serialization.Serialize(sharedVariables[i].GetValue(), BehaviorTreeData.ValidateSerializedObject); } } return new VariableSaveData() { UniqueID = variableContainer.UniqueID, Values = serializedSharedVariablesData, Scope = variableContainer.VariableScope }; } /// /// Loads the save data contained within the specified file. /// /// The behavior trees that should be loaded. /// The save data path. /// Optional callback after the graph variables have been restored. /// True if at least one behavior tree's save data was loaded. public static bool Load(BehaviorTree[] behaviorTrees, string filePath, Action afterVariablesRestored = null) { if (!Application.isPlaying) { Debug.LogWarning($"Warning: Behavior trees can only be loaded at runtime."); return false; } if (!File.Exists(filePath)) { Debug.LogWarning($"Warning: The file at path {filePath} does not exist."); return false; } var fileStream = File.Open(filePath, FileMode.Open); var saveData = new SaveData(); using (var streamReader = new StreamReader(fileStream)) { var fileData = streamReader.ReadToEnd(); saveData = JsonUtility.FromJson(fileData); } fileStream.Close(); return Load(behaviorTrees, saveData, afterVariablesRestored); } /// /// Loads the save data. /// /// The behavior trees that should be loaded. /// The loaded save data. /// Optional callback after the graph variables have been restored. /// True if at least one behavior tree's save data was loaded. public static bool Load(BehaviorTree[] behaviorTrees, SaveData saveData, Action afterVariablesRestored = null) { // Load the shared variables before the behavior trees so the trees can reference the variables. var loadedSceneVariables = false; var loadedProjectVariables = false; var loadCount = 0; Dictionary variableSaveDataByID = null; if (saveData.VariableSaveData != null && saveData.VariableSaveData.Length > 0) { // The save data is unique to the variable container specified by the unique ID. for (int i = 0; i < saveData.VariableSaveData.Length; ++i) { // Remember the scope to determine if the variable scope needs to be checked again when loading. if (saveData.VariableSaveData[i].Scope == SharedVariable.SharingScope.GameObject) { // The GameObject SharedVariables will be loaded with the behavior tree. if (variableSaveDataByID == null) { variableSaveDataByID = new Dictionary(); } variableSaveDataByID.Add(saveData.VariableSaveData[i].UniqueID, saveData.VariableSaveData[i]); } else if (saveData.VariableSaveData[i].Scope == SharedVariable.SharingScope.Scene) { // Restore the SceneSharedVariables if it hasn't already been loaded. if (!loadedSceneVariables) { if (SceneSharedVariables.Instance != null) { if (Load(SceneSharedVariables.Instance, saveData.VariableSaveData[i])) { loadCount++; } } loadedSceneVariables = true; } } else if (saveData.VariableSaveData[i].Scope == SharedVariable.SharingScope.Project) { // Restore the ProjectSharedVariables if it hasn't already been loaded. if (!loadedProjectVariables) { if (ProjectSharedVariables.Instance != null) { if (Load(ProjectSharedVariables.Instance, saveData.VariableSaveData[i])) { loadCount++; } } } } } } if (saveData.BehaviorTreeSaveData != null && saveData.BehaviorTreeSaveData.Length > 0) { // The save data is unique to the behavior tree specified by the unique ID. var behaviorTreeSaveDataByID = new Dictionary(); for (int i = 0; i < saveData.BehaviorTreeSaveData.Length; ++i) { behaviorTreeSaveDataByID.Add(saveData.BehaviorTreeSaveData[i].UniqueID, saveData.BehaviorTreeSaveData[i]); } // Load the save data for each behavior tree. for (int i = 0; i < behaviorTrees.Length; ++i) { var behaviorTree = behaviorTrees[i]; if (behaviorTreeSaveDataByID.TryGetValue(behaviorTree.UniqueID, out var behaviorTreeSaveData)) { // Restore the GameObjectSharedVariables if they haven't already been restored. if (variableSaveDataByID != null) { var gameObjectSharedVariables = behaviorTree.GetComponent(); if (gameObjectSharedVariables != null) { if (variableSaveDataByID.TryGetValue(gameObjectSharedVariables.UniqueID, out var variableSaveData)) { if (Load(gameObjectSharedVariables, variableSaveData)) { loadCount++; } // Remove the ID after it has been loaded so the variables aren't loaded again. This can happen if multiple // trees on the same GameObject reference the same GameObject variable. variableSaveDataByID.Remove(gameObjectSharedVariables.UniqueID); } } } // Restore the Graph SharedVariables. if (Load(behaviorTree, behaviorTreeSaveData.GraphSharedVariables)) { loadCount++; } // Callback after the variables have been restored. afterVariablesRestored?.Invoke(behaviorTree); // Populate the variables in an internal mapping for quick lookup. var variableByNameMap = BehaviorTreeData.PopulateSharedVariablesMapping(behaviorTree, true); // Restore the tree after the variables have been restored. if (Load(behaviorTree, behaviorTreeSaveData, afterVariablesRestored, variableByNameMap)) { loadCount++; } } } } return loadCount > 0; } /// /// Loads the behavior tree from the specified save data. /// /// The behavior tree that should be restored. /// The save data associated with the behavior tree. /// Optional callback after the graph variables have been restored. /// A mapping between the variable name and the variable reference. /// True if the behavior tree was successfully loaded. private static bool Load(BehaviorTree behaviorTree, BehaviorTreeSaveData saveData, Action afterVariablesRestored, Dictionary variableByNameMap) { // The ID must match. if (behaviorTree.UniqueID != saveData.UniqueID) { Debug.LogError($"Error: The behavior tree {behaviorTree.name} cannot be loaded due to being saved in a different version of the behavior tree."); return false; } // The behavior tree must be initialized in order to be loaded. if (!behaviorTree.InitializeTree()) { return false; } // Stop the behavior tree so all tasks issue their end callback. var enableEntity = behaviorTree.World.EntityManager.HasComponent(behaviorTree.Entity) && behaviorTree.World.EntityManager.IsComponentEnabled(behaviorTree.Entity); var evaluateEntity = behaviorTree.World.EntityManager.HasComponent(behaviorTree.Entity) && behaviorTree.World.EntityManager.IsComponentEnabled(behaviorTree.Entity); var active = behaviorTree.IsActive(); if (active) { behaviorTree.StopBehavior(); } // Restore the task component status. var taskComponents = behaviorTree.World.EntityManager.GetBuffer(behaviorTree.Entity); for (int i = 0; i < saveData.TaskComponents.Length; ++i) { taskComponents[i] = (TaskComponent)saveData.TaskComponents[i].DeserializeFields(MemberVisibility.Public); } var branchComponents = behaviorTree.World.EntityManager.GetBuffer(behaviorTree.Entity); // Restore the branch info components. for (int i = 0; i < saveData.BranchComponents.Length; ++i) { branchComponents[i] = (BranchComponent)saveData.BranchComponents[i].DeserializeFields(MemberVisibility.Public); if (branchComponents[i].ActiveTagComponentType.TypeIndex == TypeIndex.Null) { continue; } behaviorTree.World.EntityManager.SetComponentEnabled(behaviorTree.Entity, branchComponents[i].ActiveTagComponentType, true); } if (behaviorTree.World.EntityManager.HasBuffer(behaviorTree.Entity)) { var reevaluatedTaskComponents = behaviorTree.World.EntityManager.GetBuffer(behaviorTree.Entity); // Restore the reevaluated components. for (int i = 0; i < saveData.ReevaluateTaskComponents.Length; ++i) { reevaluatedTaskComponents[i] = (ReevaluateTaskComponent)saveData.ReevaluateTaskComponents[i].DeserializeFields(MemberVisibility.Public); if (reevaluatedTaskComponents[i].ReevaluateTagComponentType.TypeIndex == TypeIndex.Null) { continue; } behaviorTree.World.EntityManager.SetComponentEnabled(behaviorTree.Entity, reevaluatedTaskComponents[i].ReevaluateTagComponentType, true); } } // Each task can serialize their own data. var tasks = behaviorTree.LogicNodes; var saveDataIndex = 0; ResizableArray taskReferences = null; for (int i = 0; i < tasks.Length; ++i) { if (!(tasks[i] is ISavableTask)) { if (tasks[i] is Task) { (tasks[i] as Task).Initialize(behaviorTree, (ushort)i); } continue; } var taskSaveData = saveData.TaskData[saveDataIndex]; saveDataIndex++; if (taskSaveData.Value == null || taskSaveData.Value.Length == 0) { if (tasks[i] is Task) { (tasks[i] as Task).Initialize(behaviorTree, (ushort)i); } continue; } var saveableTask = tasks[i] as ISavableTask; var reflectionType = saveableTask.GetSaveReflectionType(-1); if (reflectionType != MemberVisibility.None) { taskSaveData.Value[0].DeserializeFields(saveableTask, saveableTask.GetSaveReflectionType(0), BehaviorTreeData.ValidateDeserializedTypeObject, (object fieldInfoObj, object task, object value) => { return BehaviorTreeData.ValidateDeserializedObject(fieldInfoObj, task, value, ref variableByNameMap, ref taskReferences); }); } else { if (taskSaveData.Value.Length == 1) { if (saveableTask.GetSaveReflectionType(0) != MemberVisibility.None) { saveableTask.Load(taskSaveData.Value[0], behaviorTree.World, behaviorTree.Entity, variableByNameMap, ref taskReferences); } else { saveableTask.Load(taskSaveData.Value[0].DeserializeFields(MemberVisibility.Public, BehaviorTreeData.ValidateDeserializedTypeObject, (object fieldInfoObj, object task, object value) => { return BehaviorTreeData.ValidateDeserializedObject(fieldInfoObj, task, value, ref variableByNameMap, ref taskReferences); }), behaviorTree.World, behaviorTree.Entity, variableByNameMap, ref taskReferences); } } else { var taskData = new object[taskSaveData.Value.Length]; for (int j = 0; j < taskSaveData.Value.Length; ++j) { if (taskSaveData.Value[j] == null) { continue; } if (saveableTask.GetSaveReflectionType(j) != MemberVisibility.None) { // The task is responsible for deserializing the fields. taskData[j] = taskSaveData.Value[j]; } else { taskData[j] = taskSaveData.Value[j].DeserializeFields(MemberVisibility.Public, BehaviorTreeData.ValidateDeserializedTypeObject, (object fieldInfoObj, object task, object value) => { return BehaviorTreeData.ValidateDeserializedObject(fieldInfoObj, task, value, ref variableByNameMap, ref taskReferences); }); } } saveableTask.Load(taskData, behaviorTree.World, behaviorTree.Entity, variableByNameMap, ref taskReferences); } } if (tasks[i] is Task) { (tasks[i] as Task).Initialize(behaviorTree, (ushort)i); } } // After the tree has been loaded the task references need to be assigned. BehaviorTreeData.AssignTaskReferences(behaviorTree.LogicNodes, taskReferences); if (active) { behaviorTree.StartBehavior(); } if (enableEntity) { behaviorTree.World.EntityManager.SetComponentEnabled(behaviorTree.Entity, true); } if (evaluateEntity) { behaviorTree.World.EntityManager.SetComponentEnabled(behaviorTree.Entity, true); } return true; } /// /// Loads the variable from the specified file path. /// /// The variable container that should be restored. /// The save data associated with the variable container. /// True if the variable container was successfully loaded. private static bool Load(ISharedVariableContainer variableContainer, VariableSaveData saveData) { // There may not be any variables saved. if (saveData.UniqueID == 0) { return false; } // The ID must match. if (variableContainer.UniqueID != saveData.UniqueID) { Debug.LogError($"Error: The variables {variableContainer} cannot be loaded due to being saved in a different version of the variable container."); return false; } var sharedVariables = variableContainer.SharedVariables; if (sharedVariables != null) { for (int i = 0; i < saveData.Values.Length; ++i) { if (saveData.Values[i] == null) { continue; } var sharedVariableValue = saveData.Values[i].DeserializeFields(MemberVisibility.Public); sharedVariables[i].SetValue(sharedVariableValue); } } return true; } } } #endif