using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Pathfinding;
using Pathfinding.Drawing;
using UnityEngine.Profiling;
using Pathfinding.Util;
using Pathfinding.Graphs.Navmesh;
using Pathfinding.Graphs.Util;
using Pathfinding.Jobs;
using Pathfinding.Collections;
using Pathfinding.Sync;
using Unity.Jobs;

#if NETFX_CORE
using Thread = Pathfinding.WindowsStore.Thread;
#else
using Thread = System.Threading.Thread;
#endif

[ExecuteInEditMode]
[AddComponentMenu("Pathfinding/AstarPath")]
[DisallowMultipleComponent]
/// <summary>
/// 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.
///
/// See: inspector (view in online documentation for working links)
/// See: calling-directly (view in online documentation for working links)
/// </summary>
[HelpURL("https://arongranberg.com/astar/documentation/stable/astarpath.html")]
public class AstarPath : VersionedMonoBehaviour {
	/// <summary>The version number for the A* Pathfinding Project</summary>
	public static readonly System.Version Version = new System.Version(5, 4, 6);

	/// <summary>Information about where the package was downloaded</summary>
	public enum AstarDistribution { WebsiteDownload, AssetStore, PackageManager };

	/// <summary>Used by the editor to guide the user to the correct place to download updates</summary>
	public static readonly AstarDistribution Distribution = AstarDistribution.AssetStore;

	/// <summary>
	/// 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.
	/// </summary>
	public static readonly string Branch = "master";

	/// <summary>Holds all graph data</summary>
	[UnityEngine.Serialization.FormerlySerializedAs("astarData")]
	public AstarData data;

	/// <summary>
	/// Returns the active AstarPath object in the scene.
	/// Note: This is only set if the AstarPath object has been initialized (which happens in Awake).
	/// </summary>
	public static AstarPath active;

	/// <summary>Shortcut to <see cref="AstarData.graphs"/></summary>
	public NavGraph[] graphs => data.graphs;

	bool hasScannedGraphAtStartup = false;

	#region InspectorDebug
	/// <summary>
	/// Visualize graphs in the scene view (editor only).
	/// [Open online documentation to see images]
	///
	/// See: <see cref="showGraphsInStandalonePlayer"/>
	/// </summary>
	public bool showNavGraphs = true;

	/// <summary>
	/// Toggle to show unwalkable nodes.
	///
	/// Note: Only relevant in the editor
	///
	/// See: <see cref="unwalkableNodeDebugSize"/>
	/// </summary>
	public bool showUnwalkableNodes = true;

	/// <summary>
	/// The mode to use for drawing nodes in the sceneview.
	///
	/// Note: Only relevant in the editor
	///
	/// See: <see cref="GraphDebugMode"/>
	/// </summary>
	public GraphDebugMode debugMode;

	/// <summary>
	/// How to visualize graph updates in the editor.
	/// This is used to visualize the bounds of graph updates, and how they affect the graph.
	///
	/// If <see cref="showGraphsInStandalonePlayer"/> is enabled, the visualization will also be shown in a standalone player.
	///
	/// [Open online documentation to see images]
	/// [Open online documentation to see images]
	///
	/// See: <see cref="GraphUpdateDebugMode"/> for more info about the different modes.
	/// </summary>
	public GraphUpdateDebugMode graphUpdateDebugMode = GraphUpdateDebugMode.Nothing;

	/// <summary>
	/// Low value to use for certain <see cref="debugMode"/> modes.
	/// For example if <see cref="debugMode"/> is set to G, this value will determine when the node will be completely red.
	///
	/// Note: Only relevant in the editor
	///
	/// See: <see cref="debugRoof"/>
	/// See: <see cref="debugMode"/>
	/// </summary>
	public float debugFloor = 0;

	/// <summary>
	/// High value to use for certain <see cref="debugMode"/> modes.
	/// For example if <see cref="debugMode"/> 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 <see cref="debugFloor"/> 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 cref="debugFloor"/>
	/// See: <see cref="debugMode"/>
	/// </summary>
	public float debugRoof = 20000;

	/// <summary>
	/// If set, the <see cref="debugFloor"/> and <see cref="debugRoof"/> values will not be automatically recalculated.
	///
	/// Note: Only relevant in the editor
	/// </summary>
	public bool manualDebugFloorRoof = false;


	/// <summary>
	/// 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
	/// </summary>
	public bool showSearchTree = false;

	/// <summary>
	/// Size of the red cubes shown in place of unwalkable nodes.
	///
	/// Note: Only relevant in the editor. Does not apply to grid graphs.
	/// See: <see cref="showUnwalkableNodes"/>
	/// </summary>
	public float unwalkableNodeDebugSize = 0.3F;

	/// <summary>
	/// 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]
	/// </summary>
	public PathLog logPathResults = PathLog.Normal;

	#endregion

	#region InspectorSettings
	/// <summary>
	/// 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: <see cref="NearestNodeConstraint.maxDistance"/>
	/// </summary>
	public float maxNearestNodeDistance = 100;

	/// <summary>
	/// Max Nearest Node Distance Squared.
	/// See: <see cref="maxNearestNodeDistance"/>
	/// </summary>
	public float maxNearestNodeDistanceSqr => maxNearestNodeDistance*maxNearestNodeDistance;

	/// <summary>
	/// If true, all graphs will be scanned when the game starts, during OnEnable.
	/// If you disable this, you will have to call <see cref="AstarPath.active.Scan"/> 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, and the graphs will be loaded from the cache instead of scanned.
	///
	/// This can be useful to disable 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 cref="Scan"/>
	/// See: <see cref="ScanAsync"/>
	/// </summary>
	public bool scanOnStartup = true;

	/// <summary>
	/// 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.
	/// </summary>
	[System.Obsolete("This setting has been removed. It is now always true", true)]
	public bool fullGetNearestSearch = false;

	/// <summary>
	/// Prioritize graphs.
	/// Graphs will be prioritized based on their order in the inspector.
	/// The first graph which has a node closer than <see cref="prioritizeGraphsLimit"/> will be chosen instead of searching all graphs.
	///
	/// Deprecated: This setting has been removed. It was always a bit of a hack. Use TraversalConstraint.graphMask if you want to choose which graphs are searched.
	/// </summary>
	[System.Obsolete("This setting has been removed. It was always a bit of a hack. Use TraversalConstraint.graphMask if you want to choose which graphs are searched.", true)]
	public bool prioritizeGraphs = false;

	/// <summary>
	/// Distance limit for <see cref="prioritizeGraphs"/>.
	/// See: <see cref="prioritizeGraphs"/>
	///
	/// Deprecated: This setting has been removed. It was always a bit of a hack. Use TraversalConstraint.graphMask if you want to choose which graphs are searched.
	/// </summary>
	[System.Obsolete("This setting has been removed. It was always a bit of a hack. Use TraversalConstraint.graphMask if you want to choose which graphs are searched.", true)]
	public float prioritizeGraphsLimit = 1F;

	/// <summary>
	/// Reference to the color settings for this AstarPath object.
	/// Color settings include for example which color the nodes should be in, in the sceneview.
	/// </summary>
	public AstarColor colorSettings;

	/// <summary>
	/// Stored tag names.
	/// See: AstarPath.FindTagNames
	/// See: AstarPath.GetTagNames
	/// </summary>
	[SerializeField]
	protected string[] tagNames = null;

	/// <summary>
	/// 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: <see cref="Pathfinding.Heuristic"/> for more details and descriptions of the different modes.
	/// See: <a href="https://en.wikipedia.org/wiki/Admissible_heuristic">Wikipedia: Admissible heuristic</a>
	/// See: <a href="https://en.wikipedia.org/wiki/A*_search_algorithm">Wikipedia: A* search algorithm</a>
	/// See: <a href="https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm">Wikipedia: Dijkstra's Algorithm</a>
	///
	/// Warning: Reducing the heuristic scale below 1, or disabling the heuristic, can significantly increase the cpu cost for pathfinding, especially for large graphs.
	/// </summary>
	public Heuristic heuristic = Heuristic.Euclidean;

	/// <summary>
	/// 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 <see cref="heuristic"/> 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: <a href="https://en.wikipedia.org/wiki/Admissible_heuristic">Wikipedia: Admissible heuristic</a>
	/// See: <a href="https://en.wikipedia.org/wiki/A*_search_algorithm">Wikipedia: A* search algorithm</a>
	/// See: <a href="https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm">Wikipedia: Dijkstra's Algorithm</a>
	/// </summary>
	public float heuristicScale = 1F;

	/// <summary>
	/// The effective heuristic scale.
	/// This is the heuristic scale used for pathfinding. Zero is returned if <see cref="heuristic"/> is set to None, otherwise <see cref="heuristicScale"/> is returned.
	/// See: <see cref="heuristic"/>
	/// </summary>
	public float effectiveHeuristicScale => heuristic == Heuristic.None ? 0 : heuristicScale;

	/// <summary>
	/// 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.
	///
	/// Warning: If you are modifying the pathfinding core scripts or if you are directly modifying graph data without using any of the
	/// safe wrappers (like <see cref="AddWorkItem)"/>, multithreading can cause strange errors and cause pathfinding to stop working if you are not careful.
	///
	/// Note: WebGL does not support threads at all (since javascript is single-threaded) so no threads will be used on that platform.
	///
	/// Note: This setting only applies to pathfinding. Graph updates use the Unity Job System, which uses a different thread pool.
	///
	/// See: CalculateThreadCount
	/// </summary>
	public ThreadCount threadCount = ThreadCount.One;

	/// <summary>
	/// Max number of milliseconds to spend on pathfinding during each frame.
	/// At least 500 nodes will be searched each frame (if there are that many to search).
	/// When using multithreading this value is irrelevant.
	/// </summary>
	public float maxFrameTime = 1F;

