733 lines
25 KiB (Stored with Git LFS)
C#
733 lines
25 KiB (Stored with Git LFS)
C#
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]
|
|
/// <summary>
|
|
/// Stores the navigation graphs for the A* Pathfinding System.
|
|
///
|
|
/// An instance of this class is assigned to <see cref="AstarPath.data"/>. From it you can access all graphs loaded through the <see cref="graphs"/> variable.
|
|
/// This class also handles a lot of the high level serialization.
|
|
/// </summary>
|
|
public class AstarData {
|
|
/// <summary>The AstarPath component which owns this AstarData</summary>
|
|
AstarPath active;
|
|
|
|
#region Fields
|
|
/// <summary>
|
|
/// Shortcut to the first <see cref="NavMeshGraph"/>
|
|
///
|
|
/// Deprecated: Use <see cref="navmeshGraph"/> instead
|
|
/// </summary>
|
|
[System.Obsolete("Use navmeshGraph instead")]
|
|
public NavMeshGraph navmesh => navmeshGraph;
|
|
|
|
/// <summary>Shortcut to the first <see cref="NavMeshGraph"/></summary>
|
|
public NavMeshGraph navmeshGraph { get; private set; }
|
|
|
|
#if !ASTAR_NO_GRID_GRAPH
|
|
/// <summary>Shortcut to the first <see cref="GridGraph"/></summary>
|
|
public GridGraph gridGraph { get; private set; }
|
|
|
|
/// <summary>Shortcut to the first <see cref="LayerGridGraph"/>.</summary>
|
|
public LayerGridGraph layerGridGraph { get; private set; }
|
|
#endif
|
|
|
|
#if !ASTAR_NO_POINT_GRAPH
|
|
/// <summary>Shortcut to the first <see cref="PointGraph"/>.</summary>
|
|
public PointGraph pointGraph { get; private set; }
|
|
#endif
|
|
|
|
/// <summary>Shortcut to the first <see cref="RecastGraph"/>.</summary>
|
|
public RecastGraph recastGraph { get; private set; }
|
|
|
|
/// <summary>Shortcut to the first <see cref="LinkGraph"/>.</summary>
|
|
public LinkGraph linkGraph { get; private set; }
|
|
|
|
/// <summary>
|
|
/// All supported graph types.
|
|
/// Populated through reflection search
|
|
/// </summary>
|
|
public static System.Type[] graphTypes { get; private set; }
|
|
|
|
#if ASTAR_FAST_NO_EXCEPTIONS || UNITY_WINRT
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// All graphs.
|
|
/// This will be filled only after deserialization has completed.
|
|
/// May contain null entries if graph have been removed.
|
|
/// </summary>
|
|
[System.NonSerialized]
|
|
public NavGraph[] graphs = new NavGraph[0];
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="data"/> property.
|
|
/// </summary>
|
|
[SerializeField]
|
|
string dataString;
|
|
|
|
/// <summary>Serialized data for all graphs and settings</summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialized data for cached startup.
|
|
/// If set, and <see cref="cacheStartup"/> is enabled, graphs will be deserialized from this file when the game starts.
|
|
///
|
|
/// [Open online documentation to see images]
|
|
/// </summary>
|
|
public TextAsset file_cachedStartup;
|
|
|
|
/// <summary>
|
|
/// Should graph-data be cached.
|
|
/// Caching the startup means saving the whole graphs - not only the settings - to a file (<see cref="file_cachedStartup)"/> 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)
|
|
/// </summary>
|
|
[SerializeField]
|
|
public bool cacheStartup;
|
|
|
|
List<bool> graphStructureLocked = new List<bool>();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>Get the serialized data for all graphs and their settings</summary>
|
|
public byte[] GetData() => data;
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="DeserializeGraphs(byte"/>[]) instead.
|
|
/// </summary>
|
|
public void SetData (byte[] data) {
|
|
this.data = data;
|
|
}
|
|
|
|
/// <summary>Loads the graphs from memory, will load cached graphs if any exists</summary>
|
|
public void OnEnable () {
|
|
FindGraphTypes();
|
|
|
|
if (graphs == null) graphs = new NavGraph[0];
|
|
|
|
if (cacheStartup && file_cachedStartup != null && Application.isPlaying) {
|
|
LoadFromCache();
|
|
} else {
|
|
DeserializeGraphs();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="UnlockGraphStructure"/>.
|
|
/// The calls may be nested.
|
|
/// </summary>
|
|
internal void LockGraphStructure (bool allowAddingGraphs = false) {
|
|
graphStructureLocked.Add(allowAddingGraphs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows the graph structure to change again.
|
|
/// See: <see cref="LockGraphStructure"/>
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls the callback with every node in all graphs.
|
|
/// This is the easiest way to iterate through every existing node.
|
|
///
|
|
/// <code>
|
|
/// AstarPath.active.data.GetNodes(node => {
|
|
/// Debug.Log("I found a node at position " + (Vector3)node.position);
|
|
/// });
|
|
/// </code>
|
|
///
|
|
/// See: <see cref="Pathfinding.NavGraph.GetNodes"/> for getting the nodes of a single graph instead of all.
|
|
/// See: graph-updates (view in online documentation for working links)
|
|
/// </summary>
|
|
public void GetNodes (System.Action<GraphNode> callback) {
|
|
for (int i = 0; i < graphs.Length; i++) {
|
|
if (graphs[i] != null) graphs[i].GetNodes(callback);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>Load from data from <see cref="file_cachedStartup"/></summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Serializes all graphs settings to a byte array.
|
|
/// See: DeserializeGraphs(byte[])
|
|
/// </summary>
|
|
public byte[] SerializeGraphs () {
|
|
return SerializeGraphs(SerializeSettings.Settings);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes all graphs settings and optionally node data to a byte array.
|
|
/// See: DeserializeGraphs(byte[])
|
|
/// See: Pathfinding.Serialization.SerializeSettings
|
|
/// </summary>
|
|
public byte[] SerializeGraphs (SerializeSettings settings) {
|
|
return SerializeGraphs(settings, out var _);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main serializer function.
|
|
/// Serializes all graphs to a byte array
|
|
/// A similar function exists in the AstarPathEditor.cs script to save additional info
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>Deserializes graphs from <see cref="data"/></summary>
|
|
public void DeserializeGraphs () {
|
|
var dataBytes = data;
|
|
if (dataBytes != null) {
|
|
DeserializeGraphs(dataBytes);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys all graphs and sets <see cref="graphs"/> to null.
|
|
/// See: <see cref="RemoveGraph"/>
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Makes all graphs become unscanned</summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes and loads graphs from the specified byte array.
|
|
/// An error will be logged if deserialization fails.
|
|
///
|
|
/// Returns: The deserialized graphs
|
|
/// </summary>
|
|
public NavGraph[] DeserializeGraphs (byte[] bytes) {
|
|
using (AssertSafe()) {
|
|
ClearGraphs();
|
|
return DeserializeGraphsAdditive(bytes);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Helper function for deserializing graphs</summary>
|
|
NavGraph[] DeserializeGraphsPartAdditive (AstarSerializer sr, bool warnIfDuplicateGuids) {
|
|
if (graphs == null) graphs = new NavGraph[0];
|
|
|
|
var gr = new List<NavGraph>(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
|
|
|
|
/// <summary>
|
|
/// Find all graph types supported in this build.
|
|
/// Using reflection, the assembly is searched for types which inherit from NavGraph.
|
|
/// </summary>
|
|
public void FindGraphTypes () {
|
|
if (graphTypes != null) return;
|
|
|
|
MarkerFindGraphTypes.Begin();
|
|
#if !ASTAR_FAST_NO_EXCEPTIONS && !UNITY_WINRT
|
|
graphTypes = AssemblySearcher.FindTypesInheritingFrom<NavGraph>().ToArray();
|
|
#else
|
|
graphTypes = DefaultGraphTypes;
|
|
#endif
|
|
MarkerFindGraphTypes.End();
|
|
}
|
|
|
|
#region GraphCreation
|
|
|
|
/// <summary>Creates a new graph instance of type type</summary>
|
|
internal NavGraph CreateGraph (System.Type type) {
|
|
var graph = System.Activator.CreateInstance(type) as NavGraph;
|
|
|
|
graph.active = active;
|
|
return graph;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a graph of type T to the <see cref="graphs"/> array.
|
|
/// See: runtime-graphs (view in online documentation for working links)
|
|
/// </summary>
|
|
public T AddGraph<T> () where T : NavGraph => AddGraph(typeof(T)) as T;
|
|
|
|
/// <summary>
|
|
/// Adds a graph of type type to the <see cref="graphs"/> array.
|
|
/// See: runtime-graphs (view in online documentation for working links)
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Adds the specified graph to the <see cref="graphs"/> array</summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the specified graph from the <see cref="graphs"/> 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 <see cref="graphs"/> array). False otherwise.
|
|
///
|
|
/// See: <see cref="ClearGraphs"/>
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Duplicates the given graph and adds the duplicate to the <see cref="graphs"/> 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.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Graph which contains the specified node.
|
|
/// The graph must be in the <see cref="graphs"/> array.
|
|
///
|
|
/// Returns: Returns the graph which contains the node. Null if the graph wasn't found
|
|
/// </summary>
|
|
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];
|
|
}
|
|
|
|
/// <summary>Returns the first graph which satisfies the predicate. Returns null if no graph was found.</summary>
|
|
public NavGraph FindGraph (System.Func<NavGraph, bool> predicate) {
|
|
if (graphs != null) {
|
|
for (int i = 0; i < graphs.Length; i++) {
|
|
if (graphs[i] != null && predicate(graphs[i])) {
|
|
return graphs[i];
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>Returns the first graph of type type found in the <see cref="graphs"/> array. Returns null if no graph was found.</summary>
|
|
public NavGraph FindGraphOfType (System.Type type) {
|
|
return FindGraph(graph => System.Type.Equals(graph.GetType(), type));
|
|
}
|
|
|
|
/// <summary>Returns the first graph which inherits from the type type. Returns null if no graph was found.</summary>
|
|
public NavGraph FindGraphWhichInheritsFrom (System.Type type) {
|
|
return FindGraph(graph => WindowsStoreCompatibility.GetTypeInfo(type).IsAssignableFrom(WindowsStoreCompatibility.GetTypeInfo(graph.GetType())));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loop through this function to get all graphs of type 'type'
|
|
/// <code>
|
|
/// foreach (GridGraph graph in AstarPath.data.FindGraphsOfType (typeof(GridGraph))) {
|
|
/// //Do something with the graph
|
|
/// }
|
|
/// </code>
|
|
/// See: <see cref="AstarPath.AddWorkItem"/>
|
|
/// </summary>
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// All graphs which implements the UpdateableGraph interface
|
|
/// <code> foreach (IUpdatableGraph graph in AstarPath.data.GetUpdateableGraphs ()) {
|
|
/// //Do something with the graph
|
|
/// } </code>
|
|
/// See: <see cref="AstarPath.AddWorkItem"/>
|
|
/// See: <see cref="IUpdatableGraph"/>
|
|
/// </summary>
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets the index of the graph in the <see cref="graphs"/> array</summary>
|
|
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
|
|
}
|
|
}
|