From 8722a9920c1f6119bf6e769cba270e63097f8e25 Mon Sep 17 00:00:00 2001
From: chai <215380520@qq.com>
Date: Thu, 23 May 2024 10:08:29 +0800
Subject: + astar project
---
.../com.arongranberg.astar/Core/AstarPath.cs | 2307 ++++++++++++++++++++
1 file changed, 2307 insertions(+)
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AstarPath.cs
(limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AstarPath.cs')
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AstarPath.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AstarPath.cs
new file mode 100644
index 0000000..1c5e6c0
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AstarPath.cs
@@ -0,0 +1,2307 @@
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+using Pathfinding;
+using Pathfinding.Drawing;
+#if UNITY_5_5_OR_NEWER
+using UnityEngine.Profiling;
+using Pathfinding.Util;
+using Pathfinding.Graphs.Navmesh;
+using Pathfinding.Graphs.Util;
+using Pathfinding.Jobs;
+using Unity.Jobs;
+
+
+
+#endif
+
+#if NETFX_CORE
+using Thread = Pathfinding.WindowsStore.Thread;
+#else
+using Thread = System.Threading.Thread;
+#endif
+
+[ExecuteInEditMode]
+[AddComponentMenu("Pathfinding/AstarPath")]
+///
+/// Core component for the A* Pathfinding System.
+/// This class handles all of the pathfinding system, calculates all paths and stores the info.
+/// This class is a singleton class, meaning there should only exist at most one active instance of it in the scene.
+/// It might be a bit hard to use directly, usually interfacing with the pathfinding system is done through the class.
+///
+[HelpURL("https://arongranberg.com/astar/documentation/stable/astarpath.html")]
+public class AstarPath : VersionedMonoBehaviour {
+ /// The version number for the A* Pathfinding Project
+ public static readonly System.Version Version = new System.Version(5, 1, 1);
+
+ /// Information about where the package was downloaded
+ public enum AstarDistribution { WebsiteDownload, AssetStore, PackageManager };
+
+ /// Used by the editor to guide the user to the correct place to download updates
+ public static readonly AstarDistribution Distribution = AstarDistribution.AssetStore;
+
+ ///
+ /// Which branch of the A* Pathfinding Project is this release.
+ /// Used when checking for updates so that
+ /// users of the development versions can get notifications of development
+ /// updates.
+ ///
+ public static readonly string Branch = "master";
+
+ /// Holds all graph data
+ [UnityEngine.Serialization.FormerlySerializedAs("astarData")]
+ public AstarData data;
+
+ ///
+ /// Returns the active AstarPath object in the scene.
+ /// Note: This is only set if the AstarPath object has been initialized (which happens in Awake).
+ ///
+#if UNITY_4_6 || UNITY_4_3
+ public static new AstarPath active;
+#else
+ public static AstarPath active;
+#endif
+
+ /// Shortcut to Pathfinding.AstarData.graphs
+ public NavGraph[] graphs {
+ get {
+ return data.graphs;
+ }
+ }
+
+ #region InspectorDebug
+ ///
+ /// Visualize graphs in the scene view (editor only).
+ /// [Open online documentation to see images]
+ ///
+ public bool showNavGraphs = true;
+
+ ///
+ /// Toggle to show unwalkable nodes.
+ ///
+ /// Note: Only relevant in the editor
+ ///
+ /// See:
+ ///
+ public bool showUnwalkableNodes = true;
+
+ ///
+ /// The mode to use for drawing nodes in the sceneview.
+ ///
+ /// Note: Only relevant in the editor
+ ///
+ /// See: Pathfinding.GraphDebugMode
+ ///
+ public GraphDebugMode debugMode;
+
+ ///
+ /// Low value to use for certain modes.
+ /// For example if is set to G, this value will determine when the node will be completely red.
+ ///
+ /// Note: Only relevant in the editor
+ ///
+ /// See:
+ /// See:
+ ///
+ public float debugFloor = 0;
+
+ ///
+ /// High value to use for certain modes.
+ /// For example if is set to G, this value will determine when the node will be completely green.
+ ///
+ /// For the penalty debug mode, the nodes will be colored green when they have a penalty less than and red
+ /// when their penalty is greater or equal to this value and something between red and green otherwise.
+ ///
+ /// Note: Only relevant in the editor
+ ///
+ /// See:
+ /// See:
+ ///
+ public float debugRoof = 20000;
+
+ ///
+ /// If set, the and values will not be automatically recalculated.
+ ///
+ /// Note: Only relevant in the editor
+ ///
+ public bool manualDebugFloorRoof = false;
+
+
+ ///
+ /// If enabled, nodes will draw a line to their 'parent'.
+ /// This will show the search tree for the latest path.
+ ///
+ /// Note: Only relevant in the editor
+ ///
+ /// TODO: Add a showOnlyLastPath flag to indicate whether to draw every node or only the ones visited by the latest path.
+ ///
+ public bool showSearchTree = false;
+
+ ///
+ /// Size of the red cubes shown in place of unwalkable nodes.
+ ///
+ /// Note: Only relevant in the editor. Does not apply to grid graphs.
+ /// See:
+ ///
+ public float unwalkableNodeDebugSize = 0.3F;
+
+ ///
+ /// The amount of debugging messages.
+ /// Use less debugging to improve performance (a bit) or just to get rid of the Console spamming.
+ /// Use more debugging (heavy) if you want more information about what the pathfinding scripts are doing.
+ /// The InGame option will display the latest path log using in-game GUI.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ public PathLog logPathResults = PathLog.Normal;
+
+ #endregion
+
+ #region InspectorSettings
+ ///
+ /// Maximum distance to search for nodes.
+ /// When searching for the nearest node to a point, this is the limit (in world units) for how far away it is allowed to be.
+ ///
+ /// This is relevant if you try to request a path to a point that cannot be reached and it thus has to search for
+ /// the closest node to that point which can be reached (which might be far away). If it cannot find a node within this distance
+ /// then the path will fail.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: Pathfinding.NNConstraint.constrainDistance
+ ///
+ public float maxNearestNodeDistance = 100;
+
+ ///
+ /// Max Nearest Node Distance Squared.
+ /// See:
+ ///
+ public float maxNearestNodeDistanceSqr {
+ get { return maxNearestNodeDistance*maxNearestNodeDistance; }
+ }
+
+ ///
+ /// If true, all graphs will be scanned during Awake.
+ /// If you disable this, you will have to call yourself to enable pathfinding.
+ /// Alternatively you could load a saved graph from a file.
+ ///
+ /// If a startup cache has been generated (see save-load-graphs) (view in online documentation for working links), it always takes priority to load that instead of scanning the graphs.
+ ///
+ /// This can be useful to enable if you want to scan your graphs asynchronously, or if you have a procedural world which has not been created yet
+ /// at the start of the game.
+ ///
+ /// See:
+ /// See:
+ ///
+ public bool scanOnStartup = true;
+
+ ///
+ /// Do a full GetNearest search for all graphs.
+ /// Additional searches will normally only be done on the graph which in the first fast search seemed to have the closest node.
+ /// With this setting on, additional searches will be done on all graphs since the first check is not always completely accurate.
+ /// More technically: GetNearestForce on all graphs will be called if true, otherwise only on the one graph which's GetNearest search returned the best node.
+ /// Usually faster when disabled, but higher quality searches when enabled.
+ /// Note: For the PointGraph this setting doesn't matter much as it has only one search mode.
+ ///
+ [System.Obsolete("This setting has been removed. It is now always true", true)]
+ public bool fullGetNearestSearch = false;
+
+ ///
+ /// Prioritize graphs.
+ /// Graphs will be prioritized based on their order in the inspector.
+ /// The first graph which has a node closer than will be chosen instead of searching all graphs.
+ ///
+ /// Deprecated: This setting has been removed. It was always a bit of a hack. Use NNConstraint.graphMask if you want to choose which graphs are searched.
+ ///
+ [System.Obsolete("This setting has been removed. It was always a bit of a hack. Use NNConstraint.graphMask if you want to choose which graphs are searched.", true)]
+ public bool prioritizeGraphs = false;
+
+ ///
+ /// Distance limit for .
+ /// See:
+ ///
+ /// Deprecated: This setting has been removed. It was always a bit of a hack. Use NNConstraint.graphMask if you want to choose which graphs are searched.
+ ///
+ [System.Obsolete("This setting has been removed. It was always a bit of a hack. Use NNConstraint.graphMask if you want to choose which graphs are searched.", true)]
+ public float prioritizeGraphsLimit = 1F;
+
+ ///
+ /// Reference to the color settings for this AstarPath object.
+ /// Color settings include for example which color the nodes should be in, in the sceneview.
+ ///
+ public AstarColor colorSettings;
+
+ ///
+ /// Stored tag names.
+ /// See: AstarPath.FindTagNames
+ /// See: AstarPath.GetTagNames
+ ///
+ [SerializeField]
+ protected string[] tagNames = null;
+
+ ///
+ /// The distance function to use as a heuristic.
+ /// The heuristic, often referred to as just 'H' is the estimated cost from a node to the target.
+ /// Different heuristics affect how the path picks which one to follow from multiple possible with the same length
+ /// See: for more details and descriptions of the different modes.
+ /// See: Wikipedia: Admissible heuristic
+ /// See: Wikipedia: A* search algorithm
+ /// See: Wikipedia: Dijkstra's Algorithm
+ ///
+ /// Warning: Reducing the heuristic scale below 1, or disabling the heuristic, can significantly increase the cpu cost for pathfinding, especially for large graphs.
+ ///
+ public Heuristic heuristic = Heuristic.Euclidean;
+
+ ///
+ /// The scale of the heuristic.
+ /// If a value lower than 1 is used, the pathfinder will search more nodes (slower).
+ /// If 0 is used, the pathfinding algorithm will be reduced to dijkstra's algorithm. This is equivalent to setting to None.
+ /// If a value larger than 1 is used the pathfinding will (usually) be faster because it expands fewer nodes, but the paths may no longer be the optimal (i.e the shortest possible paths).
+ ///
+ /// Usually you should leave this to the default value of 1.
+ ///
+ /// Warning: Reducing the heuristic scale below 1, or disabling the heuristic, can significantly increase the cpu cost for pathfinding, especially for large graphs.
+ ///
+ /// See: https://en.wikipedia.org/wiki/Admissible_heuristic
+ /// See: https://en.wikipedia.org/wiki/A*_search_algorithm
+ /// See: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
+ ///
+ public float heuristicScale = 1F;
+
+ ///
+ /// Number of pathfinding threads to use.
+ /// Multithreading puts pathfinding in another thread, this is great for performance on 2+ core computers since the framerate will barely be affected by the pathfinding at all.
+ /// - None indicates that the pathfinding is run in the Unity thread as a coroutine
+ /// - Automatic will try to adjust the number of threads to the number of cores and memory on the computer.
+ /// Less than 512mb of memory or a single core computer will make it revert to using no multithreading.
+ ///
+ /// It is recommended that you use one of the "Auto" settings that are available.
+ /// The reason is that even if your computer might be beefy and have 8 cores.
+ /// Other computers might only be quad core or dual core in which case they will not benefit from more than
+ /// 1 or 3 threads respectively (you usually want to leave one core for the unity thread).
+ /// If you use more threads than the number of cores on the computer it is mostly just wasting memory, it will not run any faster.
+ /// The extra memory usage is not trivially small. Each thread needs to keep a small amount of data for each node in all the graphs.
+ /// It is not the full graph data but it is proportional to the number of nodes.
+ /// The automatic settings will inspect the machine it is running on and use that to determine the number of threads so that no memory is wasted.
+ ///
+ /// The exception is if you only have one (or maybe two characters) active at time. Then you should probably just go with one thread always since it is very unlikely
+ /// that you will need the extra throughput given by more threads. Keep in mind that more threads primarily increases throughput by calculating different paths on different
+ /// threads, it will not calculate individual paths any faster.
+ ///
+ /// Note that if you are modifying the pathfinding core scripts or if you are directly modifying graph data without using any of the
+ /// safe wrappers (like multithreading can cause strange errors and pathfinding stopping to work if you are not careful.
+ /// For basic usage (not modding the pathfinding core) it should be safe.
+ ///
+ /// Note: WebGL does not support threads at all (since javascript is single-threaded) so no threads will be used on that platform.
+ ///
+ /// See: CalculateThreadCount
+ ///
+ public ThreadCount threadCount = ThreadCount.One;
+
+ ///
+ /// Max number of milliseconds to spend each frame for pathfinding.
+ /// At least 500 nodes will be searched each frame (if there are that many to search).
+ /// When using multithreading this value is irrelevant.
+ ///
+ public float maxFrameTime = 1F;
+
+ ///
+ /// Throttle graph updates and batch them to improve performance.
+ /// If toggled, graph updates will batched and executed less often (specified by .
+ ///
+ /// This can have a positive impact on pathfinding throughput since the pathfinding threads do not need
+ /// to be stopped as often, and it reduces the overhead per graph update.
+ /// All graph updates are still applied however, they are just batched together so that more of them are
+ /// applied at the same time.
+ ///
+ /// However do not use this if you want minimal latency between a graph update being requested
+ /// and it being applied.
+ ///
+ /// This only applies to graph updates requested using the method. Not those requested
+ /// using .
+ ///
+ /// If you want to apply graph updates immediately at some point, you can call .
+ ///
+ /// See: graph-updates (view in online documentation for working links)
+ ///
+ public bool batchGraphUpdates = false;
+
+ ///
+ /// Minimum number of seconds between each batch of graph updates.
+ /// If is true, this defines the minimum number of seconds between each batch of graph updates.
+ ///
+ /// This can have a positive impact on pathfinding throughput since the pathfinding threads do not need
+ /// to be stopped as often, and it reduces the overhead per graph update.
+ /// All graph updates are still applied however, they are just batched together so that more of them are
+ /// applied at the same time.
+ ///
+ /// Do not use this if you want minimal latency between a graph update being requested
+ /// and it being applied.
+ ///
+ /// This only applies to graph updates requested using the method. Not those requested
+ /// using .
+ ///
+ /// See: graph-updates (view in online documentation for working links)
+ ///
+ public float graphUpdateBatchingInterval = 0.2F;
+
+ #endregion
+
+ #region DebugVariables
+#if ProfileAstar
+ ///
+ /// How many paths has been computed this run. From application start.
+ /// Debugging variable
+ ///
+ public static int PathsCompleted = 0;
+
+ public static System.Int64 TotalSearchedNodes = 0;
+ public static System.Int64 TotalSearchTime = 0;
+#endif
+
+ ///
+ /// The time it took for the last call to Scan() to complete.
+ /// Used to prevent automatically rescanning the graphs too often (editor only)
+ ///
+ public float lastScanTime { get; private set; }
+
+ ///
+ /// The path to debug using gizmos.
+ /// This is the path handler used to calculate the last path.
+ /// It is used in the editor to draw debug information using gizmos.
+ ///
+ [System.NonSerialized]
+ public PathHandler debugPathData;
+
+ /// The path ID to debug using gizmos
+ [System.NonSerialized]
+ public ushort debugPathID;
+
+ ///
+ /// Debug string from the last completed path.
+ /// Will be updated if == PathLog.InGame
+ ///
+ string inGameDebugPath;
+
+ #endregion
+
+ #region StatusVariables
+
+ ///
+ /// Backing field for .
+ /// Cannot use an auto-property because they cannot be marked with System.NonSerialized.
+ ///
+ [System.NonSerialized]
+ bool isScanningBacking;
+
+ ///
+ /// Set while any graphs are being scanned.
+ /// It will be true up until the FloodFill is done.
+ ///
+ /// Note: Not to be confused with graph updates.
+ ///
+ /// Used to better support Graph Update Objects called for example in OnPostScan
+ ///
+ /// See: IsAnyGraphUpdateQueued
+ /// See: IsAnyGraphUpdateInProgress
+ ///
+ public bool isScanning { get { return isScanningBacking; } private set { isScanningBacking = value; } }
+
+ ///
+ /// Number of parallel pathfinders.
+ /// Returns the number of concurrent processes which can calculate paths at once.
+ /// When using multithreading, this will be the number of threads, if not using multithreading it is always 1 (since only 1 coroutine is used).
+ /// See: IsUsingMultithreading
+ ///
+ public int NumParallelThreads {
+ get {
+ return pathProcessor.NumThreads;
+ }
+ }
+
+ ///
+ /// Returns whether or not multithreading is used.
+ /// \exception System.Exception Is thrown when it could not be decided if multithreading was used or not.
+ /// This should not happen if pathfinding is set up correctly.
+ /// Note: This uses info about if threads are running right now, it does not use info from the settings on the A* object.
+ ///
+ public bool IsUsingMultithreading {
+ get {
+ return pathProcessor.IsUsingMultithreading;
+ }
+ }
+
+ ///
+ /// Returns if any graph updates are waiting to be applied.
+ /// Note: This is false while the updates are being performed.
+ /// Note: This does *not* includes other types of work items such as navmesh cutting or anything added by .
+ ///
+ public bool IsAnyGraphUpdateQueued { get { return graphUpdates.IsAnyGraphUpdateQueued; } }
+
+ ///
+ /// Returns if any graph updates are being calculated right now.
+ /// Note: This does *not* includes other types of work items such as navmesh cutting or anything added by .
+ ///
+ /// See: IsAnyWorkItemInProgress
+ ///
+ public bool IsAnyGraphUpdateInProgress { get { return graphUpdates.IsAnyGraphUpdateInProgress; } }
+
+ ///
+ /// Returns if any work items are in progress right now.
+ /// Note: This includes pretty much all types of graph updates.
+ /// Such as normal graph updates, navmesh cutting and anything added by .
+ ///
+ public bool IsAnyWorkItemInProgress { get { return workItems.workItemsInProgress; } }
+
+ ///
+ /// Returns if this code is currently being exectuted inside a work item.
+ /// Note: This includes pretty much all types of graph updates.
+ /// Such as normal graph updates, navmesh cutting and anything added by .
+ ///
+ /// In contrast to this is only true when work item code is being executed, it is not
+ /// true in-between the updates to a work item that takes several frames to complete.
+ ///
+ internal bool IsInsideWorkItem { get { return workItems.workItemsInProgressRightNow; } }
+
+ #endregion
+
+ #region Callbacks
+ ///
+ /// Called on Awake before anything else is done.
+ /// This is called at the start of the Awake call, right after has been set, but this is the only thing that has been done.
+ /// Use this when you want to set up default settings for an AstarPath component created during runtime since some settings can only be changed in Awake
+ /// (such as multithreading related stuff)
+ ///
+ /// // Create a new AstarPath object on Start and apply some default settings
+ /// public void Start () {
+ /// AstarPath.OnAwakeSettings += ApplySettings;
+ /// AstarPath astar = gameObject.AddComponent();
+ /// }
+ ///
+ /// public void ApplySettings () {
+ /// // Unregister from the delegate
+ /// AstarPath.OnAwakeSettings -= ApplySettings;
+ /// // For example threadCount should not be changed after the Awake call
+ /// // so here's the only place to set it if you create the component during runtime
+ /// AstarPath.active.threadCount = ThreadCount.One;
+ /// }
+ ///
+ ///
+ public static System.Action OnAwakeSettings;
+
+ /// Called for each graph before they are scanned. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.
+ public static OnGraphDelegate OnGraphPreScan;
+
+ /// Called for each graph after they have been scanned. All other graphs might not have been scanned yet. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.
+ public static OnGraphDelegate OnGraphPostScan;
+
+ /// Called for each path before searching. Be careful when using multithreading since this will be called from a different thread.
+ public static OnPathDelegate OnPathPreSearch;
+
+ /// Called for each path after searching. Be careful when using multithreading since this will be called from a different thread.
+ public static OnPathDelegate OnPathPostSearch;
+
+ /// Called before starting the scanning. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.
+ public static OnScanDelegate OnPreScan;
+
+ /// Called after scanning. This is called before applying links, flood-filling the graphs and other post processing. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.
+ public static OnScanDelegate OnPostScan;
+
+ /// Called after scanning has completed fully. This is called as the last thing in the Scan function. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.
+ public static OnScanDelegate OnLatePostScan;
+
+ /// Called when any graphs are updated. Register to for example recalculate the path whenever a graph changes. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.
+ public static OnScanDelegate OnGraphsUpdated;
+
+ ///
+ /// Called when pathID overflows 65536 and resets back to zero.
+ /// Note: This callback will be cleared every time it is called, so if you want to register to it repeatedly, register to it directly on receiving the callback as well.
+ ///
+ public static System.Action On65KOverflow;
+
+ ///
+ /// Called right after callbacks on paths have been called.
+ ///
+ /// A path's callback function runs on the main thread when the path has been calculated.
+ /// This is done in batches for all paths that have finished their calculation since the last frame.
+ /// This event will trigger right after a batch of callbacks have been called.
+ ///
+ /// If you do not want to use individual path callbacks, you can use this instead to poll all pending paths
+ /// and see which ones have completed. This is better than doing it in e.g. the Update loop, because
+ /// here you will have a guarantee that all calculated paths are still valid.
+ /// Immediately after this callback has finished, other things may invalidate calculated paths, like for example
+ /// graph updates.
+ ///
+ /// This is used by the ECS integration to update all entities' pending paths, without having to store
+ /// a callback for each agent, and also to avoid the ECS synchronization overhead that having individual
+ /// callbacks would entail.
+ ///
+ public static System.Action OnPathsCalculated;
+
+ #endregion
+
+ #region MemoryStructures
+
+ /// Processes graph updates
+ readonly GraphUpdateProcessor graphUpdates;
+
+ /// Holds a hierarchical graph to speed up some queries like if there is a path between two nodes
+ internal readonly HierarchicalGraph hierarchicalGraph;
+
+ /// Holds all active off-mesh links
+ public readonly OffMeshLinks offMeshLinks;
+
+ ///
+ /// Handles navmesh cuts.
+ /// See:
+ ///
+ public NavmeshUpdates navmeshUpdates = new NavmeshUpdates();
+
+ /// Processes work items
+ readonly WorkItemProcessor workItems;
+
+ /// Holds all paths waiting to be calculated and calculates them
+ readonly PathProcessor pathProcessor;
+
+ /// Holds global node data that cannot be stored in individual graphs
+ internal GlobalNodeStorage nodeStorage;
+
+ ///
+ /// Global read-write lock for graph data.
+ ///
+ /// Graph data is always consistent from the main-thread's perspective, but if you are using jobs to read from graph data, you may need this.
+ ///
+ /// A write lock is held automatically...
+ /// - During graph updates. During async graph updates, the lock is only held once per frame while the graph update is actually running, not for the whole duration.
+ /// - During work items. Async work items work similarly to graph updates, the lock is only held once per frame while the work item is actually running.
+ /// - When events run.
+ /// - When graph related callbacks, such as , run.
+ /// - During the last step of a graph's scanning process. See .
+ ///
+ /// To use e.g. AstarPath.active.GetNearest from an ECS job, you'll need to acquire a read lock first, and make sure the lock is only released when the job is finished.
+ ///
+ ///
+ /// var readLock = AstarPath.active.LockGraphDataForReading();
+ /// var handle = new MyJob {
+ /// // ...
+ /// }.Schedule(readLock.dependency);
+ /// readLock.UnlockAfter(handle);
+ ///
+ ///
+ /// See:
+ ///
+ RWLock graphDataLock = new RWLock();
+
+ bool graphUpdateRoutineRunning = false;
+
+ /// Makes sure QueueGraphUpdates will not queue multiple graph update orders
+ bool graphUpdatesWorkItemAdded = false;
+
+ ///
+ /// Time the last graph update was done.
+ /// Used to group together frequent graph updates to batches
+ ///
+ float lastGraphUpdate = -9999F;
+
+ /// Held if any work items are currently queued
+ PathProcessor.GraphUpdateLock workItemLock;
+
+ /// Holds all completed paths waiting to be returned to where they were requested
+ internal readonly PathReturnQueue pathReturnQueue;
+
+ ///
+ /// Holds settings for heuristic optimization.
+ /// See: heuristic-opt (view in online documentation for working links)
+ ///
+ public EuclideanEmbedding euclideanEmbedding = new EuclideanEmbedding();
+
+ #endregion
+
+ ///
+ /// Shows or hides graph inspectors.
+ /// Used internally by the editor
+ ///
+ public bool showGraphs = false;
+
+ ///
+ /// The next unused Path ID.
+ /// Incremented for every call to GetNextPathID
+ ///
+ private ushort nextFreePathID = 1;
+
+ private AstarPath () {
+ pathReturnQueue = new PathReturnQueue(this, () => {
+ if (OnPathsCalculated != null) OnPathsCalculated();
+ });
+
+ // Make sure that the pathProcessor and node storage is never null
+ nodeStorage = new GlobalNodeStorage(this);
+ hierarchicalGraph = new HierarchicalGraph(nodeStorage);
+ pathProcessor = new PathProcessor(this, pathReturnQueue, 1, false);
+ offMeshLinks = new OffMeshLinks(this);
+
+ workItems = new WorkItemProcessor(this);
+ graphUpdates = new GraphUpdateProcessor(this);
+ navmeshUpdates.astar = this;
+ data = new AstarData(this);
+
+ // Forward graphUpdates.OnGraphsUpdated to AstarPath.OnGraphsUpdated
+ workItems.OnGraphsUpdated += () => {
+ if (OnGraphsUpdated != null) {
+ try {
+ OnGraphsUpdated(this);
+ } catch (System.Exception e) {
+ Debug.LogException(e);
+ }
+ }
+ };
+
+ pathProcessor.OnPathPreSearch += path => {
+ var tmp = OnPathPreSearch;
+ if (tmp != null) tmp(path);
+ };
+
+ pathProcessor.OnPathPostSearch += path => {
+ LogPathResults(path);
+ var tmp = OnPathPostSearch;
+ if (tmp != null) tmp(path);
+ };
+
+ // Sent every time the path queue is unblocked
+ pathProcessor.OnQueueUnblocked += () => {
+ if (euclideanEmbedding.dirty) {
+ euclideanEmbedding.RecalculateCosts();
+ }
+ };
+ }
+
+ ///
+ /// Returns tag names.
+ /// Makes sure that the tag names array is not null and of length 32.
+ /// If it is null or not of length 32, it creates a new array and fills it with 0,1,2,3,4 etc...
+ /// See: AstarPath.FindTagNames
+ ///
+ public string[] GetTagNames () {
+ if (tagNames == null || tagNames.Length != 32) {
+ tagNames = new string[32];
+ for (int i = 0; i < tagNames.Length; i++) {
+ tagNames[i] = ""+i;
+ }
+ tagNames[0] = "Basic Ground";
+ }
+ return tagNames;
+ }
+
+ ///
+ /// Used outside of play mode to initialize the AstarPath object even if it has not been selected in the inspector yet.
+ /// This will set the property and deserialize all graphs.
+ ///
+ /// This is useful if you want to do changes to the graphs in the editor outside of play mode, but cannot be sure that the graphs have been deserialized yet.
+ /// In play mode this method does nothing.
+ ///
+ public static void FindAstarPath () {
+ if (Application.isPlaying) return;
+ if (active == null) active = UnityCompatibility.FindAnyObjectByType();
+ if (active != null && (active.data.graphs == null || active.data.graphs.Length == 0)) active.data.DeserializeGraphs();
+ }
+
+ ///
+ /// Tries to find an AstarPath object and return tag names.
+ /// If an AstarPath object cannot be found, it returns an array of length 1 with an error message.
+ /// See: AstarPath.GetTagNames
+ ///
+ public static string[] FindTagNames () {
+ FindAstarPath();
+ return active != null? active.GetTagNames () : new string[1] { "There is no AstarPath component in the scene" };
+ }
+
+ /// Returns the next free path ID
+ internal ushort GetNextPathID () {
+ if (nextFreePathID == 0) {
+ nextFreePathID++;
+
+ if (On65KOverflow != null) {
+ System.Action tmp = On65KOverflow;
+ On65KOverflow = null;
+ tmp();
+ }
+ }
+ return nextFreePathID++;
+ }
+
+ void RecalculateDebugLimits () {
+#if UNITY_EDITOR
+ debugFloor = float.PositiveInfinity;
+ debugRoof = float.NegativeInfinity;
+
+ bool ignoreSearchTree = !showSearchTree || debugPathData == null;
+ UnsafeSpan debugPathNodes;
+ if (debugPathData != null && debugPathData.threadID < active.nodeStorage.pathfindingThreadData.Length) debugPathNodes = active.nodeStorage.pathfindingThreadData[debugPathData.threadID].debugPathNodes;
+ else debugPathNodes = default;
+
+ for (int i = 0; i < graphs.Length; i++) {
+ if (graphs[i] != null && graphs[i].drawGizmos) {
+ graphs[i].GetNodes(node => {
+ if (node.Walkable && (ignoreSearchTree || Pathfinding.Util.GraphGizmoHelper.InSearchTree(node, debugPathNodes, debugPathID))) {
+ float value;
+ if (debugMode == GraphDebugMode.Penalty) {
+ value = node.Penalty;
+ } else if (debugPathNodes.Length > 0) {
+ var rnode = debugPathNodes[node.NodeIndex];
+ switch (debugMode) {
+ case GraphDebugMode.F:
+ value = rnode.g + rnode.h;
+ break;
+ case GraphDebugMode.G:
+ value = rnode.g;
+ break;
+ default:
+ case GraphDebugMode.H:
+ value = rnode.h;
+ break;
+ }
+ } else {
+ value = 0;
+ }
+ debugFloor = Mathf.Min(debugFloor, value);
+ debugRoof = Mathf.Max(debugRoof, value);
+ }
+ });
+ }
+ }
+
+ if (float.IsInfinity(debugFloor)) {
+ debugFloor = 0;
+ debugRoof = 1;
+ }
+
+ // Make sure they are not identical, that will cause the color interpolation to fail
+ if (debugRoof-debugFloor < 1) debugRoof += 1;
+#else
+ debugFloor = 0;
+ debugRoof = 1;
+#endif
+ }
+
+ RedrawScope redrawScope;
+
+ /// Calls OnDrawGizmos on graph generators
+ public override void DrawGizmos () {
+ if (active != this || graphs == null) {
+ return;
+ }
+
+ colorSettings.PushToStatic(this);
+
+ if (!redrawScope.isValid) redrawScope = DrawingManager.GetRedrawScope(gameObject);
+
+ if (!workItems.workItemsInProgress && !isScanning) {
+ // When updating graphs, graph info might not be valid,
+ // and we cannot render anything during those frames.
+ // Therefore we use a redraw scope which will continue drawing
+ // until we dispose it.
+ redrawScope.Rewind();
+ if (showNavGraphs && !manualDebugFloorRoof) {
+ RecalculateDebugLimits();
+ }
+
+ Profiler.BeginSample("Graph.OnDrawGizmos");
+ // Loop through all graphs and draw their gizmos
+ for (int i = 0; i < graphs.Length; i++) {
+ if (graphs[i] != null && graphs[i].drawGizmos)
+ graphs[i].OnDrawGizmos(DrawingManager.instance.gizmos, showNavGraphs, redrawScope);
+ }
+ Profiler.EndSample();
+
+ if (showNavGraphs) {
+ euclideanEmbedding.OnDrawGizmos();
+ if (debugMode == GraphDebugMode.HierarchicalNode) hierarchicalGraph.OnDrawGizmos(DrawingManager.instance.gizmos, redrawScope);
+ if (debugMode == GraphDebugMode.NavmeshBorderObstacles) hierarchicalGraph.navmeshEdges.OnDrawGizmos(DrawingManager.instance.gizmos, redrawScope);
+ }
+ }
+ }
+
+#if !ASTAR_NO_GUI
+ ///
+ /// Draws the InGame debugging (if enabled)
+ /// See: PathLog
+ ///
+ private void OnGUI () {
+ if (logPathResults == PathLog.InGame && inGameDebugPath != "") {
+ GUI.Label(new Rect(5, 5, 400, 600), inGameDebugPath);
+ }
+ }
+#endif
+
+ ///
+ /// Prints path results to the log. What it prints can be controled using .
+ /// See:
+ /// See: PathLog
+ /// See: Pathfinding.Path.DebugString
+ ///
+ private void LogPathResults (Path path) {
+ if (logPathResults != PathLog.None && (path.error || logPathResults != PathLog.OnlyErrors)) {
+ string debug = (path as IPathInternals).DebugString(logPathResults);
+
+ if (logPathResults == PathLog.InGame) {
+ inGameDebugPath = debug;
+ } else if (path.error) {
+ Debug.LogWarning(debug);
+ } else {
+ Debug.Log(debug);
+ }
+ }
+ }
+
+ ///
+ /// Checks if any work items need to be executed
+ /// then runs pathfinding for a while (if not using multithreading because
+ /// then the calculation happens in other threads)
+ /// and then returns any calculated paths to the
+ /// scripts that requested them.
+ ///
+ /// See: PerformBlockingActions
+ /// See: PathProcessor.TickNonMultithreaded
+ /// See: PathReturnQueue.ReturnPaths
+ ///
+ private void Update () {
+ // This class uses the [ExecuteInEditMode] attribute
+ // So Update is called even when not playing
+ // Don't do anything when not in play mode
+ if (!Application.isPlaying) return;
+
+ navmeshUpdates.Update();
+
+ // Execute blocking actions such as graph updates
+ // when not scanning
+ if (!isScanning) {
+ PerformBlockingActions();
+ }
+
+ // Calculates paths when not using multithreading
+ if (!pathProcessor.IsUsingMultithreading) pathProcessor.TickNonMultithreaded();
+
+ // Return calculated paths
+ pathReturnQueue.ReturnPaths(true);
+ }
+
+ private void PerformBlockingActions (bool force = false) {
+ if (workItemLock.Held && pathProcessor.queue.allReceiversBlocked) {
+ // Return all paths before starting blocking actions
+ // since these might change the graph and make returned paths invalid (at least the nodes)
+ pathReturnQueue.ReturnPaths(false);
+
+ Profiler.BeginSample("Work Items");
+ if (workItems.ProcessWorkItemsForUpdate(force)) {
+ // At this stage there are no more work items, resume pathfinding threads
+ workItemLock.Release();
+ }
+ Profiler.EndSample();
+ }
+ }
+
+ ///
+ /// Add a work item to be processed when pathfinding is paused.
+ /// Convenience method that is equivalent to
+ ///
+ /// AddWorkItem(new AstarWorkItem(callback));
+ ///
+ ///
+ /// See:
+ ///
+ public void AddWorkItem (System.Action callback) {
+ AddWorkItem(new AstarWorkItem(callback));
+ }
+
+ ///
+ /// Add a work item to be processed when pathfinding is paused.
+ /// Convenience method that is equivalent to
+ ///
+ /// AddWorkItem(new AstarWorkItem(callback));
+ ///
+ ///
+ /// See:
+ ///
+ public void AddWorkItem (System.Action callback) {
+ AddWorkItem(new AstarWorkItem(callback));
+ }
+
+ ///
+ /// Add a work item to be processed when pathfinding is paused.
+ ///
+ /// The work item will be executed when it is safe to update nodes. This is defined as between the path searches.
+ /// When using more threads than one, calling this often might decrease pathfinding performance due to a lot of idling in the threads.
+ /// Not performance as in it will use much CPU power, but performance as in the number of paths per second will probably go down
+ /// (though your framerate might actually increase a tiny bit).
+ ///
+ /// You should only call this function from the main unity thread (i.e normal game code).
+ ///
+ ///
+ /// AstarPath.active.AddWorkItem(new AstarWorkItem(() => {
+ /// // Safe to update graphs here
+ /// var node = AstarPath.active.GetNearest(transform.position).node;
+ /// node.Walkable = false;
+ /// }));
+ ///
+ ///
+ ///
+ /// AstarPath.active.AddWorkItem(() => {
+ /// // Safe to update graphs here
+ /// var node = AstarPath.active.GetNearest(transform.position).node;
+ /// node.position = (Int3)transform.position;
+ /// });
+ ///
+ ///
+ /// See:
+ ///
+ public void AddWorkItem (AstarWorkItem item) {
+ workItems.AddWorkItem(item);
+
+ // Make sure pathfinding is stopped and work items are processed
+ if (!workItemLock.Held) {
+ workItemLock = PausePathfindingSoon();
+ }
+
+#if UNITY_EDITOR
+ // If not playing, execute instantly
+ if (!Application.isPlaying) {
+ FlushWorkItems();
+ }
+#endif
+ }
+
+ #region GraphUpdateMethods
+
+ ///
+ /// Will apply queued graph updates as soon as possible, regardless of .
+ /// Calling this multiple times will not create multiple callbacks.
+ /// This function is useful if you are limiting graph updates, but you want a specific graph update to be applied as soon as possible regardless of the time limit.
+ /// Note that this does not block until the updates are done, it merely bypasses the time limit.
+ ///
+ /// See:
+ ///
+ public void QueueGraphUpdates () {
+ if (!graphUpdatesWorkItemAdded) {
+ graphUpdatesWorkItemAdded = true;
+ var workItem = graphUpdates.GetWorkItem();
+
+ // Add a new work item which first
+ // sets the graphUpdatesWorkItemAdded flag to false
+ // and then processes the graph updates
+ AddWorkItem(new AstarWorkItem(context => {
+ graphUpdatesWorkItemAdded = false;
+ lastGraphUpdate = Time.realtimeSinceStartup;
+
+ workItem.initWithContext(context);
+ }, workItem.updateWithContext));
+ }
+ }
+
+ ///
+ /// Waits a moment with updating graphs.
+ /// If batchGraphUpdates is set, we want to keep some space between them to let pathfinding threads running and then calculate all queued calls at once
+ ///
+ IEnumerator DelayedGraphUpdate () {
+ graphUpdateRoutineRunning = true;
+
+ yield return new WaitForSeconds(graphUpdateBatchingInterval-(Time.realtimeSinceStartup-lastGraphUpdate));
+ QueueGraphUpdates();
+ graphUpdateRoutineRunning = false;
+ }
+
+ ///
+ /// Update all graphs within bounds after delay seconds.
+ /// The graphs will be updated as soon as possible.
+ ///
+ /// See: FlushGraphUpdates
+ /// See: batchGraphUpdates
+ /// See: graph-updates (view in online documentation for working links)
+ ///
+ public void UpdateGraphs (Bounds bounds, float delay) {
+ UpdateGraphs(new GraphUpdateObject(bounds), delay);
+ }
+
+ ///
+ /// Update all graphs using the GraphUpdateObject after delay seconds.
+ /// This can be used to, e.g make all nodes in a region unwalkable, or set them to a higher penalty.
+ ///
+ /// See: FlushGraphUpdates
+ /// See: batchGraphUpdates
+ /// See: graph-updates (view in online documentation for working links)
+ ///
+ public void UpdateGraphs (GraphUpdateObject ob, float delay) {
+ StartCoroutine(UpdateGraphsInternal(ob, delay));
+ }
+
+ /// Update all graphs using the GraphUpdateObject after delay seconds
+ IEnumerator UpdateGraphsInternal (GraphUpdateObject ob, float delay) {
+ yield return new WaitForSeconds(delay);
+ UpdateGraphs(ob);
+ }
+
+ ///
+ /// Update all graphs within bounds.
+ /// The graphs will be updated as soon as possible.
+ ///
+ /// This is equivalent to
+ ///
+ /// UpdateGraphs(new GraphUpdateObject(bounds));
+ ///
+ ///
+ /// See: FlushGraphUpdates
+ /// See: batchGraphUpdates
+ /// See: graph-updates (view in online documentation for working links)
+ ///
+ public void UpdateGraphs (Bounds bounds) {
+ UpdateGraphs(new GraphUpdateObject(bounds));
+ }
+
+ ///
+ /// Update all graphs using the GraphUpdateObject.
+ /// This can be used to, e.g make all nodes in a region unwalkable, or set them to a higher penalty.
+ /// The graphs will be updated as soon as possible (with respect to
+ ///
+ /// See: FlushGraphUpdates
+ /// See: batchGraphUpdates
+ /// See: graph-updates (view in online documentation for working links)
+ ///
+ public void UpdateGraphs (GraphUpdateObject ob) {
+ if (ob.internalStage != GraphUpdateObject.STAGE_CREATED) {
+ throw new System.Exception("You are trying to update graphs using the same graph update object twice. Please create a new GraphUpdateObject instead.");
+ }
+ ob.internalStage = GraphUpdateObject.STAGE_PENDING;
+ graphUpdates.AddToQueue(ob);
+
+ // If we should limit graph updates, start a coroutine which waits until we should update graphs
+ if (batchGraphUpdates && Time.realtimeSinceStartup-lastGraphUpdate < graphUpdateBatchingInterval) {
+ if (!graphUpdateRoutineRunning) {
+ StartCoroutine(DelayedGraphUpdate());
+ }
+ } else {
+ // Otherwise, graph updates should be carried out as soon as possible
+ QueueGraphUpdates();
+ }
+ }
+
+ ///
+ /// Forces graph updates to complete in a single frame.
+ /// This will force the pathfinding threads to finish calculating the path they are currently calculating (if any) and then pause.
+ /// When all threads have paused, graph updates will be performed.
+ /// Warning: Using this very often (many times per second) can reduce your fps due to a lot of threads waiting for one another.
+ /// But you probably wont have to worry about that.
+ ///
+ /// Note: This is almost identical to , but added for more descriptive name.
+ /// This function will also override any time limit delays for graph updates.
+ /// This is because graph updates are implemented using work items.
+ /// So calling this function will also execute any other work items (if any are queued).
+ ///
+ /// Will not do anything if there are no graph updates queued (not even execute other work items).
+ ///
+ public void FlushGraphUpdates () {
+ if (IsAnyGraphUpdateQueued || IsAnyGraphUpdateInProgress) {
+ QueueGraphUpdates();
+ FlushWorkItems();
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Forces work items to complete in a single frame.
+ /// This will force all work items to run immidiately.
+ /// This will force the pathfinding threads to finish calculating the path they are currently calculating (if any) and then pause.
+ /// When all threads have paused, work items will be executed (which can be e.g graph updates).
+ ///
+ /// Warning: Using this very often (many times per second) can reduce your fps due to a lot of threads waiting for one another.
+ /// But you probably wont have to worry about that
+ ///
+ /// Note: This is almost (note almost) identical to , but added for more descriptive name.
+ ///
+ /// Will not do anything if there are no queued work items waiting to run.
+ ///
+ public void FlushWorkItems () {
+ if (workItems.anyQueued || workItems.workItemsInProgress) {
+ var graphLock = PausePathfinding();
+ PerformBlockingActions(true);
+ graphLock.Release();
+ }
+ }
+
+ ///
+ /// Calculates number of threads to use.
+ /// If count is not Automatic, simply returns count casted to an int.
+ /// Returns: An int specifying how many threads to use, 0 means a coroutine should be used for pathfinding instead of a separate thread.
+ ///
+ /// If count is set to Automatic it will return a value based on the number of processors and memory for the current system.
+ /// If memory is <= 512MB or logical cores are <= 1, it will return 0. If memory is <= 1024 it will clamp threads to max 2.
+ /// Otherwise it will return the number of logical cores clamped to 6.
+ ///
+ /// When running on WebGL this method always returns 0
+ ///
+ public static int CalculateThreadCount (ThreadCount count) {
+#if UNITY_WEBGL
+ return 0;
+#else
+ if (count == ThreadCount.AutomaticLowLoad || count == ThreadCount.AutomaticHighLoad) {
+#if ASTARDEBUG
+ Debug.Log(SystemInfo.systemMemorySize + " " + SystemInfo.processorCount + " " + SystemInfo.processorType);
+#endif
+
+ int logicalCores = Mathf.Max(1, SystemInfo.processorCount);
+ int memory = SystemInfo.systemMemorySize;
+
+ if (memory <= 0) {
+ Debug.LogError("Machine reporting that is has <= 0 bytes of RAM. This is definitely not true, assuming 1 GiB");
+ memory = 1024;
+ }
+
+ if (logicalCores <= 1) return 0;
+
+ if (memory <= 512) return 0;
+
+ if (count == ThreadCount.AutomaticHighLoad) {
+ if (memory <= 1024) logicalCores = System.Math.Min(logicalCores, 2);
+ } else {
+ //Always run at at most processorCount-1 threads (one core reserved for unity thread).
+ // Many computers use hyperthreading, so dividing by two is used to remove the hyperthreading cores, pathfinding
+ // doesn't scale well past the number of physical cores anyway
+ logicalCores /= 2;
+ logicalCores = Mathf.Max(1, logicalCores);
+
+ if (memory <= 1024) logicalCores = System.Math.Min(logicalCores, 2);
+
+ logicalCores = System.Math.Min(logicalCores, 6);
+ }
+
+ return logicalCores;
+ } else {
+ int val = (int)count;
+ return val;
+ }
+#endif
+ }
+
+ /// Initializes the field
+ void InitializePathProcessor () {
+ int numThreads = CalculateThreadCount(threadCount);
+
+ // Outside of play mode everything is synchronous, so no threads are used.
+ if (!Application.isPlaying) numThreads = 0;
+
+
+ int numProcessors = Mathf.Max(numThreads, 1);
+ bool multithreaded = numThreads > 0;
+ pathProcessor.StopThreads();
+ pathProcessor.SetThreadCount(numProcessors, multithreaded);
+ }
+
+ /// Does simple error checking
+ internal void VerifyIntegrity () {
+ if (data.graphs == null) {
+ data.graphs = new NavGraph[0];
+ data.UpdateShortcuts();
+ }
+ }
+
+ /// \cond internal
+ ///
+ /// Internal method to make sure is set to this object and that is not null.
+ /// Also calls OnEnable for the and initializes data.userConnections if it wasn't initialized before
+ ///
+ /// Warning: This is mostly for use internally by the system.
+ ///
+ public void ConfigureReferencesInternal () {
+ colorSettings = colorSettings ?? new AstarColor();
+ colorSettings.PushToStatic(this);
+ }
+ /// \endcond
+
+ ///
+ /// Initializes the AstarData class.
+ /// Searches for graph types, calls Awake on and on all graphs
+ ///
+ /// See: AstarData.FindGraphTypes
+ ///
+ void InitializeGraphs () {
+ data.FindGraphTypes();
+ data.OnEnable();
+ data.UpdateShortcuts();
+ }
+
+ void ShutdownPathfindingThreads () {
+ // Block until the pathfinding threads have
+ // completed their current path calculation
+ var graphLock = PausePathfinding();
+
+ navmeshUpdates.OnDisable();
+
+ euclideanEmbedding.dirty = false;
+
+ // Discard all queued graph updates. Graph updates that are already in progress will still be allowed to finish,
+ // as they may be allocating unmanaged data which we don't know how to safely deallocate.
+ graphUpdates.DiscardQueued();
+
+ // TODO: Add unit test that verifies that work items that are added will always complete
+ // Ensure work items complete before disabling this component.
+ // This is important because work items may allocate temporary unmanaged memory, so we cannot just forget about them.
+ FlushWorkItems();
+
+ if (logPathResults == PathLog.Heavy)
+ Debug.Log("Processing Possible Work Items");
+
+ // Try to join pathfinding threads
+ pathProcessor.StopThreads();
+
+ if (logPathResults == PathLog.Heavy)
+ Debug.Log("Returning Paths");
+
+
+ // Return all paths
+ pathReturnQueue.ReturnPaths(false);
+ graphLock.Release();
+ euclideanEmbedding.OnDisable();
+ }
+
+ bool hasScannedGraphAtStartup = false;
+
+ ///
+ /// Called after this component is enabled.
+ ///
+ /// Unless the component has already been activated in Awake, this method should:
+ /// - Ensure the singleton holds (setting to this).
+ /// - Make sure all subsystems that were disabled in OnDisable are again enabled.
+ /// - This includes starting pathfinding threads.
+ ///
+ void OnEnable () {
+ // If the component gets re-enabled during runtime.
+ // Note that the first time the component loads, then Awake will run first
+ // and will already have set the #active field.
+ // In the editor, OnDisable -> OnEnable will be called when an undo or redo event happens (both in and outside of play mode).
+ if (active != null) {
+ if (active != this && Application.isPlaying) {
+ if (this.enabled) {
+ Debug.LogWarning("Another A* component is already in the scene. More than one A* component cannot be active at the same time. Disabling this one.", this);
+ }
+ enabled = false;
+ }
+ return;
+ }
+
+ // Very important to set this. Ensures the singleton pattern holds
+ active = this;
+
+ // Disable GUILayout to gain some performance, it is not used in the OnGUI call
+ useGUILayout = false;
+
+ if (OnAwakeSettings != null) {
+ OnAwakeSettings();
+ }
+
+ hierarchicalGraph.OnEnable();
+
+ // To make sure all graph modifiers have been enabled before scan (to avoid script execution order issues)
+ GraphModifier.FindAllModifiers();
+ RelevantGraphSurface.FindAllGraphSurfaces();
+
+ ConfigureReferencesInternal();
+
+ // This will load the graph settings, or whole initialized graphs from the cache, if one has been supplied.
+ data.OnEnable();
+
+ // Flush work items, possibly added when loading the graph data
+ FlushWorkItems();
+
+ euclideanEmbedding.dirty = true;
+
+ InitializePathProcessor();
+
+ // This class uses the [ExecuteInEditMode] attribute
+ // So OnEnable is called even when not playing
+ // Don't scan the graphs unless we are in play mode
+ if (Application.isPlaying) {
+ navmeshUpdates.OnEnable();
+
+ // Scan the graphs if #scanOnStartup is enabled, and we have not loaded a graph cache already.
+ // We only do this the first time the AstarPath component is enabled.
+ if (scanOnStartup && !hasScannedGraphAtStartup && (!data.cacheStartup || data.file_cachedStartup == null)) {
+ hasScannedGraphAtStartup = true;
+ Scan();
+ }
+ }
+ }
+
+
+ ///
+ /// Cleans up graphs to avoid memory leaks.
+ ///
+ /// This is called by Unity when:
+ /// - The component is explicitly disabled in play mode or editor mode.
+ /// - When the component is about to be destroyed
+ /// - Including when the game stops
+ /// - When an undo/redo event takes place (Unity will first disable the component and then enable it again).
+ ///
+ /// During edit and play mode this method should:
+ /// - Destroy all node data (but not the graphs themselves)
+ /// - Dispose all unmanaged data
+ /// - Shutdown pathfinding threads if they are running (any pending path requests are left in the queue)
+ ///
+ void OnDisable () {
+ redrawScope.Dispose();
+ if (active == this) {
+ // Ensure there are no jobs running that might read or write graph data
+ graphDataLock.WriteSync().Unlock();
+
+ ShutdownPathfindingThreads();
+
+ // We need to call dispose data here because in the editor the OnDestroy
+ // method is not called but OnDisable is. It is vital that graph data
+ // is destroyed even in the editor (e.g. when going from edit mode to play mode)
+ // because a lot of data is stored as NativeArrays which need to be disposed.
+
+ // There is also another case where this is important. When the unity
+ // editor is configured to stop play mode after recompiling scripts
+ // it seems to not call OnDestroy (or at least not reliably across all versions of Unity).
+ // So we need to ensure we dispose of all the data during OnDisable.
+ data.DestroyAllNodes();
+ data.DisposeUnmanagedData();
+ hierarchicalGraph.OnDisable();
+ nodeStorage.OnDisable();
+ offMeshLinks.OnDisable();
+ active = null;
+ }
+ }
+
+ ///
+ /// Clears up variables and other stuff, destroys graphs.
+ /// Note that when destroying an AstarPath object, all static variables such as callbacks will be cleared.
+ ///
+ void OnDestroy () {
+ if (logPathResults == PathLog.Heavy)
+ Debug.Log("AstarPath Component Destroyed - Cleaning Up Pathfinding Data");
+
+ // active has already been set to null during OnDisable.
+ // We temporarily make this object the active one just during the destruction.
+ var prevActive = active;
+ active = this;
+
+ ShutdownPathfindingThreads();
+
+ pathProcessor.Dispose();
+
+ if (logPathResults == PathLog.Heavy)
+ Debug.Log("Destroying Graphs");
+
+ // Clean up graph data
+ // Data may be null if this object was never enabled because another A* instance existed.
+ if (data != null) data.OnDestroy();
+
+ active = prevActive;
+
+ if (logPathResults == PathLog.Heavy)
+ Debug.Log("Cleaning up variables");
+
+ // Clear variables up, static variables are good to clean up, otherwise the next scene might get weird data
+
+ if (active == this) {
+ // Clear all callbacks
+ OnAwakeSettings = null;
+ OnGraphPreScan = null;
+ OnGraphPostScan = null;
+ OnPathPreSearch = null;
+ OnPathPostSearch = null;
+ OnPreScan = null;
+ OnPostScan = null;
+ OnLatePostScan = null;
+ On65KOverflow = null;
+ OnGraphsUpdated = null;
+
+ active = null;
+ }
+ }
+
+ #region ScanMethods
+
+ ///
+ /// Allocate a bunch of nodes at once.
+ /// This is faster than allocating each individual node separately and it can be done in a separate thread by using jobs.
+ ///
+ ///
+ /// var nodes = new PointNode[128];
+ /// var job = AstarPath.active.AllocateNodes(nodes, 128, () => new PointNode(), 1);
+ ///
+ /// job.Complete();
+ ///
+ ///
+ /// See:
+ ///
+ /// Node array to fill
+ /// How many nodes to allocate
+ /// Delegate which creates a node. () => new T(). Note that new T(AstarPath.active) should *not* be used as that will cause the node to be initialized twice.
+ /// How many variants of the node to allocate. Should be the same as \reflink{GraphNode.PathNodeVariants} for this node type.
+ public Unity.Jobs.JobHandle AllocateNodes(T[] result, int count, System.Func createNode, uint variantsPerNode) where T : GraphNode {
+ if (!pathProcessor.queue.allReceiversBlocked) {
+ throw new System.Exception("Trying to initialize a node when it is not safe to initialize any nodes. Must be done during a graph update. See http://arongranberg.com/astar/docs/graph-updates.html#direct");
+ }
+ return nodeStorage.AllocateNodesJob(result, count, createNode, variantsPerNode);
+ }
+
+ ///
+ /// Initializes temporary path data for a node.
+ ///
+ /// Use like: InitializeNode(new PointNode())
+ ///
+ /// See:
+ ///
+ internal void InitializeNode (GraphNode node) {
+ if (!pathProcessor.queue.allReceiversBlocked) {
+ throw new System.Exception("Trying to initialize a node when it is not safe to initialize any nodes. Must be done during a graph update. See http://arongranberg.com/astar/docs/graph-updates.html#direct");
+ }
+ nodeStorage.InitializeNode(node);
+ }
+
+ internal void InitializeNodes (GraphNode[] nodes) {
+ if (!pathProcessor.queue.allReceiversBlocked) {
+ throw new System.Exception("Trying to initialize a node when it is not safe to initialize any nodes. Must be done during a graph update. See http://arongranberg.com/astar/docs/graph-updates.html#direct");
+ }
+
+ for (int i = 0; i < nodes.Length; i++) nodeStorage.InitializeNode(nodes[i]);
+ }
+
+ ///
+ /// Internal method to destroy a given node.
+ /// This is to be called after the node has been disconnected from the graph so that it cannot be reached from any other nodes.
+ /// It should only be called during graph updates, that is when the pathfinding threads are either not running or paused.
+ ///
+ /// Warning: This method should not be called by user code. It is used internally by the system.
+ ///
+ internal void DestroyNode (GraphNode node) {
+ nodeStorage.DestroyNode(node);
+ }
+
+ ///
+ /// Blocks until all pathfinding threads are paused and blocked.
+ ///
+ ///
+ /// var graphLock = AstarPath.active.PausePathfinding();
+ /// // Here we can modify the graphs safely. For example by increasing the penalty of a node
+ /// AstarPath.active.data.gridGraph.GetNode(0, 0).Penalty += 1000;
+ ///
+ /// // Allow pathfinding to resume
+ /// graphLock.Release();
+ ///
+ ///
+ /// Returns: A lock object. You need to call on that object to allow pathfinding to resume.
+ /// Note: In most cases this should not be called from user code. Use the method instead.
+ ///
+ /// See:
+ ///
+ public PathProcessor.GraphUpdateLock PausePathfinding () {
+ // Ensure there are no jobs running that might read or write graph data,
+ // as this method is typically used right before one modifies graph data.
+ graphDataLock.WriteSync().Unlock();
+ return pathProcessor.PausePathfinding(true);
+ }
+
+ ///
+ /// Blocks the path queue so that e.g work items can be performed.
+ ///
+ /// Pathfinding threads will stop accepting new path requests and will finish the ones they are currently calculating asynchronously.
+ /// When the lock is released, the pathfinding threads will resume as normal.
+ ///
+ /// Note: You are unlikely to need to use this method. It is primarily for internal use.
+ ///
+ public PathProcessor.GraphUpdateLock PausePathfindingSoon () {
+ return pathProcessor.PausePathfinding(false);
+ }
+
+ ///
+ /// Scans a particular graph.
+ /// Calling this method will recalculate the specified graph from scratch.
+ /// This method is pretty slow (depending on graph type and graph complexity of course), so it is advisable to use
+ /// smaller graph updates whenever possible.
+ ///
+ ///
+ /// // Recalculate all graphs
+ /// AstarPath.active.Scan();
+ ///
+ /// // Recalculate only the first grid graph
+ /// var graphToScan = AstarPath.active.data.gridGraph;
+ /// AstarPath.active.Scan(graphToScan);
+ ///
+ /// // Recalculate only the first and third graphs
+ /// var graphsToScan = new [] { AstarPath.active.data.graphs[0], AstarPath.active.data.graphs[2] };
+ /// AstarPath.active.Scan(graphsToScan);
+ ///
+ ///
+ /// See: graph-updates (view in online documentation for working links)
+ /// See: ScanAsync
+ ///
+ public void Scan (NavGraph graphToScan) {
+ if (graphToScan == null) throw new System.ArgumentNullException();
+ Scan(new NavGraph[] { graphToScan });
+ }
+
+ ///
+ /// Scans all specified graphs.
+ ///
+ /// Calling this method will recalculate all specified graphs (or all graphs if the graphsToScan parameter is null) from scratch.
+ /// This method is pretty slow (depending on graph type and graph complexity of course), so it is advisable to use
+ /// smaller graph updates whenever possible.
+ ///
+ ///
+ /// // Recalculate all graphs
+ /// AstarPath.active.Scan();
+ ///
+ /// // Recalculate only the first grid graph
+ /// var graphToScan = AstarPath.active.data.gridGraph;
+ /// AstarPath.active.Scan(graphToScan);
+ ///
+ /// // Recalculate only the first and third graphs
+ /// var graphsToScan = new [] { AstarPath.active.data.graphs[0], AstarPath.active.data.graphs[2] };
+ /// AstarPath.active.Scan(graphsToScan);
+ ///
+ ///
+ /// See: graph-updates (view in online documentation for working links)
+ /// See: ScanAsync
+ ///
+ /// The graphs to scan. If this parameter is null then all graphs will be scanned
+ public void Scan (NavGraph[] graphsToScan = null) {
+ var prevStage = (ScanningStage)(-1);
+
+ Profiler.BeginSample("Scan");
+ Profiler.BeginSample("Init");
+ foreach (var p in ScanInternal(graphsToScan, false)) {
+ if (prevStage != p.stage) {
+ Profiler.EndSample();
+ Profiler.BeginSample(p.stage.ToString());
+#if !NETFX_CORE && UNITY_EDITOR
+ // Log progress to the console
+ System.Console.WriteLine(p.stage);
+#endif
+ prevStage = p.stage;
+ }
+ }
+ Profiler.EndSample();
+ Profiler.EndSample();
+ }
+
+ ///
+ /// Scans a particular graph asynchronously. This is a IEnumerable, you can loop through it to get the progress
+ ///
+ /// You can scan graphs asyncronously by yielding when you iterate through the returned IEnumerable.
+ /// Note that this does not guarantee a good framerate, but it will allow you
+ /// to at least show a progress bar while scanning.
+ ///
+ ///
+ /// IEnumerator Start () {
+ /// foreach (Progress progress in AstarPath.active.ScanAsync()) {
+ /// Debug.Log("Scanning... " + progress.ToString());
+ /// yield return null;
+ /// }
+ /// }
+ ///
+ ///
+ /// See: Scan
+ ///
+ public IEnumerable