	/// <summary>
	/// Throttle graph updates and batch them to improve performance.
	/// If toggled, graph updates will batched and executed less often (specified by <see cref="graphUpdateBatchingInterval)"/>.
	///
	/// 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, 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 <see cref="UpdateGraphs"/> method. Not those requested
	/// using <see cref="AddWorkItem"/>.
	///
	/// If you want to apply graph updates immediately at some point, you can call <see cref="FlushGraphUpdates"/>.
	///
	/// See: graph-updates (view in online documentation for working links)
	/// </summary>
	public bool batchGraphUpdates = false;

	/// <summary>
	/// Minimum number of seconds between each batch of graph updates.
	/// If <see cref="batchGraphUpdates"/> 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 <see cref="UpdateGraphs"/> method. Not those requested
	/// using <see cref="AddWorkItem"/>.
	///
	/// See: graph-updates (view in online documentation for working links)
	/// </summary>
	public float graphUpdateBatchingInterval = 0.2F;

	#endregion

	#region DebugVariables
#if ProfileAstar
	/// <summary>
	/// How many paths has been computed this run. From application start.
	/// Debugging variable
	/// </summary>
	public static int PathsCompleted = 0;

	public static System.Int64 TotalSearchedNodes = 0;
	public static System.Int64 TotalSearchTime = 0;
#endif

	/// <summary>The time it took for the last call to <see cref="Scan"/> to complete</summary>
	public float lastScanTime { get; private set; }

	/// <summary>
	/// 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.
	/// </summary>
	[System.NonSerialized]
	internal PathHandler debugPathData;

	/// <summary>The path ID to debug using gizmos</summary>
	[System.NonSerialized]
	internal ushort debugPathID;

	/// <summary>
	/// Debug string from the last completed path.
	/// Will be updated if <see cref="logPathResults"/> == PathLog.InGame
	/// </summary>
	string inGameDebugPath;

	#endregion

	#region StatusVariables

	/// <summary>
	/// True while any graphs are being scanned.
	///
	/// This is primarily relevant when scanning graph asynchronously.
	///
	/// Note: Not to be confused with graph updates.
	///
	/// Note: This will be false during <see cref="OnLatePostScan"/> and during the <see cref="GraphModifier.EventType"/>.LatePostScan event.
	///
	/// See: IsAnyGraphUpdateQueued
	/// See: IsAnyGraphUpdateInProgress
	/// </summary>
	[field: System.NonSerialized]
	public bool isScanning { get; private set; }

	/// <summary>
	/// 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
	/// </summary>
	public int NumParallelThreads => pathProcessor.NumThreads;

	/// <summary>
	/// 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.
	/// </summary>
	public bool IsUsingMultithreading => pathProcessor.IsUsingMultithreading;

	/// <summary>
	/// 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 <see cref="AddWorkItem"/>.
	/// </summary>
	public bool IsAnyGraphUpdateQueued => graphUpdates.IsAnyGraphUpdateQueued;

	/// <summary>
	/// 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 cref="AddWorkItem"/>.
	///
	/// See: IsAnyWorkItemInProgress
	/// </summary>
	public bool IsAnyGraphUpdateInProgress => graphUpdates.IsAnyGraphUpdateInProgress;

	/// <summary>
	/// 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 <see cref="AddWorkItem"/>.
	/// </summary>
	public bool IsAnyWorkItemInProgress => workItems.workItemsInProgress;

	/// <summary>
	/// 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 <see cref="AddWorkItem"/>.
	///
	/// In contrast to <see cref="IsAnyWorkItemInProgress"/> 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.
	/// </summary>
	internal bool IsInsideWorkItem => workItems.workItemsInProgressRightNow;

	#endregion

	#region Callbacks
	/// <summary>
	/// Called on Awake before anything else is done.
	/// This is called at the start of the Awake call, right after <see cref="active"/> 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)
	/// <code>
	/// // Create a new AstarPath object on Start and apply some default settings
	/// public void Start () {
	///     AstarPath.OnAwakeSettings += ApplySettings;
	///     AstarPath astar = gameObject.AddComponent<AstarPath>();
	/// }
	///
	/// 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;
	/// }
	/// </code>
	/// </summary>
	public static System.Action OnAwakeSettings;

	/// <summary>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.</summary>
	public static OnGraphDelegate OnGraphPreScan;

	/// <summary>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.</summary>
	public static OnGraphDelegate OnGraphPostScan;

	/// <summary>Called for each path before searching. Be careful when using multithreading since this will be called from a different thread.</summary>
	public static OnPathDelegate OnPathPreSearch;

	/// <summary>Called for each path after searching. Be careful when using multithreading since this will be called from a different thread.</summary>
	public static OnPathDelegate OnPathPostSearch;

	/// <summary>Called before starting the scanning. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.</summary>
	public static OnScanDelegate OnPreScan;

	/// <summary>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.</summary>
	public static OnScanDelegate OnPostScan;

	/// <summary>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.</summary>
	public static OnScanDelegate OnLatePostScan;

	/// <summary>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.</summary>
	public static OnScanDelegate OnGraphsUpdated;

	/// <summary>
	/// 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.
	/// </summary>
	public static System.Action On65KOverflow;

	/// <summary>
	/// 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.
	/// </summary>
	public static System.Action OnPathsCalculated;

	#endregion

	#region MemoryStructures

	/// <summary>Processes graph updates</summary>
	readonly GraphUpdateProcessor graphUpdates;

	/// <summary>Holds a hierarchical graph to speed up some queries like if there is a path between two nodes</summary>
	internal readonly HierarchicalGraph hierarchicalGraph;

	/// <summary>Holds all active off-mesh links</summary>
	public readonly OffMeshLinks offMeshLinks;

	/// <summary>
	/// Handles navmesh cuts.
	/// See: <see cref="Pathfinding.NavmeshCut"/>
	/// </summary>
	public NavmeshUpdates navmeshUpdates = new NavmeshUpdates();

	/// <summary>Processes work items</summary>
	readonly WorkItemProcessor workItems;

	/// <summary>Holds all paths waiting to be calculated and calculates them</summary>
	internal readonly PathProcessor pathProcessor;

	/// <summary>Holds global node data that cannot be stored in individual graphs</summary>
	internal GlobalNodeStorage nodeStorage;

	/// <summary>
	/// 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 <see cref="GraphModifier"/> events run.
	/// - When graph related callbacks, such as <see cref="OnGraphsUpdated"/>, run.
	/// - During the last step of a graph's scanning process, when the new graph data is being made visible to the rest of the game. See <see cref="ScanningStage"/>.
	///
	/// 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.
	///
	/// <code>
	/// var readLock = AstarPath.active.LockGraphDataForReading();
	/// var handle = new MyJob {
	///     // ...
	/// }.Schedule(readLock.dependency);
	/// readLock.UnlockAfter(handle);
	/// </code>
	///
	/// See: <see cref="LockGraphDataForReading"/>
	/// </summary>
	RWLock graphDataLock = new RWLock();

	/// <summary>Makes sure QueueGraphUpdates will not queue multiple graph update orders</summary>
	bool graphUpdatesWorkItemAdded = false;

	/// <summary>
	/// Time the last graph update was done.
	/// Used to group together frequent graph updates to batches
	/// </summary>
	float lastGraphUpdate = float.NegativeInfinity;

	/// <summary>Held if any work items are currently queued</summary>
	PathProcessor.GraphUpdateLock workItemLock;

	/// <summary>Holds all completed paths waiting to be returned to where they were requested</summary>
	internal readonly PathReturnQueue pathReturnQueue;

	/// <summary>
	/// Holds settings for heuristic optimization.
	/// See: heuristic-opt (view in online documentation for working links)
	/// </summary>
	public EuclideanEmbedding euclideanEmbedding = new EuclideanEmbedding();

	/// <summary>
	/// If an async scan is running, this will be set to the coroutine.
	///
	/// This primarily used to be able to force the async scan to complete immediately,
	/// if the AstarPath component should happen to be destroyed while an async scan is running.
	/// </summary>
	IEnumerator<Progress> asyncScanTask;

	#endregion

	/// <summary>
	/// Shows or hides graph inspectors.
	/// Used internally by the editor.
	///
	/// See: <see cref="showNavGraphs"/>
	/// See: <see cref="showGraphsInStandalonePlayer"/>
	/// </summary>
	public bool showGraphs = false;

	/// <summary>
	/// Shows or hides the graph visualizations in standalone games.
	/// Normally, graph visualizations are only shown in the editor.
	/// If this is set to true, the graph visualizations will also be shown in standalone games.
	///
	/// Note: In the editor, this setting has no effect.
	///
	/// Note: <see cref="showNavGraphs"/> will be ignored in standalone builds, and this value will be used instead.
	///
	/// See: <see cref="NavGraph.drawGizmos"/>
	/// </summary>
	public bool showGraphsInStandalonePlayer = false;

	/// <summary>
	/// The next unused Path ID.
	/// Incremented for every call to GetNextPathID
	/// </summary>
	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 += (pathHandler, path) => {
			LogPathResults(pathHandler.DebugStringBuilder, path);
			var tmp = OnPathPostSearch;
			if (tmp != null) tmp(path);
		};

