using UnityEngine; using System.Collections; using System.Collections.Generic; using Pathfinding.WindowsStore; using Pathfinding.Serialization; using Pathfinding.Util; using Pathfinding.Sync; namespace Pathfinding { [System.Serializable] /// /// Stores the navigation graphs for the A* Pathfinding System. /// /// An instance of this class is assigned to . From it you can access all graphs loaded through the variable. /// This class also handles a lot of the high level serialization. /// public class AstarData { /// The AstarPath component which owns this AstarData AstarPath active; #region Fields /// /// Shortcut to the first /// /// Deprecated: Use instead /// [System.Obsolete("Use navmeshGraph instead")] public NavMeshGraph navmesh => navmeshGraph; /// Shortcut to the first public NavMeshGraph navmeshGraph { get; private set; } #if !ASTAR_NO_GRID_GRAPH /// Shortcut to the first public GridGraph gridGraph { get; private set; } /// Shortcut to the first . public LayerGridGraph layerGridGraph { get; private set; } #endif #if !ASTAR_NO_POINT_GRAPH /// Shortcut to the first . public PointGraph pointGraph { get; private set; } #endif /// Shortcut to the first . public RecastGraph recastGraph { get; private set; } /// Shortcut to the first . public LinkGraph linkGraph { get; private set; } /// /// All supported graph types. /// Populated through reflection search /// public static System.Type[] graphTypes { get; private set; } #if ASTAR_FAST_NO_EXCEPTIONS || UNITY_WINRT /// /// Graph types to use when building with Fast But No Exceptions for iPhone. /// If you add any custom graph types, you need to add them to this hard-coded list. /// public static readonly System.Type[] DefaultGraphTypes = new System.Type[] { #if !ASTAR_NO_GRID_GRAPH typeof(GridGraph), typeof(LayerGridGraph), #endif #if !ASTAR_NO_POINT_GRAPH typeof(PointGraph), #endif typeof(NavMeshGraph), typeof(RecastGraph), typeof(LinkGraph), }; #endif /// /// All graphs. /// This will be filled only after deserialization has completed. /// May contain null entries if graph have been removed. /// [System.NonSerialized] public NavGraph[] graphs = new NavGraph[0]; /// /// Serialized data for all graphs and settings. /// Stored as a base64 encoded string because otherwise Unity's Undo system would sometimes corrupt the byte data (because it only stores deltas). /// /// This can be accessed as a byte array from the property. /// [SerializeField] string dataString; /// Serialized data for all graphs and settings private byte[] data { get { var d = dataString != null? System.Convert.FromBase64String(dataString) : null; // Unity can initialize the dataString to an empty string, but that's not a valid zip file if (d != null && d.Length == 0) return null; return d; } set { dataString = value != null? System.Convert.ToBase64String(value) : null; } } /// /// Serialized data for cached startup. /// If set, and is enabled, graphs will be deserialized from this file when the game starts. /// /// [Open online documentation to see images] /// public TextAsset file_cachedStartup; /// /// Should graph-data be cached. /// Caching the startup means saving the whole graphs - not only the settings - to a file ( which can /// be loaded when the game starts. This is usually much faster than scanning the graphs when the game starts. This is configured from the editor under the "Save & Load" tab. /// /// [Open online documentation to see images] /// /// See: save-load-graphs (view in online documentation for working links) /// [SerializeField] public bool cacheStartup; List graphStructureLocked = new List(); static readonly Unity.Profiling.ProfilerMarker MarkerLoadFromCache = new Unity.Profiling.ProfilerMarker("LoadFromCache"); static readonly Unity.Profiling.ProfilerMarker MarkerDeserializeGraphs = new Unity.Profiling.ProfilerMarker("DeserializeGraphs"); static readonly Unity.Profiling.ProfilerMarker MarkerSerializeGraphs = new Unity.Profiling.ProfilerMarker("SerializeGraphs"); static readonly Unity.Profiling.ProfilerMarker MarkerFindGraphTypes = new Unity.Profiling.ProfilerMarker("FindGraphTypes"); #endregion internal AstarData (AstarPath active) { this.active = active; } /// Get the serialized data for all graphs and their settings public byte[] GetData() => data; /// /// Set the serialized data for all graphs and their settings. /// /// During runtime you usually want to deserialize the graphs immediately, in which case you should use []) instead. /// public void SetData (byte[] data) { this.data = data; } /// Loads the graphs from memory, will load cached graphs if any exists public void OnEnable () { FindGraphTypes(); if (graphs == null) graphs = new NavGraph[0]; if (cacheStartup && file_cachedStartup != null && Application.isPlaying) { LoadFromCache(); } else { DeserializeGraphs(); } } /// /// Prevent the graph structure from changing during the time this lock is held. /// This prevents graphs from being added or removed and also prevents graphs from being serialized or deserialized. /// This is used when e.g an async scan is happening to ensure that for example a graph that is being scanned is not destroyed. /// /// Each call to this method *must* be paired with exactly one call to . /// The calls may be nested. /// internal void LockGraphStructure (bool allowAddingGraphs = false) { graphStructureLocked.Add(allowAddingGraphs); } /// /// Allows the graph structure to change again. /// See: /// internal void UnlockGraphStructure () { if (graphStructureLocked.Count == 0) throw new System.InvalidOperationException(); graphStructureLocked.RemoveAt(graphStructureLocked.Count - 1); } PathProcessor.GraphUpdateLock AssertSafe (bool onlyAddingGraph = false) { if (graphStructureLocked.Count > 0) { bool allowAdding = true; for (int i = 0; i < graphStructureLocked.Count; i++) allowAdding &= graphStructureLocked[i]; if (!(onlyAddingGraph && allowAdding)) throw new System.InvalidOperationException("Graphs cannot be added, removed or serialized while the graph structure is locked. This is the case when a graph is currently being scanned and when executing graph updates and work items.\nHowever as a special case, graphs can be added inside work items."); } // Pause the pathfinding threads var graphLock = active.PausePathfinding(); if (!active.IsInsideWorkItem) { // Make sure all graph updates and other callbacks are done // Only do this if this code is not being called from a work item itself as that would cause a recursive wait that could never complete. // There are some valid cases when this can happen. For example it may be necessary to add a new graph inside a work item. active.FlushWorkItems(); // Paths that are already calculated and waiting to be returned to the Seeker component need to be // processed immediately as their results usually depend on graphs that currently exist. If this was // not done then after destroying a graph one could get a path result with destroyed nodes in it. active.pathReturnQueue.ReturnPaths(false); } return graphLock; } /// /// Calls the callback with every node in all graphs. /// This is the easiest way to iterate through every existing node. /// /// /// AstarPath.active.data.GetNodes(node => { /// Debug.Log("I found a node at position " + (Vector3)node.position); /// }); /// /// /// See: for getting the nodes of a single graph instead of all. /// See: graph-updates (view in online documentation for working links) /// public void GetNodes (System.Action callback) { for (int i = 0; i < graphs.Length; i++) { if (graphs[i] != null) graphs[i].GetNodes(callback); } } /// /// Updates shortcuts to the first graph of different types. /// Hard coding references to some graph types is not really a good thing imo. I want to keep it dynamic and flexible. /// But these references ease the use of the system, so I decided to keep them. /// public void UpdateShortcuts () { navmeshGraph = (NavMeshGraph)FindGraphOfType(typeof(NavMeshGraph)); #if !ASTAR_NO_GRID_GRAPH gridGraph = (GridGraph)FindGraphOfType(typeof(GridGraph)); layerGridGraph = (LayerGridGraph)FindGraphOfType(typeof(LayerGridGraph)); #endif #if !ASTAR_NO_POINT_GRAPH pointGraph = (PointGraph)FindGraphOfType(typeof(PointGraph)); #endif recastGraph = (RecastGraph)FindGraphOfType(typeof(RecastGraph)); linkGraph = (LinkGraph)FindGraphOfType(typeof(LinkGraph)); } /// Load from data from public void LoadFromCache () { using var _ = MarkerLoadFromCache.Auto(); using (AssertSafe()) { if (file_cachedStartup != null) { var bytes = file_cachedStartup.bytes; DeserializeGraphs(bytes); GraphModifier.TriggerEvent(GraphModifier.EventType.PostCacheLoad); } else { Debug.LogError("Can't load from cache since the cache is empty"); } } } #region Serialization /// /// Serializes all graphs settings to a byte array. /// See: DeserializeGraphs(byte[]) /// public byte[] SerializeGraphs () { return SerializeGraphs(SerializeSettings.Settings); } /// /// Serializes all graphs settings and optionally node data to a byte array. /// See: DeserializeGraphs(byte[]) /// See: Pathfinding.Serialization.SerializeSettings /// public byte[] SerializeGraphs (SerializeSettings settings) { return SerializeGraphs(settings, out var _); } /// /// Main serializer function. /// Serializes all graphs to a byte array /// A similar function exists in the AstarPathEditor.cs script to save additional info /// public byte[] SerializeGraphs (SerializeSettings settings, out uint checksum) { return SerializeGraphs(settings, out checksum, graphs); } byte[] SerializeGraphs (SerializeSettings settings, out uint checksum, NavGraph[] graphs) { MarkerSerializeGraphs.Begin(); using (AssertSafe()) { var sr = new AstarSerializer(this, settings, active.gameObject); sr.OpenSerialize(); sr.SerializeGraphs(graphs); sr.SerializeExtraInfo(); byte[] bytes = sr.CloseSerialize(); checksum = sr.GetChecksum(); #if ASTARDEBUG Debug.Log("Got a whole bunch of data, "+bytes.Length+" bytes"); #endif MarkerSerializeGraphs.End(); return bytes; } } /// Deserializes graphs from public void DeserializeGraphs () { var dataBytes = data; if (dataBytes != null) { DeserializeGraphs(dataBytes); } } /// /// Destroys all graphs and sets to null. /// See: /// public void ClearGraphs () { using (AssertSafe()) { ClearGraphsInternal(); } } void ClearGraphsInternal () { if (graphs == null) return; using (AssertSafe()) { for (int i = 0; i < graphs.Length; i++) { if (graphs[i] != null) { active.DirtyBounds(graphs[i].bounds); ((IGraphInternals)graphs[i]).OnDestroy(); graphs[i].active = null; } } graphs = new NavGraph[0]; UpdateShortcuts(); } } public void DisposeUnmanagedData () { if (graphs == null) return; using (AssertSafe()) { for (int i = 0; i < graphs.Length; i++) { if (graphs[i] != null) { ((IGraphInternals)graphs[i]).DisposeUnmanagedData(); } } } } /// Makes all graphs become unscanned internal void DestroyAllNodes () { if (graphs == null) return; using (AssertSafe()) { for (int i = 0; i < graphs.Length; i++) { if (graphs[i] != null) { ((IGraphInternals)graphs[i]).DestroyAllNodes(); } } } } public void OnDestroy () { ClearGraphsInternal(); } /// /// Deserializes and loads graphs from the specified byte array. /// An error will be logged if deserialization fails. /// /// Returns: The deserialized graphs /// public NavGraph[] DeserializeGraphs (byte[] bytes) { using (AssertSafe()) { ClearGraphs(); return DeserializeGraphsAdditive(bytes); } } /// /// Deserializes and loads graphs from the specified byte array additively. /// An error will be logged if deserialization fails. /// This function will add loaded graphs to the current ones. /// /// Returns: The deserialized graphs /// public NavGraph[] DeserializeGraphsAdditive (byte[] bytes) { return DeserializeGraphsAdditive(bytes, true); } NavGraph[] DeserializeGraphsAdditive (byte[] bytes, bool warnIfDuplicateGuids) { using (AssertSafe()) { try { MarkerDeserializeGraphs.Begin(); NavGraph[] result; if (bytes != null) { var sr = new AstarSerializer(this, active.gameObject); if (sr.OpenDeserialize(bytes)) { result = DeserializeGraphsPartAdditive(sr, warnIfDuplicateGuids); sr.CloseDeserialize(); } else { throw new System.ArgumentException("Invalid data file (cannot read zip).\nThe data is either corrupt or it was saved using a 3.0.x or earlier version of the system"); } } else { throw new System.ArgumentNullException(nameof(bytes)); } UpdateShortcuts(); GraphModifier.TriggerEvent(GraphModifier.EventType.PostGraphLoad); return result; } catch (System.Exception e) { Debug.LogException(new System.Exception("Caught exception while deserializing data.", e)); graphs = new NavGraph[0]; UpdateShortcuts(); throw; } finally { MarkerDeserializeGraphs.End(); } } } /// Helper function for deserializing graphs NavGraph[] DeserializeGraphsPartAdditive (AstarSerializer sr, bool warnIfDuplicateGuids) { if (graphs == null) graphs = new NavGraph[0]; var gr = new List(graphs); // Trim nulls at the end while (gr.Count > 0 && gr[gr.Count-1] == null) gr.RemoveAt(gr.Count-1); FindGraphTypes(); // This may be false if the user is editing a prefab, for example. // If it is false, we must not try to load any nodes bool astarInitialized = active == AstarPath.active; int lastUsedGraphIndex = -1; var newGraphs = sr.DeserializeGraphs(graphTypes, astarInitialized, () => { // Find the index to insert the new graph at // This is the first index which is not yet filled with a graph lastUsedGraphIndex++; while (lastUsedGraphIndex < gr.Count && gr[lastUsedGraphIndex] != null) { lastUsedGraphIndex++; } return lastUsedGraphIndex; }); for (int i = 0; i < newGraphs.Length; i++) { while (gr.Count < (int)newGraphs[i].graphIndex + 1) gr.Add(null); gr[(int)newGraphs[i].graphIndex] = newGraphs[i]; } if (gr.Count > GraphNode.MaxGraphIndex + 1) { throw new System.InvalidOperationException("Graph Count Limit Reached. You cannot have more than " + GraphNode.MaxGraphIndex + " graphs."); } graphs = gr.ToArray(); // Assign correct graph indices. bool anyScanned = false; for (int i = 0; i < graphs.Length; i++) { if (graphs[i] == null) continue; graphs[i].GetNodes(node => node.GraphIndex = (uint)i); anyScanned |= graphs[i].isScanned; } for (int i = 0; i < graphs.Length; i++) { for (int j = i+1; j < graphs.Length; j++) { if (graphs[i] != null && graphs[j] != null && graphs[i].guid == graphs[j].guid) { if (warnIfDuplicateGuids) Debug.LogWarning("Guid Conflict when importing graphs additively. Imported graph will get a new Guid.\nThis message is (relatively) harmless."); graphs[i].guid = Pathfinding.Util.Guid.NewGuid(); break; } } } sr.PostDeserialization(); if (anyScanned) { // This will refresh off-mesh links, // and also recalculate the hierarchical graph if necessary. // // It's important that this does not run if no graphs are scanned, // which is the case when just deserializing graph settings in the editor. // This is because we may be in a prefab, and prefabs should never be able // to actually load graphs with nodes. active.AddWorkItem(ctx => { for (int i = 0; i < newGraphs.Length; i++) { if (newGraphs[i].isScanned) { ctx.DirtyBounds(newGraphs[i].bounds); } } }); active.FlushWorkItems(); } return newGraphs; } #endregion /// /// Find all graph types supported in this build. /// Using reflection, the assembly is searched for types which inherit from NavGraph. /// public void FindGraphTypes () { if (graphTypes != null) return; MarkerFindGraphTypes.Begin(); #if !ASTAR_FAST_NO_EXCEPTIONS && !UNITY_WINRT graphTypes = AssemblySearcher.FindTypesInheritingFrom().ToArray(); #else graphTypes = DefaultGraphTypes; #endif MarkerFindGraphTypes.End(); } #region GraphCreation /// Creates a new graph instance of type type internal NavGraph CreateGraph (System.Type type) { var graph = System.Activator.CreateInstance(type) as NavGraph; graph.active = active; return graph; } /// /// Adds a graph of type T to the array. /// See: runtime-graphs (view in online documentation for working links) /// public T AddGraph () where T : NavGraph => AddGraph(typeof(T)) as T; /// /// Adds a graph of type type to the array. /// See: runtime-graphs (view in online documentation for working links) /// public NavGraph AddGraph (System.Type type) { NavGraph graph = null; for (int i = 0; i < graphTypes.Length; i++) { if (System.Type.Equals(graphTypes[i], type)) { graph = CreateGraph(graphTypes[i]); } } if (graph == null) { Debug.LogError("No NavGraph of type '"+type+"' could be found, "+graphTypes.Length+" graph types are avaliable"); return null; } AddGraph(graph); return graph; } /// Adds the specified graph to the array void AddGraph (NavGraph graph) { // Make sure to not interfere with pathfinding using (AssertSafe(true)) { // Try to fill in an empty position int graphIndex = System.Array.IndexOf(graphs, null); if (graphIndex == -1) { if (graphs.Length >= GraphNode.MaxGraphIndex) { throw new System.Exception($"Graph Count Limit Reached. You cannot have more than {GraphNode.MaxGraphIndex} graphs."); } // Add a new entry Memory.Realloc(ref graphs, graphs.Length + 1); graphIndex = graphs.Length-1; } graphs[graphIndex] = graph; graph.graphIndex = (uint)graphIndex; graph.active = active; UpdateShortcuts(); } } /// /// Removes the specified graph from the array and Destroys it in a safe manner. /// To avoid changing graph indices for the other graphs, the graph is simply nulled in the array instead /// of actually removing it from the array. /// The empty position will be reused if a new graph is added. /// /// Returns: True if the graph was sucessfully removed (i.e it did exist in the array). False otherwise. /// /// See: /// public bool RemoveGraph (NavGraph graph) { // Make sure the pathfinding threads are paused using (AssertSafe()) { active.DirtyBounds(graph.bounds); ((IGraphInternals)graph).OnDestroy(); graph.active = null; int i = System.Array.IndexOf(graphs, graph); if (i != -1) graphs[i] = null; UpdateShortcuts(); // If we are working on a prefab, this may not be true if (AstarPath.active == active) { active.AddWorkItem(() => active.offMeshLinks.Refresh()); active.FlushWorkItems(); } return i != -1; } } /// /// Duplicates the given graph and adds the duplicate to the array. /// /// Note: Only graph settings are duplicated, not the nodes in the graph. You may want to scan the graph after duplicating it. /// /// Returns: The duplicated graph. /// public NavGraph DuplicateGraph (NavGraph graph) { if (graph == null) throw new System.ArgumentNullException(nameof(graph)); int i = System.Array.IndexOf(graphs, graph); if (i == -1) throw new System.ArgumentException("Graph doesn't exist"); var bytes = SerializeGraphs(SerializeSettings.Settings, out var _, new NavGraph[] { graph }); var newGraphs = DeserializeGraphsAdditive(bytes, false); UnityEngine.Assertions.Assert.AreEqual(1, newGraphs.Length); #if UNITY_EDITOR foreach (var g in newGraphs) { var existingNames = new string[graphs.Length]; for (int j = 0; j < graphs.Length; j++) existingNames[j] = graphs[j].name; g.name = UnityEditor.ObjectNames.GetUniqueName(existingNames, g.name); } #endif return newGraphs[0]; } #endregion #region GraphUtility /// /// Graph which contains the specified node. /// The graph must be in the array. /// /// Returns: Returns the graph which contains the node. Null if the graph wasn't found /// public static NavGraph GetGraph (GraphNode node) { if (node == null || node.Destroyed) return null; AstarPath script = AstarPath.active; if (System.Object.ReferenceEquals(script, null)) return null; AstarData data = script.data; if (data == null || data.graphs == null) return null; uint graphIndex = node.GraphIndex; return data.graphs[(int)graphIndex]; } /// Returns the first graph which satisfies the predicate. Returns null if no graph was found. public NavGraph FindGraph (System.Func predicate) { if (graphs != null) { for (int i = 0; i < graphs.Length; i++) { if (graphs[i] != null && predicate(graphs[i])) { return graphs[i]; } } } return null; } /// Returns the first graph of type type found in the array. Returns null if no graph was found. public NavGraph FindGraphOfType (System.Type type) { return FindGraph(graph => System.Type.Equals(graph.GetType(), type)); } /// Returns the first graph which inherits from the type type. Returns null if no graph was found. public NavGraph FindGraphWhichInheritsFrom (System.Type type) { return FindGraph(graph => WindowsStoreCompatibility.GetTypeInfo(type).IsAssignableFrom(WindowsStoreCompatibility.GetTypeInfo(graph.GetType()))); } /// /// Loop through this function to get all graphs of type 'type' /// /// foreach (GridGraph graph in AstarPath.data.FindGraphsOfType (typeof(GridGraph))) { /// //Do something with the graph /// } /// /// See: /// public IEnumerable FindGraphsOfType (System.Type type) { if (graphs == null) yield break; for (int i = 0; i < graphs.Length; i++) { if (graphs[i] != null && System.Type.Equals(graphs[i].GetType(), type)) { yield return graphs[i]; } } } /// /// All graphs which implements the UpdateableGraph interface /// foreach (IUpdatableGraph graph in AstarPath.data.GetUpdateableGraphs ()) { /// //Do something with the graph /// } /// See: /// See: /// public IEnumerable GetUpdateableGraphs () { if (graphs == null) yield break; for (int i = 0; i < graphs.Length; i++) { if (graphs[i] is IUpdatableGraph) { yield return graphs[i]; } } } /// Gets the index of the graph in the array public int GetGraphIndex (NavGraph graph) { if (graph == null) throw new System.ArgumentNullException("graph"); if (graphs == null) throw new System.ArgumentException("No graphs exist"); var index = System.Array.IndexOf(graphs, graph); if (index == -1) throw new System.ArgumentException("Graph doesn't exist"); return index; } #endregion } }