		// Sent every time the path queue is unblocked
		pathProcessor.OnQueueUnblocked += () => {
			if (euclideanEmbedding.dirty) {
				euclideanEmbedding.RecalculateCosts();
			}
		};
	}

	/// <summary>
	/// 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
	/// </summary>
	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;
	}

	/// <summary>
	/// 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 <see cref="active"/> 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.
	/// </summary>
	public static void FindAstarPath () {
		if (Application.isPlaying) return;
		if (active == null) active = UnityCompatibility.FindAnyObjectByType<AstarPath>();
		if (active != null && (active.data.graphs == null || active.data.graphs.Length == 0)) active.data.DeserializeGraphs();
	}

	/// <summary>
	/// 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
	/// </summary>
	public static string[] FindTagNames () {
		FindAstarPath();
		return active != null? active.GetTagNames () : new string[1] { "There is no AstarPath component in the scene" };
	}

	/// <summary>Returns the next free path ID</summary>
	internal ushort GetNextPathID () {
		if (nextFreePathID == 0) {
			nextFreePathID++;

			if (On65KOverflow != null) {
				System.Action tmp = On65KOverflow;
				On65KOverflow = null;
				tmp();
			}
		}
		return nextFreePathID++;
	}

#if UNITY_EDITOR
	struct DebugLimitsCtx {
		public UnsafeSpan<GlobalNodeStorage.DebugPathNode> debugPathNodes;
		public ushort debugPathID;
		public float debugFloor;
		public float debugRoof;
		public GraphDebugMode debugMode;
	}
#endif

	void RecalculateDebugLimits () {
#if UNITY_EDITOR
		var ctx = new DebugLimitsCtx {
			debugPathNodes = showSearchTree && debugPathData != null && debugPathData.threadID < active.nodeStorage.pathfindingThreadData.Length ? active.nodeStorage.pathfindingThreadData[debugPathData.threadID].debugPathNodes : default,
			debugPathID = debugPathID,
			debugFloor = float.PositiveInfinity,
			debugRoof = float.NegativeInfinity,
			debugMode = debugMode,
		};

		for (int i = 0; i < graphs.Length; i++) {
			if (graphs[i] != null && graphs[i].drawGizmos) {
				graphs[i].GetNodes(static (GraphNode node, ref DebugLimitsCtx ctx) => {
					if (node.Walkable && (ctx.debugPathNodes.length == 0 || GraphGizmoHelper.InSearchTree(node, ctx.debugPathNodes, ctx.debugPathID))) {
						float value;
						if (ctx.debugMode == GraphDebugMode.Penalty) {
							value = node.Penalty;
						} else if (ctx.debugPathNodes.Length > 0) {
							var rnode = ctx.debugPathNodes[node.NodeIndex];
							switch (ctx.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;
						}
						ctx.debugFloor = Mathf.Min(ctx.debugFloor, value);
						ctx.debugRoof = Mathf.Max(ctx.debugRoof, value);
					}
				}, ref ctx);
			}
		}

		if (float.IsInfinity(ctx.debugFloor)) {
			ctx.debugFloor = 0;
			ctx.debugRoof = 1;
		}

		// Make sure they are not identical, that will cause the color interpolation to fail
		if (ctx.debugRoof-ctx.debugFloor < 1) ctx.debugRoof += 1;
		debugFloor = ctx.debugFloor;
		debugRoof = ctx.debugRoof;
#else
		debugFloor = 0;
		debugRoof = 1;
#endif
	}

	/// <summary>Calls OnDrawGizmos on all graphs</summary>
	public override void DrawGizmos () {
		if (active != this || graphs == null) {
			return;
		}

		InitializeColors();

		var render = Application.isEditor ? showNavGraphs : showGraphsInStandalonePlayer;
		var renderInGame = showGraphsInStandalonePlayer && !Application.isEditor;

		// Note: even when graph updates are in-progress, the graph data will always be consistent and valid on the main thread.
		// So we can draw gizmos at any time.

		if (render && !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, render, default, renderInGame);
		}
		Profiler.EndSample();

		if (render) {
			euclideanEmbedding.OnDrawGizmos();
			if (debugMode == GraphDebugMode.HierarchicalNode) hierarchicalGraph.OnDrawGizmos(DrawingManager.instance.gizmos, renderInGame);
			if (debugMode == GraphDebugMode.NavmeshBorderObstacles) hierarchicalGraph.navmeshEdges.OnDrawGizmos(DrawingManager.instance.gizmos, renderInGame);
		}

		workItems.DrawGizmos();
	}

#if !ASTAR_NO_GUI
	/// <summary>
	/// Draws the InGame debugging (if enabled)
	/// See: <see cref="logPathResults"/> PathLog
	/// </summary>
	private void OnGUI () {
		if (logPathResults == PathLog.InGame && inGameDebugPath != "") {
			GUI.Label(new Rect(5, 5, 400, 600), inGameDebugPath);
		}
	}
#endif

	/// <summary>
	/// Prints path results to the log. What it prints can be controled using <see cref="logPathResults"/>.
	/// See: <see cref="logPathResults"/>
	/// See: PathLog
	/// See: Pathfinding.Path.DebugString
	/// </summary>
	private void LogPathResults (System.Text.StringBuilder debugStringBuilder, Path path) {
		if (logPathResults != PathLog.None && (path.error || logPathResults != PathLog.OnlyErrors)) {
			debugStringBuilder.Clear();
			(path as IPathInternals).DebugString(debugStringBuilder, logPathResults);
			var output = debugStringBuilder.ToString();

			if (logPathResults == PathLog.InGame) {
				inGameDebugPath = output;
			} else if (path.error) {
				Debug.LogWarning(output);
			} else {
				Debug.Log(output);
			}
		}
	}

	/// <summary>
	/// 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
	/// </summary>
	private void Update () {
		navmeshUpdates.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;

		TryQueueGraphUpdates();

		// 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);

		if (!Application.isEditor && showGraphsInStandalonePlayer) {
			showGraphs = showGraphsInStandalonePlayer;
			DrawGizmos();
		}
	}

	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();
		}
	}

	/// <summary>
	/// Add a work item to be processed when pathfinding is paused.
	///
	/// The callback will be called once when it is safe to update graphs.
	///
	/// This is a convenience method that is equivalent to
	/// <code>
	/// AddWorkItem(new AstarWorkItem(callback));
	/// </code>
	///
	/// See: <see cref="AddWorkItem(AstarWorkItem)"/>
	/// </summary>
	public void AddWorkItem (System.Action callback) {
		AddWorkItem(new AstarWorkItem(callback));
	}

	/// <summary>
	/// Add a work item to be processed when pathfinding is paused.
	///
	/// THe callback will be called once when it is safe to update graphs.
	///
	/// This is a convenience method that is equivalent to
	/// <code>
	/// AddWorkItem(new AstarWorkItem(callback));
	/// </code>
	///
	/// See: <see cref="AddWorkItem(AstarWorkItem)"/>
	/// </summary>
	public void AddWorkItem (System.Action<IWorkItemContext> callback) {
		AddWorkItem(new AstarWorkItem(callback));
	}

	/// <summary>
	/// 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).
	///
	/// <code>
	/// AstarPath.active.AddWorkItem(new AstarWorkItem(() => {
	///     // Safe to update graphs here
	///     var node = AstarPath.active.GetNearest(transform.position).node;
	///     node.Walkable = false;
	/// }));
	/// </code>
	///
	/// <code>
	/// AstarPath.active.AddWorkItem(() => {
	///     // Safe to update graphs here
	///     var node = AstarPath.active.GetNearest(transform.position).node;
	///     node.position = (Int3)transform.position;
	/// });
	/// </code>
	///
	/// You can run work items over multiple frames:
	/// <code>
	/// AstarPath.active.AddWorkItem(new AstarWorkItem(() => {
	///     // Called once, right before the
	///     // first call to the method below
	/// },
	///     force => {
	///     // Called every frame until complete.
	///     // Signal that the work item is
	///     // complete by returning true.
	///     // The "force" parameter will
	///     // be true if the work item is
	///     // required to complete immediately.
	///     // In that case this method should
	///     // block and return true when done.
	///     return true;
	/// }));
	/// </code>
	///
	/// See: <see cref="FlushWorkItems"/>
	/// </summary>
	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

	/// <summary>
	/// Will apply queued graph updates as soon as possible, regardless of <see cref="batchGraphUpdates"/>.
	/// Calling this multiple times, before graph updates are applied, is equivalent to calling it once.
	/// 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 <see cref="batchGraphUpdates"/> time limit.
	///
	/// See: <see cref="FlushGraphUpdates"/>
	/// </summary>
	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));
		}
	}

	/// <summary>Calls <see cref="QueueGraphUpdates"/>, unless we should wait for a larger batch</summary>
	void TryQueueGraphUpdates () {
		if (IsAnyGraphUpdateQueued && (!batchGraphUpdates || Time.realtimeSinceStartup >= lastGraphUpdate + graphUpdateBatchingInterval)) {
			QueueGraphUpdates();
		}
	}

	/// <summary>
	/// Update all graphs within bounds after delay seconds.
	/// The graphs will be updated as soon as possible.
	///
	/// <code>
	/// // Create an obstacle
	/// var obstacle = GameObject.Instantiate(someObstacle, somePosition, Quaternion.identity);
	///
	/// // Use the bounding box from the attached collider
	/// var bounds = obstacle.GetComponent<Collider>().bounds;
	///
	/// // Update the graph to account for the new obstacle
	/// AstarPath.active.UpdateGraphs(bounds);
	/// </code>
	///
	/// See: FlushGraphUpdates
	/// See: batchGraphUpdates
	/// See: graph-updates (view in online documentation for working links)
	/// </summary>
	public void UpdateGraphs (Bounds bounds, float delay) {
		UpdateGraphs(new GraphUpdateObject(bounds), delay);
	}

	/// <summary>
	/// 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.
	///
	/// <code>
	/// // using Pathfinding; //At top of script
	///
	/// // As an example, use the bounding box from the attached collider
	/// Bounds bounds = GetComponent<Collider>().bounds;
	/// var guo = new GraphUpdateObject(bounds);
	///
	/// // Set some settings
	/// guo.updatePhysics = true;
	/// AstarPath.active.UpdateGraphs(guo);
	/// </code>
	///
	/// See: FlushGraphUpdates
	/// See: batchGraphUpdates
	/// See: graph-updates (view in online documentation for working links)
	/// </summary>
	public void UpdateGraphs (GraphUpdateObject ob, float delay) {
		StartCoroutine(UpdateGraphsInternal(ob, delay));
	}

	/// <summary>Update all graphs using the GraphUpdateObject after delay seconds</summary>
	IEnumerator UpdateGraphsInternal (GraphUpdateObject ob, float delay) {
		yield return new WaitForSeconds(delay);
		UpdateGraphs(ob);
	}

	/// <summary>
	/// Update all graphs within bounds.
	/// The graphs will be updated as soon as possible.
	///
	/// This is equivalent to
	/// <code>
	/// UpdateGraphs(new GraphUpdateObject(bounds));
	/// </code>
	///
	/// <code>
	/// // Create an obstacle
	/// var obstacle = GameObject.Instantiate(someObstacle, somePosition, Quaternion.identity);
	///
	/// // Use the bounding box from the attached collider
	/// var bounds = obstacle.GetComponent<Collider>().bounds;
	///
	/// // Update the graph to account for the new obstacle
	/// AstarPath.active.UpdateGraphs(bounds);
	/// </code>
	///
	/// See: FlushGraphUpdates
	/// See: batchGraphUpdates
	/// See: graph-updates (view in online documentation for working links)
	/// </summary>
	public void UpdateGraphs (Bounds bounds) {
		UpdateGraphs(new GraphUpdateObject(bounds));
	}

	/// <summary>
	/// 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 cref="batchGraphUpdates)"/>
	///
	/// <code>
	/// // using Pathfinding; //At top of script
	///
	/// // As an example, use the bounding box from the attached collider
	/// Bounds bounds = GetComponent<Collider>().bounds;
	/// var guo = new GraphUpdateObject(bounds);
	///
	/// // Set some settings
	/// guo.updatePhysics = true;
	/// AstarPath.active.UpdateGraphs(guo);
	/// </code>
	///
	/// See: FlushGraphUpdates
	/// See: batchGraphUpdates
	/// See: graph-updates (view in online documentation for working links)
	/// </summary>
	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);

		TryQueueGraphUpdates();
	}

	/// <summary>
	/// 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 <see cref="FlushWorkItems"/>, 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).
	/// </summary>
	public void FlushGraphUpdates () {
		if (IsAnyGraphUpdateQueued || IsAnyGraphUpdateInProgress) {
			QueueGraphUpdates();
			FlushWorkItems();
		}
	}

	#endregion

	/// <summary>
	/// 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 <see cref="FlushGraphUpdates"/>, but added for more descriptive name.
	///
	/// Will not do anything if there are no queued work items waiting to run.
	/// </summary>
	public void FlushWorkItems () {
		if (workItems.anyQueued || workItems.workItemsInProgress) {
			if (active != this) throw new System.Exception("This AstarPath component is not initialized in a scene. Are you trying to add work items to a prefab or a disabled AstarPath component?");
			using (PausePathfinding()) {
				PerformBlockingActions(true);
			}
		}
	}

	/// <summary>Initializes the <see cref="pathProcessor"/> field</summary>
	void InitializePathProcessor () {
		int numThreads = threadCount.ToConcreteThreadCount();

		// 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);
	}

	void InitializeColors () {
		colorSettings = colorSettings ?? new AstarColor();
		colorSettings.PushToStatic();
	}

	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();
	}

	/// <summary>
	/// Called after this component is enabled.
	///
	/// Unless the component has already been activated in Awake, this method should:
	/// - Ensure the singleton holds (setting <see cref="active"/> to this).
	/// - Make sure all subsystems that were disabled in OnDisable are again enabled.
	///   - This includes starting pathfinding threads.
	/// </summary>
	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();

		InitializeColors();

		navmeshUpdates.OnEnable();

		// 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) {
			// 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();
			}
		}
	}


	/// <summary>
	/// 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)
	/// </summary>
	void OnDisable () {
		if (active == this) {
			if (asyncScanTask != null) {
				Debug.LogWarning("An async scan was running when the AstarPath component was disabled. Blocking until the async scan is complete.", this);
				BlockUntilAsyncScanComplete();
			}

			// 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;
		}
	}

	/// <summary>
	/// Clears up variables and other stuff, destroys graphs.
	/// Note that when destroying an AstarPath object, all static variables such as callbacks will be cleared.
	/// </summary>
	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 all static variables, 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

	/// <summary>
	/// 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.
	///
	/// <code>
	/// var nodes = new PointNode[128];
	/// var job = AstarPath.active.AllocateNodes(nodes, 128, () => new PointNode(), 1);
	///
	/// job.Complete();
	/// </code>
	///
	/// See: <see cref="InitializeNode"/>
	/// </summary>
	/// <param name="result">Node array to fill</param>
	/// <param name="count">How many nodes to allocate</param>
	/// <param name="createNode">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.</param>
	/// <param name="variantsPerNode">How many variants of the node to allocate. Should be the same as \reflink{GraphNode.PathNodeVariants} for this node type.</param>
	public Unity.Jobs.JobHandle AllocateNodes<T>(T[] result, int count, System.Func<T> 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);
	}

	/// <summary>
	/// Initializes temporary path data for a node.
	///
	/// Use like: InitializeNode(new PointNode())
	///
	/// See: <see cref="AstarPath.AllocateNodes"/>
	/// </summary>
	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]);
	}

	/// <summary>
	/// 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.
	/// </summary>
	internal void DestroyNode (GraphNode node) {
		nodeStorage.DestroyNode(node);
	}

	/// <summary>
	/// Blocks until all pathfinding threads are paused and blocked.
	///
	/// <code>
	/// 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();
	/// </code>
	///
	/// Returns: A lock object. You need to call <see cref="Pathfinding.PathProcessor.GraphUpdateLock.Release"/> on that object to allow pathfinding to resume.
	/// Note: In most cases this should not be called from user code. Use the <see cref="AddWorkItem"/> method instead.
	///
	/// See: <see cref="AddWorkItem"/>
	/// </summary>
	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);
	}

	/// <summary>
	/// 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.
	/// </summary>
	public PathProcessor.GraphUpdateLock PausePathfindingSoon () {
		return pathProcessor.PausePathfinding(false);
	}

	/// <summary>Blocks until the currently running async scan (if any) has completed</summary>
	void BlockUntilAsyncScanComplete () {
		// We can't block and wait for the async scan, so we have to spin.
		// Not great, but this is not something that should happen during normal gameplay.
		// It's more a fallback if the user doesn't wait for the async scan to complete before starting a new one.
		// Note: The ProgressScanningIteratorsConcurrently method used internally by the scan will ensure
		// that the thread yields its time slice in case it's just waiting for other threads.
		while (asyncScanTask != null && asyncScanTask.MoveNext()) {}
		asyncScanTask = null;
	}

	/// <summary>
	/// 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.
	///
	/// <code>
	/// // 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);
	/// </code>
	///
	/// See: graph-updates (view in online documentation for working links)
	/// See: ScanAsync
	/// </summary>
	public void Scan (NavGraph graphToScan) {
		if (graphToScan == null) throw new System.ArgumentNullException();
		Scan(new NavGraph[] { graphToScan });
	}

	/// <summary>
	/// 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.
	///
	/// <code>
	/// // 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);
	/// </code>
	///
	/// See: graph-updates (view in online documentation for working links)
	/// See: ScanAsync
	/// </summary>
	/// <param name="graphsToScan">The graphs to scan. If this parameter is null then all graphs will be scanned</param>
	public void Scan (NavGraph[] graphsToScan = null) {
		var prevStage = (ScanningStage)(-1);

		if (asyncScanTask != null) {
			Debug.LogWarning("An async scan was already running when a new scan was requested. Blocking until it is complete. You can check if a scan is currently in progress using the AstarPath.active.isScanning property.", this);
			BlockUntilAsyncScanComplete();
		}

		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();
	}

	/// <summary>
	/// 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.
	///
	/// <code>
	/// IEnumerator Start () {
	///     foreach (Progress progress in AstarPath.active.ScanAsync()) {
	///         Debug.Log("Scanning... " + progress.ToString());
	///         yield return null;
	///     }
	/// }
	/// </code>
	///
	/// See: Scan
	/// </summary>
	public IEnumerable<Progress> ScanAsync (NavGraph graphToScan) {
		if (graphToScan == null) throw new System.ArgumentNullException();
		return ScanAsync(new NavGraph[] { graphToScan });
	}

	/// <summary>
	/// Scans all specified graphs asynchronously. This is a IEnumerable, you can loop through it to get the progress
	///
	/// You can scan graphs asyncronously by yielding when you loop through the progress.
	/// Note that this does not guarantee a good framerate, but it will allow you
	/// to at least show a progress bar during scanning.
	///
	/// <code>
	/// IEnumerator Start () {
	///     foreach (Progress progress in AstarPath.active.ScanAsync()) {
	///         Debug.Log("Scanning... " + progress.ToString());
	///         yield return null;
	///     }
	/// }
	/// </code>
	///
	/// Note: If the graphs are already scanned, doing an async scan will temporarily cause increased memory usage, since two copies of the graphs will be kept in memory during the async scan.
	/// This may not be desirable on some platforms. A non-async scan will not cause this temporary increased memory usage.
	///
	/// See: Scan
	/// </summary>
	/// <param name="graphsToScan">The graphs to scan. If this parameter is null then all graphs will be scanned</param>
	public IEnumerable<Progress> ScanAsync (NavGraph[] graphsToScan = null) {
		if (asyncScanTask != null) {
			Debug.LogWarning("An async scan was already running when a new async scan was requested. Blocking until the previous one is complete. You can check if a scan is currently in progress using the AstarPath.active.isScanning property.", this);
			BlockUntilAsyncScanComplete();
		}
		asyncScanTask = ScanInternal(graphsToScan, true).GetEnumerator();
		// We cannot inline the TickAsyncScanUntilCompletion function, because we want *this* function to
		// not be a coroutine, so that the setup runs immediately when calling ScanAsync,
		// instead of defering until the coroutine is ticked for the first time.

		// We tick the coroutine once here to do some inital setup.
		// This includes setting isScanning to true.
		try {
			asyncScanTask.MoveNext();
		} catch {
			asyncScanTask = null;
			throw;
		}
		return TickAsyncScanUntilCompletion(asyncScanTask);
	}

	IEnumerable<Progress> TickAsyncScanUntilCompletion (IEnumerator<Progress> task) {
		while (true) {
			try {
				if (!task.MoveNext()) break;
			} catch {
				if (asyncScanTask == task) asyncScanTask = null;
				throw;
			}
			yield return task.Current;
		}
		if (asyncScanTask == task) asyncScanTask = null;
	}

	class DummyGraphUpdateContext : IGraphUpdateContext {
		public void DirtyBounds (Bounds bounds) {}
	}

	class DestroyGraphPromise : IGraphUpdatePromise {
		public IGraphInternals graph;
		public IEnumerator<JobHandle> Prepare () {
			return null;
		}
		public void Apply (IGraphUpdateContext context) {
			graph.DestroyAllNodes();
		}
	}

	IEnumerable<Progress> ScanInternal (NavGraph[] graphsToScan, bool async) {
		if (graphsToScan == null) graphsToScan = graphs;

		if (graphsToScan == null || graphsToScan.Length == 0) {
			yield break;
		}

		// Guard to ensure the A* object is always enabled if the graphs have any valid data.
		// This is because otherwise the OnDisable method will not be called and some unmanaged data
		// in NativeArrays may end up leaking.
		if (!enabled) throw new System.InvalidOperationException("The AstarPath object must be enabled to scan graphs");
		if (active != this) throw new System.InvalidOperationException("The AstarPath object is not enabled in a scene");

		isScanning = true;

		var graphUpdateLock = PausePathfinding();

		// Make sure all paths that are in the queue to be returned
		// are returned immediately
		// Some modifiers (e.g the funnel modifier) rely on
		// the nodes being valid when the path is returned
		pathReturnQueue.ReturnPaths(false);

		// Ensure all graph updates that are in progress get completed immediately.
		// Graph updates that are in progress may use graph data, and we don't want to re-scan the graphs under their feet.
		workItems.ProcessWorkItemsForScan(true);

		if (!Application.isPlaying) {
			data.FindGraphTypes();
			GraphModifier.FindAllModifiers();
		}


		yield return new Progress(0.05F, ScanningStage.PreProcessingGraphs);


		using (var writeLock2 = graphDataLock.WriteSync()) {
			try {
				if (OnPreScan != null) {
					OnPreScan(this);
				}

				GraphModifier.TriggerEvent(GraphModifier.EventType.PreScan);
				GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate);
			} catch {
				isScanning = false;
				graphUpdateLock.Release();
				throw;
			}
		}

		data.LockGraphStructure();

		// Make sure the physics engine data is up to date.
		// Scanning graphs may use physics methods and it is very confusing if they
		// do not always pick up the latest changes made to the scene.
		Physics.SyncTransforms();
		Physics2D.SyncTransforms();

		var watch = System.Diagnostics.Stopwatch.StartNew();

		// Destroy previous nodes, unless we are doing an async scan.
		// We do not want the graphs to be in an invalid state during the async scan,
		// so we cannot eagerly destroy them here.
		// This means that during an async scan we may have two copies of the graphs in memory.
		// Most of the data will be destroyed at the end of the async scan, but some memory will
		// still be reserved. So a non-async scan is more memory efficient.
		if (!async) {
			using (var writeLock2 = graphDataLock.WriteSync()) {
				Profiler.BeginSample("Destroy previous nodes");
				for (int i = 0; i < graphsToScan.Length; i++) {
					if (graphsToScan[i] != null) {
						((IGraphInternals)graphsToScan[i]).DestroyAllNodes();
					}
				}
				Profiler.EndSample();
			}
		}

		if (OnGraphPreScan != null) {
			using (var writeLock2 = graphDataLock.WriteSync()) {
				try {
					for (int i = 0; i < graphsToScan.Length; i++) {
						if (graphsToScan[i] != null) OnGraphPreScan(graphsToScan[i]);
					}
				} catch {
					isScanning = false;
					data.UnlockGraphStructure();
					graphUpdateLock.Release();
					throw;
				}
			}
		}

		// Loop through all graphs and start scanning them
		var promises = new List<(IGraphUpdatePromise, IEnumerator<JobHandle>)>(graphsToScan.Length);
		for (int i = 0; i < graphsToScan.Length; i++) {
			if (graphsToScan[i] != null) {
				var promise = ((IGraphInternals)graphsToScan[i]).ScanInternal(async) ?? new DestroyGraphPromise { graph = (IGraphInternals)graphsToScan[i] };
				var iterator = promise.Prepare();
				promises.Add((promise, iterator));
			}
		}

		// Scan all graphs concurrently by progressing all scanning iterators.
		// If the graphs use the job system internally (like the grid, recast and navmesh graphs),
		// then multiple graphs will even be scanned in parallel.
		while (true) {
			int firstNonFinished;
			try {
				firstNonFinished = GraphUpdateProcessor.PrepareGraphUpdatePromises(promises, async ? TimeSlice.MillisFromNow(2) : TimeSlice.Infinite);
			} catch {
				isScanning = false;
				data.UnlockGraphStructure();
				graphUpdateLock.Release();
				throw;
			}
			if (firstNonFinished == -1) {
				break;
			} else {
				// Just used for progress information
				// This graph will advance the progress bar from minp to maxp
				float meanProgress = 0;
				for (int i = 0; i < promises.Count; i++) meanProgress += promises[i].Item1.Progress;
				meanProgress /= promises.Count;
				yield return new Progress(Mathf.Lerp(0.1f, 0.8f, meanProgress), ScanningStage.ScanningGraph, firstNonFinished, promises.Count);
			}
		}

		yield return new Progress(0.95f, ScanningStage.FinishingScans);

		// Now we proceed with the last step of each graph's scanning process
		// This part will make the results of the scan visible to the rest of the game.
		// As a consequence, we must make sure to *not* yield anymore after this point,
		// since that would make the rest of the game run while the graphs may be in an invalid state.
		var writeLock = graphDataLock.WriteSync();

		var ctx = new DummyGraphUpdateContext();
		try {
			GraphUpdateProcessor.ApplyGraphUpdatePromises(promises, ctx);
		} catch {
			isScanning = false;
			data.UnlockGraphStructure();
			graphUpdateLock.Release();
			writeLock.Unlock();
			throw;
		}


		for (int i = 0; i < graphsToScan.Length; i++) {
			if (graphsToScan[i] != null) {
				if (OnGraphPostScan != null) {
					try {
						OnGraphPostScan(graphsToScan[i]);
					} catch {
						isScanning = false;
						data.UnlockGraphStructure();
						graphUpdateLock.Release();
						writeLock.Unlock();
						throw;
					}
				}
				// Notify the off mesh links subsystem that graphs have been recalculated, and we may need to recalculate off mesh links.
				// But skip this for the link graph, since that's the graph that holds the off mesh link nodes themselves.
				if (!(graphsToScan[i] is LinkGraph)) offMeshLinks.DirtyBounds(graphsToScan[i].bounds);
			}
		}

		// Unlock the graph structure here so that e.g. off-mesh-links can add the point graph required for them to work
		data.UnlockGraphStructure();

		try {
			// Graph Modifiers and the OnGraphsUpdated callback may modify graphs arbitrarily, so this also needs to be inside the write lock
			if (OnPostScan != null) OnPostScan(this);
			GraphModifier.TriggerEvent(GraphModifier.EventType.PostScan);
		} catch {
			isScanning = false;
			graphUpdateLock.Release();
			writeLock.Unlock();
			throw;
		}

		// This lock may not be held if there are no work items pending
		if (workItemLock.Held) {
			Profiler.BeginSample("Work Items");
			// Note that this never sends PostUpdate (or similar) events. Those are sent below instead.
			workItems.ProcessWorkItemsForScan(true);
			Profiler.EndSample();
			workItemLock.Release();
		}

		offMeshLinks.Refresh();

		GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdateBeforeAreaRecalculation);

		// Recalculate connected components synchronously
		hierarchicalGraph.RecalculateIfNecessary();

		// Scanning a graph *is* a type of update
		GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate);
		if (OnGraphsUpdated != null) {
			try {
				OnGraphsUpdated(this);
			} catch {
				isScanning = false;
				graphUpdateLock.Release();
				writeLock.Unlock();
				throw;
			}
		}

		// Signal that we have stopped scanning here
		isScanning = false;

		try {
			if (OnLatePostScan != null) OnLatePostScan(this);
			GraphModifier.TriggerEvent(GraphModifier.EventType.LatePostScan);
		} catch {
			graphUpdateLock.Release();
			writeLock.Unlock();
			throw;
		}

		writeLock.Unlock();

		euclideanEmbedding.dirty = true;
		euclideanEmbedding.RecalculatePivots();

		// Perform any blocking actions
		FlushWorkItems();
		// Resume pathfinding threads
		graphUpdateLock.Release();

		watch.Stop();
		lastScanTime = (float)watch.Elapsed.TotalSeconds;

		if (logPathResults != PathLog.None && logPathResults != PathLog.OnlyErrors) {
			Debug.Log("Scanned graphs in " + (lastScanTime*1000).ToString("0") + " ms");
		}
	}

	#endregion

	internal void DirtyBounds (Bounds bounds) {
		offMeshLinks.DirtyBounds(bounds);
		workItems.DirtyBounds(bounds);
	}

	internal void VisualizeOriginalGraphUpdateBounds (Bounds bounds) {
		workItems.VisualizeOriginalGraphUpdateBounds(bounds);
	}

	private static int waitForPathDepth = 0;

	/// <summary>
	/// Blocks until the path has been calculated.
	///
	/// Normally it takes a few frames for a path to be calculated and returned.
	/// This function will ensure that the path will be calculated when this function returns
	/// and that the callback for that path has been called.
	///
	/// If requesting a lot of paths in one go and waiting for the last one to complete,
	/// it will calculate most of the paths in the queue (only most if using multithreading, all if not using multithreading).
	///
	/// Use this function only if you really need to.
	/// There is a point to spreading path calculations out over several frames.
	/// It smoothes out the framerate and makes sure requesting a large
	/// number of paths at the same time does not cause lag.
	///
	/// Note: Graph updates and other callbacks might get called during the execution of this function.
	///
	/// When the pathfinder is shutting down. I.e in OnDestroy, this function will not do anything.
	///
	/// Throws: Exception if pathfinding is not initialized properly for this scene (most likely no AstarPath object exists)
	/// or if the path has not been started yet.
	/// Also throws an exception if critical errors occur such as when the pathfinding threads have crashed (which should not happen in normal cases).
	/// This prevents an infinite loop while waiting for the path.
	///
	/// See: Pathfinding.Path.WaitForPath
	/// See: Pathfinding.Path.BlockUntilCalculated
	/// </summary>
	/// <param name="path">The path to wait for. The path must be started, otherwise an exception will be thrown.</param>
	public static void BlockUntilCalculated (Path path) {
		if (active == null)
			throw new System.Exception("Pathfinding is not correctly initialized in this scene (yet?). " +
				"AstarPath.active is null.\nDo not call this function in Awake");

		if (path == null) throw new System.ArgumentNullException(nameof(path));

		if (active.pathProcessor.queue.isClosed) return;

		if (path.PipelineState == PathState.Created) {
			throw new System.Exception("The specified path has not been started yet.");
		}

		waitForPathDepth++;

		if (waitForPathDepth == 5) {
			Debug.LogError("You are calling the BlockUntilCalculated function recursively (maybe from a path callback). Please don't do this.");
		}

		if (path.PipelineState < PathState.ReturnQueue) {
			if (active.IsUsingMultithreading) {
				while (path.PipelineState < PathState.ReturnQueue) {
					if (active.pathProcessor.queue.isClosed) {
						waitForPathDepth--;
						throw new System.Exception("Pathfinding Threads seem to have crashed.");
					}

					// Wait for threads to calculate paths
					Thread.Sleep(1);
					active.PerformBlockingActions(true);
				}
			} else {
				while (path.PipelineState < PathState.ReturnQueue) {
					if (active.pathProcessor.queue.isEmpty && path.PipelineState != PathState.Processing) {
						waitForPathDepth--;
						throw new System.Exception("Critical error. Path Queue is empty but the path state is '" + path.PipelineState + "'");
					}

					// Calculate some paths
					active.pathProcessor.TickNonMultithreaded();
					active.PerformBlockingActions(true);
				}
			}
		}

		active.pathReturnQueue.ReturnPaths(false);
		waitForPathDepth--;
	}

	/// <summary>
	/// Adds the path to a queue so that it will be calculated as soon as possible.
	/// The callback specified when constructing the path will be called when the path has been calculated.
	/// Usually you should use the Seeker component instead of calling this function directly.
	///
	/// <code>
	/// // There must be an AstarPath instance in the scene
	/// if (AstarPath.active == null) return;
	///
	/// // We can calculate multiple paths asynchronously
	/// for (int i = 0; i < 10; i++) {
	///     var path = ABPath.Construct(transform.position, transform.position+transform.forward*i*10, OnPathComplete);
	///
	///     // Calculate the path by using the AstarPath component directly
	///     AstarPath.StartPath(path);
	/// }
	/// </code>
	/// </summary>
	/// <param name="path">The path that should be enqueued.</param>
	/// <param name="pushToFront">If true, the path will be pushed to the front of the queue, bypassing all waiting paths and making it the next path to be calculated.
	///    This can be useful if you have a path which you want to prioritize over all others. Be careful to not overuse it though.
	///    If too many paths are put in the front of the queue often, this can lead to normal paths having to wait a very long time before being calculated.</param>
	/// <param name="assumeInPlayMode">Typically path.BlockUntilCalculated will be called when not in play mode. However, the play mode check will not work if
	///    you call this from a separate thread, or a job. In that case you can set this to true to skip the check.</param>
	public static void StartPath (Path path, bool pushToFront = false, bool assumeInPlayMode = false) {
		// Copy to local variable to avoid multithreading issues
		var astar = active;

		if (System.Object.ReferenceEquals(astar, null)) {
			Debug.LogError("There is no AstarPath object in the scene or it has not been initialized yet");
			return;
		}

		if (path.PipelineState != PathState.Created) {
			throw new System.Exception("The path has an invalid state. Expected " + PathState.Created + " found " + path.PipelineState + "\n" +
				"Make sure you are not requesting the same path twice");
		}

		if (astar.pathProcessor.queue.isClosed) {
			path.FailWithError("No new paths are accepted");
			return;
		}

		if (astar.graphs == null || astar.graphs.Length == 0) {
			Debug.LogError("There are no graphs in the scene");
			path.FailWithError("There are no graphs in the scene");
			Debug.LogError(path.errorLog);
			return;
		}

		path.Claim(astar);

		// Will increment p.state to PathState.PathQueue
		((IPathInternals)path).AdvanceState(PathState.PathQueue);
		if (pushToFront) {
			astar.pathProcessor.queue.PushFront(path);
		} else {
			astar.pathProcessor.queue.Push(path);
		}

		// Outside of play mode, all path requests are synchronous.
		// However, inside a job we cannot check this, because Unity will throw an exception.
		// But luckily pretty much all jobs will run in game mode anyway. So we assume that if we are in a job, we are in game mode.
		if (!assumeInPlayMode && !Unity.Jobs.LowLevel.Unsafe.JobsUtility.IsExecutingJob && !Application.isPlaying) {
			BlockUntilCalculated(path);
		}
	}

	/// <summary>
	/// True if the point is on a walkable part of the navmesh, as seen from above.
	///
	/// A point is considered on the navmesh if it is above or below a walkable navmesh surface, at any distance,
	/// and if it is not above/below a closer unwalkable node.
	///
	/// Note: This means that, for example, in multi-story building a point will be considered on the navmesh if any walkable floor is below or above the point.
	/// If you want more complex behavior then you can use the GetNearest method together with the appropriate <see cref="NearestNodeConstraint.distanceMetric"/> settings for your use case.
	///
	/// This uses the graph's natural up direction to determine which way is up.
	/// Therefore, it will also work on rotated graphs, as well as graphs in 2D mode.
	///
	/// This method works for all graph types.
	/// However, for <see cref="PointGraph"/>s, this will never return true unless you pass in the exact coordinate of a node, since point nodes do not have a surface.
	///
	/// Note: For spherical navmeshes (or other weird shapes), this method will not work as expected, as there's no well defined "up" direction.
	///
	/// [Open online documentation to see images]
	///
	/// See: <see cref="NavGraph.IsPointOnNavmesh"/> to check if a point is on the navmesh of a specific graph.
	/// </summary>
	/// <param name="position">The point to check</param>
	public bool IsPointOnNavmesh (Vector3 position) {
		const float MaxHorizontalDistance = 0.01f;
		const float MaxCostSqr = MaxHorizontalDistance * MaxHorizontalDistance;
		// We use the None constraint, instead of Walkable, to avoid ignoring unwalkable nodes that are closer to the point.
		var constraint = NearestNodeConstraint.None;
		constraint.distanceMetric = DistanceMetric.ClosestAsSeenFromAbove();
		constraint.maxDistanceSqr = MaxCostSqr;
		var nearest = GetNearest(position, constraint);
		UnityEngine.Assertions.Assert.IsTrue(nearest.node == null || nearest.distanceCostSqr <= MaxCostSqr);
		return nearest.node != null && nearest.node.Walkable;
	}

	/// <summary>
	/// Returns the nearest node to a point.
	/// This method will search through all graphs and query them for the closest node (walkable or unwalkable) to this position.
	///
	/// Equivalent to GetNearest(position, NearestNodeConstraint.None).
	///
	/// <code>
	/// // Find the closest node to this GameObject's position
	/// GraphNode node = AstarPath.active.GetNearest(transform.position).node;
	///
	/// if (node.Walkable) {
	///     // Yay, the node is walkable, we can place a tower here or something
	/// }
	/// </code>
	///
	/// See: <see cref="NearestNodeConstraint"/>
	/// </summary>
	public NNInfo GetNearest (Vector3 position) {
		return GetNearest(position, NearestNodeConstraint.None);
	}

	/// <summary>
	/// Returns the nearest node to a point using the specified NNConstraint.
	///
	/// Searches through all graphs for their nearest nodes to the specified position and picks the closest one.
	/// The NNConstraint can be used to specify constraints on which nodes can be chosen, such as only picking walkable nodes.
	///
	/// <code>
	/// GraphNode node = AstarPath.active.GetNearest(transform.position, NearestNodeConstraint.Walkable).node;
	/// </code>
	///
	/// <code>
	/// var constraint = NearestNodeConstraint.None;
	///
	/// // Constrain the search to walkable nodes only
	/// constraint.walkable = NearestNodeConstraint.WalkabilityConstraint.Walkable;
	///
	/// // Constrain the search to only nodes with tag 3 or tag 5
	/// // The 'tags' field is a bitmask
	/// constraint.tags = (1 << 3) | (1 << 5);
	///
	/// var info = AstarPath.active.GetNearest(transform.position, constraint);
	/// var node = info.node;
	/// var closestPoint = info.position;
	/// </code>
	///
	/// See: <see cref="NNConstraint"/>
	/// Deprecated: This method is obsolete. Use the overload that takes a NearestNodeConstraint instead.
	/// </summary>
	/// <param name="position">The point to find nodes close to</param>
	/// <param name="constraint">The constraint which determines which graphs and nodes are acceptable to search on. May be null, in which case all nodes will be considered acceptable.</param>
	[System.Obsolete("NNConstraint is obsolete. Use the overload that takes a NearestNodeConstraint instead")]
	public NNInfo GetNearest (Vector3 position, NNConstraint constraint) {
		return GetNearest(position, constraint != null ? constraint.ToNearestNodeConstraint() : NearestNodeConstraint.None);
	}

	/// <summary>
	/// Returns the nearest node to a point using the specified NearestNodeConstraint.
	///
	/// Searches through all graphs for their nearest nodes to the specified position and picks the closest one.
	/// The <see cref="NearestNodeConstraint"/> can be used to specify constraints on which nodes can be chosen, such as only picking walkable nodes.
	///
	/// <code>
	/// GraphNode node = AstarPath.active.GetNearest(transform.position, NearestNodeConstraint.Walkable).node;
	/// </code>
	///
	/// <code>
	/// var constraint = NearestNodeConstraint.None;
	///
	/// // Constrain the search to walkable nodes only
	/// constraint.walkable = NearestNodeConstraint.WalkabilityConstraint.Walkable;
	///
	/// // Constrain the search to only nodes with tag 3 or tag 5
	/// // The 'tags' field is a bitmask
	/// constraint.tags = (1 << 3) | (1 << 5);
	///
	/// var info = AstarPath.active.GetNearest(transform.position, constraint);
	/// var node = info.node;
	/// var closestPoint = info.position;
	/// </code>
	///
	/// See: <see cref="NearestNodeConstraint"/>
	/// </summary>
	/// <param name="position">The point to find nodes close to</param>
	/// <param name="constraint">The constraint which determines which graphs and nodes are acceptable to search on.</param>
	[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
	public NNInfo GetNearest (Vector3 position, NearestNodeConstraint constraint) {
		return GetNearest(position, ref constraint);
	}

	internal NNInfo GetNearest (Vector3 position, ref NearestNodeConstraint constraint) {
		// Cache property lookups
		var graphs = this.graphs;
		constraint.maxDistanceSqr = constraint.maxDistanceSqrOrDefault(active);
		UnityEngine.Assertions.Assert.IsTrue(constraint.maxDistanceSqr >= 0);
		NNInfo nearestNode = NNInfo.Empty;

		if (graphs == null || graphs.Length == 0) return nearestNode;

		// Use a fast path in case there is only one graph.
		// This improves performance by about 10% when there is only one graph.
		if (graphs.Length == 1) {
			var graph = graphs[0];
			if (graph == null || !constraint.graphMask.Contains(graph)) {
				return nearestNode;
			}

			nearestNode = graph.GetNearest(position, ref constraint);
			UnityEngine.Assertions.Assert.IsTrue(nearestNode.node == null || nearestNode.distanceCostSqr <= constraint.maxDistanceSqr);
		} else {
			UnsafeSpan<(float, uint)> distances;
			unsafe {
				// The number of graphs is limited to GraphNode.MaxGraphIndex (256),
				// and typically there are only a few graphs, so allocating this on the stack is fine.
				var distancesPtr = stackalloc (float, int)[graphs.Length];
				distances = new UnsafeSpan<(float, uint)>(distancesPtr, graphs.Length);
			}

			// Iterate through all graphs and find a lower bound on the distance to the nearest node.
			// We then sort these distances and run the full get nearest search on the graphs in order of increasing distance.
			// This is an optimization to avoid running the full get nearest search on graphs which are far away.
			int numCandidateGraphs = 0;
			for (uint i = 0; i < (uint)graphs.Length; i++) {
				NavGraph graph = graphs[i];

				// Check if this graph should be searched
				if (graph == null || !constraint.graphMask.Contains(i)) {
					continue;
				}
				var lowerBound = graph.NearestNodeDistanceSqrLowerBound(position, ref constraint);
				if (lowerBound > constraint.maxDistanceSqr) continue;

				distances[numCandidateGraphs++] = (lowerBound, i);
			}
			distances = distances.Slice(0, numCandidateGraphs);
			distances.Sort();
			for (int i = 0; i < distances.Length; i++) {
				if (distances[i].Item1 > constraint.maxDistanceSqr) break;
				var graph = graphs[distances[i].Item2];
				NNInfo nnInfo = graph.GetNearest(position, ref constraint);
				if (nnInfo.distanceCostSqr < constraint.maxDistanceSqr) {
					constraint.maxDistanceSqr = nnInfo.distanceCostSqr;
					nearestNode = nnInfo;
				}
			}
		}
		return nearestNode;
	}

	/// <summary>
	/// Finds the closest navmesh border to the given position.
	///
	/// Returns: True if a border was found, false otherwise.
	///
	/// [Open online documentation to see images]
	/// [Open online documentation to see videos]
	///
	/// <code>
	/// var origin = target.position;
	/// using (Draw.WithLineWidth(2)) {
	///     if (AstarPath.active.GetNearestBorder(origin, NearestNodeConstraint.Walkable, out var hit)) {
	///         Draw.Arrow(origin, hit.point, Palette.Yellow);
	///         Draw.SphereOutline(origin, 0.1f, Palette.Yellow);
	///         Draw.SphereOutline(hit.point, 0.1f, Palette.Yellow);
	///         Draw.Ray(hit.tangentOrigin, hit.tangent, Palette.Purple);
	///     }
	/// }
	/// </code>
	///
	/// This method will first find the nearest node to the specified position using the specified constraint.
	/// You can think of this as the node the agent is currently standing on.
	/// Then, it will search for the closest border to that node, which is connected to it.
	///
	/// The constraint is only used to find the nearest node, it does not inform what borders are found afterwards.
	/// Notably, if an agent cannot traverse a given tag, it will still not consider a border between that tag and a node with a walkable tag as a valid border.
	///
	/// On a point graph, this method will always return false, since point nodes do not have any borders as such.
	///
	/// Note: hit.node will always be null, as it is currently not possible for the code to figure out which border belongs to what node. You can call <see cref="AstarPath.GetNearest"/> with hit.point if you need this information.
	///
	/// See: <see cref="GraphUtilities.GetContours"/>
	/// See: <see cref="GetNearest"/>
	/// See: <see cref="FollowerEntity.nearestNavmeshBorder"/>
	/// </summary>
	/// <param name="position">The position to search from.</param>
	/// <param name="constraint">Constraint for which nodes are acceptable for the agent to stand on.</param>
	/// <param name="hit">Information about the closest border, if any was found.</param>
	public bool GetNearestBorder (Vector3 position, NearestNodeConstraint constraint, out GraphHitInfo hit) {
		var nn = GetNearest(position, constraint);
		return GetNearestBorder(position, nn.node, out hit);
	}

	/// <summary>
	/// Finds the closest navmesh border to the given position.
	///
	/// [Open online documentation to see images]
	/// [Open online documentation to see videos]
	///
	/// <code>
	/// var origin = target.position;
	/// using (Draw.WithLineWidth(2)) {
	///     if (AstarPath.active.GetNearestBorder(origin, NearestNodeConstraint.Walkable, out var hit)) {
	///         Draw.Arrow(origin, hit.point, Palette.Yellow);
	///         Draw.SphereOutline(origin, 0.1f, Palette.Yellow);
	///         Draw.SphereOutline(hit.point, 0.1f, Palette.Yellow);
	///         Draw.Ray(hit.tangentOrigin, hit.tangent, Palette.Purple);
	///     }
	/// }
	/// </code>
	///
	/// Returns: True if a border was found, false otherwise.
	///
	/// Note: hit.node will always be null, as it is currently not possible for the code to figure out which border belongs to what node. You can call <see cref="AstarPath.GetNearest"/> with hit.point if you need this information.
	///
	/// See: <see cref="GraphUtilities.GetContours"/>
	/// See: <see cref="GetNearest"/>
	/// See: <see cref="FollowerEntity.nearestNavmeshBorder"/>
	/// </summary>
	/// <param name="position">The position to search from.</param>
	/// <param name="sourceNode">The node to search from. Only borders connected (directly or indirectly) to this node will be considered. This could for example be the node that an agent is currently standing on.</param>
	/// <param name="hit">Information about the closest border, if any was found.</param>
	public bool GetNearestBorder (Vector3 position, GraphNode sourceNode, out GraphHitInfo hit) {
		if (sourceNode is PointNode) {
			// Early out on point graphs, as it would otherwise try to search through the whole graph, looking for a border that won't exist.
			hit = new GraphHitInfo {
				origin = position,
				point = Vector3.positiveInfinity,
			};
			return false;
		}

		var scratchBuffer = new Unity.Collections.NativeList<int>(Unity.Collections.Allocator.Temp);
		scratchBuffer.Clear();
		var borderData = GetNavmeshBorderData(out var readLock);
		readLock.dependency.Complete();
		hit = borderData.GetClosestEdge(sourceNode != null && sourceNode.Walkable ? sourceNode.HierarchicalNodeIndex : -1, position, scratchBuffer);
		readLock.Unlock();
		return hit.point.x != float.PositiveInfinity;
	}

	/// <summary>
	/// True if there is an obstacle between start and end on the navmesh.
	///
	/// This is a simple api to check if there is an obstacle between two points.
	/// If you need more detailed information, you can use <see cref="GridGraph.Linecast"/> or <see cref="NavmeshBase.Linecast"/> (for navmesh/recast graphs).
	/// Those overloads can also return which nodes the line passed through, and allow you use custom node filtering.
	///
	/// <code>
	/// var start = transform.position;
	/// var end = start + Vector3.forward * 10;
	/// if (AstarPath.active.Linecast(start, end)) {
	///     Debug.DrawLine(start, end, Color.red);
	/// } else {
	///     Debug.DrawLine(start, end, Color.green);
	/// }
	/// </code>
	///
	/// Note: Only grid, recast and navmesh graphs support linecasts. The closest raycastable graph to the start point will be used for the linecast.
	/// Note: Linecasts cannot pass through off-mesh links.
	///
	/// [Open online documentation to see videos]
	///
	/// See: <see cref="NavmeshBase.Linecast"/>
	/// See: <see cref="GridGraph.Linecast"/>
	/// See: <see cref="IRaycastableGraph"/>
	/// See: linecasting (view in online documentation for working links), for more details about linecasting
	/// </summary>
	public bool Linecast (Vector3 start, Vector3 end) {
		var startGraph = ClosestRaycastableGraph(start);
		return startGraph == null || startGraph.Linecast(start, end);
	}

	/// <summary>
	/// True if there is an obstacle between start and end on the navmesh.
	///
	/// This is a simple api to check if there is an obstacle between two points.
	/// If you need more detailed information, you can use <see cref="GridGraph.Linecast"/> or <see cref="NavmeshBase.Linecast"/> (for navmesh/recast graphs).
	/// Those overloads can also return which nodes the line passed through, and allow you use custom node filtering.
	///
	/// <code>
	/// var start = transform.position;
	/// var end = start + Vector3.forward * 10;
	/// if (AstarPath.active.Linecast(start, end, out var hit)) {
	///     Debug.DrawLine(start, end, Color.red);
	///     Debug.DrawRay(hit.point, Vector3.up, Color.red);
	/// } else {
	///     Debug.DrawLine(start, end, Color.green);
	/// }
	/// </code>
	///
	/// Note: Only grid, recast and navmesh graphs support linecasts. The closest raycastable graph to the start point will be used for the linecast.
	/// Note: Linecasts cannot pass through off-mesh links.
	///
	/// [Open online documentation to see videos]
	///
	/// See: <see cref="NavmeshBase.Linecast"/>
	/// See: <see cref="GridGraph.Linecast"/>
	/// See: <see cref="IRaycastableGraph"/>
	/// See: linecasting (view in online documentation for working links), for more details about linecasting
	/// </summary>
	public bool Linecast (Vector3 start, Vector3 end, out GraphHitInfo hit) {
		var startGraph = ClosestRaycastableGraph(start);
		if (startGraph == null) {
			hit = new GraphHitInfo {
				origin = start,
				point = start,
			};
			return true;
		}
		return startGraph.Linecast(start, end, out hit);
	}

	IRaycastableGraph ClosestRaycastableGraph (Vector3 point) {
		if (data.graphs == null) return null;

		// Most games have just a single raycastable graph.
		IRaycastableGraph graph = null;
		int found = 0;
		for (int i = 0; i < data.graphs.Length; i++) {
			if (data.graphs[i] is IRaycastableGraph g) {
				graph = g;
				found++;
			}
		}

		// If there's more than one graph that can perform linecasts,
		// then find the nearest graph to the point.
		if (found > 1) {
			var startNode = GetNearest(point);
			graph = startNode.node?.Graph as IRaycastableGraph;
		}
		return graph;
	}

	/// <summary>
	/// Returns the nearest node to a ray (slow).
	/// Warning: This function is brute-force and very slow, use with caution
	/// </summary>
	public GraphNode GetNearest (Ray ray) {
		if (graphs == null) return null;

		float minDist = Mathf.Infinity;
		GraphNode nearestNode = null;

		Vector3 lineDirection = ray.direction;
		Vector3 lineOrigin = ray.origin;

		for (int i = 0; i < graphs.Length; i++) {
			NavGraph graph = graphs[i];

			graph.GetNodes(node => {
				Vector3 pos = (Vector3)node.position;
				Vector3 p = lineOrigin+(Vector3.Dot(pos-lineOrigin, lineDirection)*lineDirection);

				float tmp = Mathf.Abs(p.x-pos.x);
				tmp *= tmp;
				if (tmp > minDist) return;

				tmp = Mathf.Abs(p.z-pos.z);
				tmp *= tmp;
				if (tmp > minDist) return;

				float dist = (p-pos).sqrMagnitude;

				if (dist < minDist) {
					minDist = dist;
					nearestNode = node;
				}
			});
		}

		return nearestNode;
	}

	/// <summary>
	/// Captures a snapshot of a part of the graphs, to allow restoring it later.
	///
	/// This is useful if you want to do a graph update, but you want to be able to restore the graph to the previous state.
	///
	/// The snapshot will capture enough information to restore the graphs, assuming the world only changed within the given bounding box.
	/// This means the captured region may be larger than the bounding box.
	///
	/// <b>Limitations:</b>
	/// - Currently, the <see cref="GridGraph"/> and <see cref="LayerGridGraph"/> supports snapshots. Other graph types do not support it.
	/// - The graph must not change its dimensions or other core parameters between the time the snapshot is taken and the time it is restored.
	/// - Custom node connections may not be preserved. Unless they are added as off-mesh links using e.g. a <see cref="NodeLink2"/> component.
	/// - The snapshot must not be captured during a work item, graph update or when the graphs are being scanned, as the graphs may not be in a consistent state during those times.
	///
	/// See: <see cref="GraphUpdateUtilities.UpdateGraphsNoBlock"/>, which uses this method internally.
	/// See: <see cref="NavGraph.Snapshot"/>
	///
	/// Note: You must dispose the returned snapshot when you are done with it, to avoid leaking memory.
	/// </summary>
	public GraphSnapshot Snapshot (Bounds bounds, GraphMask graphMask) {
		Profiler.BeginSample("Capturing Graph Snapshot");
		var inner = new List<IGraphSnapshot>();
		for (uint i = 0; i < (uint)graphs.Length; i++) {
			if (graphs[i] != null && graphMask.Contains(i)) {
				var s = graphs[i].Snapshot(bounds);
				if (s != null) inner.Add(s);
			}
		}
		Profiler.EndSample();
		return new GraphSnapshot(inner);
	}

	/// <summary>
	/// Allows you to access read-only graph data in jobs safely.
	///
	/// You can for example use AstarPath.active.GetNearest(...) in a job.
	///
	/// Using <see cref="AstarPath.StartPath"/> is always safe to use in jobs even without calling this method.
	///
	/// When a graph update, work item, or graph scan would start, it will first block on the given dependency
	/// to ensure no race conditions occur.
	///
	/// If you do not call this method, then a graph update might start in the middle of your job, causing race conditions
	/// and all manner of other hard-to-diagnose bugs.
	///
	/// <code>
	/// var readLock = AstarPath.active.LockGraphDataForReading();
	/// var handle = new MyJob {
	///     // ...
	/// }.Schedule(readLock.dependency);
	/// readLock.UnlockAfter(handle);
	/// </code>
	///
	/// See: <see cref="LockGraphDataForWriting"/>
	/// See: <see cref="graphDataLock"/>
	/// </summary>
	public RWLock.ReadLockAsync LockGraphDataForReading() => graphDataLock.Read();

	/// <summary>
	/// Aquires an exclusive lock on the graph data asynchronously.
	/// This is used when graphs want to modify graph data.
	///
	/// This is a low-level primitive, usually you do not need to use this method.
	///
	/// <code>
	/// var readLock = AstarPath.active.LockGraphDataForReading();
	/// var handle = new MyJob {
	///     // ...
	/// }.Schedule(readLock.dependency);
	/// readLock.UnlockAfter(handle);
	/// </code>
	///
	/// See: <see cref="LockGraphDataForReading"/>
	/// See: <see cref="graphDataLock"/>
	/// </summary>
	public RWLock.WriteLockAsync LockGraphDataForWriting() => graphDataLock.Write();

	/// <summary>
	/// Aquires an exclusive lock on the graph data.
	/// This is used when graphs want to modify graph data.
	///
	/// This is a low-level primitive, usually you do not need to use this method.
	///
	/// <code>
	/// var readLock = AstarPath.active.LockGraphDataForReading();
	/// var handle = new MyJob {
	///     // ...
	/// }.Schedule(readLock.dependency);
	/// readLock.UnlockAfter(handle);
	/// </code>
	///
	/// See: <see cref="LockGraphDataForReading"/>
	/// See: <see cref="graphDataLock"/>
	/// See: <see cref="AddWorkItem"/>
	/// </summary>
	public RWLock.LockSync LockGraphDataForWritingSync() => graphDataLock.WriteSync();

	/// <summary>
	/// Obstacle data for navmesh edges.
	///
	/// This can be used to get information about the edge/borders of the navmesh.
	/// It can also be queried in burst jobs. Just make sure you release the read lock after you are done with it.
	///
	/// Note: This is not a method that you are likely to need to use.
	/// It is used internally for things like local avoidance.
	/// </summary>
	public NavmeshEdges.NavmeshBorderData GetNavmeshBorderData(out RWLock.CombinedReadLockAsync readLock) => hierarchicalGraph.navmeshEdges.GetNavmeshEdgeData(out readLock);
}
