NavmeshUpdates.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using Pathfinding.Util;
  4. using Pathfinding.Serialization;
  5. using UnityEngine.Profiling;
  6. namespace Pathfinding {
  7. /// <summary>
  8. /// Helper for navmesh cut objects.
  9. /// Responsible for keeping track of which navmesh cuts have moved and coordinating graph updates to account for those changes.
  10. ///
  11. /// See: navmeshcutting (view in online documentation for working links)
  12. /// See: <see cref="AstarPath.navmeshUpdates"/>
  13. /// See: <see cref="Pathfinding.NavmeshBase.enableNavmeshCutting"/>
  14. /// </summary>
  15. [System.Serializable]
  16. public class NavmeshUpdates {
  17. /// <summary>
  18. /// How often to check if an update needs to be done (real seconds between checks).
  19. /// For worlds with a very large number of NavmeshCut objects, it might be bad for performance to do this check every frame.
  20. /// If you think this is a performance penalty, increase this number to check less often.
  21. ///
  22. /// For almost all games, this can be kept at 0.
  23. ///
  24. /// If negative, no updates will be done. They must be manually triggered using <see cref="ForceUpdate"/>.
  25. ///
  26. /// <code>
  27. /// // Check every frame (the default)
  28. /// AstarPath.active.navmeshUpdates.updateInterval = 0;
  29. ///
  30. /// // Check every 0.1 seconds
  31. /// AstarPath.active.navmeshUpdates.updateInterval = 0.1f;
  32. ///
  33. /// // Never check for changes
  34. /// AstarPath.active.navmeshUpdates.updateInterval = -1;
  35. /// // You will have to schedule updates manually using
  36. /// AstarPath.active.navmeshUpdates.ForceUpdate();
  37. /// </code>
  38. ///
  39. /// You can also find this in the AstarPath inspector under Settings.
  40. /// [Open online documentation to see images]
  41. /// </summary>
  42. public float updateInterval;
  43. /// <summary>Last time navmesh cuts were applied</summary>
  44. float lastUpdateTime = float.NegativeInfinity;
  45. /// <summary>Stores navmesh cutting related data for a single graph</summary>
  46. internal class NavmeshUpdateSettings {
  47. public TileHandler handler;
  48. public readonly List<IntRect> forcedReloadRects = new List<IntRect>();
  49. readonly NavmeshBase graph;
  50. public NavmeshUpdateSettings(NavmeshBase graph) {
  51. this.graph = graph;
  52. }
  53. public void Refresh (bool forceCreate = false) {
  54. if (!graph.enableNavmeshCutting) {
  55. if (handler != null) {
  56. handler.cuts.Clear();
  57. handler.ReloadInBounds(new IntRect(int.MinValue, int.MinValue, int.MaxValue, int.MaxValue));
  58. // Make sure the updates are applied immediately.
  59. // This is important because if navmesh cutting is enabled immediately after this
  60. // then it will call CreateTileTypesFromGraph, and we need to ensure that it is not
  61. // calling that when the graph still has cuts in it as they will then be baked in.
  62. AstarPath.active.FlushGraphUpdates();
  63. AstarPath.active.FlushWorkItems();
  64. forcedReloadRects.ClearFast();
  65. handler = null;
  66. }
  67. } else if ((handler == null && (forceCreate || NavmeshClipper.allEnabled.Count > 0)) || (handler != null && !handler.isValid)) {
  68. // Note: Only create a handler if there are any navmesh cuts in the scene.
  69. // We don't want to waste a lot of memory if navmesh cutting isn't actually used for anything
  70. // and even more important: we don't want to do any sporadic updates to the graph which
  71. // may clear the graph's tags or change it's structure (e.g from the delaunay optimization in the TileHandler).
  72. // The tile handler is invalid (or doesn't exist), so re-create it
  73. handler = new TileHandler(graph);
  74. for (int i = 0; i < NavmeshClipper.allEnabled.Count; i++) AddClipper(NavmeshClipper.allEnabled[i]);
  75. handler.CreateTileTypesFromGraph();
  76. // Reload in huge bounds. This will cause all tiles to be updated.
  77. forcedReloadRects.Add(new IntRect(int.MinValue, int.MinValue, int.MaxValue, int.MaxValue));
  78. }
  79. }
  80. /// <summary>Called when some tiles in a recast graph have been completely recalculated (e.g from scanning the graph)</summary>
  81. public void OnRecalculatedTiles (NavmeshTile[] tiles) {
  82. Refresh();
  83. if (handler != null) handler.OnRecalculatedTiles(tiles);
  84. }
  85. /// <summary>Called when a NavmeshCut or NavmeshAdd is enabled</summary>
  86. public void AddClipper (NavmeshClipper obj) {
  87. if (!obj.graphMask.Contains((int)graph.graphIndex)) return;
  88. // Without the forceCreate parameter set to true then no handler will be created
  89. // because there are no clippers in the scene yet. However one is being added right now.
  90. Refresh(true);
  91. if (handler == null) return;
  92. var graphSpaceBounds = obj.GetBounds(handler.graph.transform);
  93. var touchingTiles = handler.graph.GetTouchingTilesInGraphSpace(graphSpaceBounds);
  94. handler.cuts.Add(obj, touchingTiles);
  95. }
  96. /// <summary>Called when a NavmeshCut or NavmeshAdd is disabled</summary>
  97. public void RemoveClipper (NavmeshClipper obj) {
  98. Refresh();
  99. if (handler == null) return;
  100. var root = handler.cuts.GetRoot(obj);
  101. if (root != null) {
  102. forcedReloadRects.Add(root.previousBounds);
  103. handler.cuts.Remove(obj);
  104. }
  105. }
  106. }
  107. internal void OnEnable () {
  108. NavmeshClipper.AddEnableCallback(HandleOnEnableCallback, HandleOnDisableCallback);
  109. }
  110. internal void OnDisable () {
  111. NavmeshClipper.RemoveEnableCallback(HandleOnEnableCallback, HandleOnDisableCallback);
  112. }
  113. /// <summary>Discards all pending updates caused by moved or modified navmesh cuts</summary>
  114. public void DiscardPending () {
  115. for (int i = 0; i < NavmeshClipper.allEnabled.Count; i++) {
  116. NavmeshClipper.allEnabled[i].NotifyUpdated();
  117. }
  118. var graphs = AstarPath.active.graphs;
  119. for (int i = 0; i < graphs.Length; i++) {
  120. var navmeshBase = graphs[i] as NavmeshBase;
  121. if (navmeshBase != null) navmeshBase.navmeshUpdateData.forcedReloadRects.Clear();
  122. }
  123. }
  124. /// <summary>Called when a NavmeshCut or NavmeshAdd is enabled</summary>
  125. void HandleOnEnableCallback (NavmeshClipper obj) {
  126. var graphs = AstarPath.active.graphs;
  127. for (int i = 0; i < graphs.Length; i++) {
  128. var navmeshBase = graphs[i] as NavmeshBase;
  129. if (navmeshBase != null) navmeshBase.navmeshUpdateData.AddClipper(obj);
  130. }
  131. obj.ForceUpdate();
  132. }
  133. /// <summary>Called when a NavmeshCut or NavmeshAdd is disabled</summary>
  134. void HandleOnDisableCallback (NavmeshClipper obj) {
  135. var graphs = AstarPath.active.graphs;
  136. for (int i = 0; i < graphs.Length; i++) {
  137. var navmeshBase = graphs[i] as NavmeshBase;
  138. if (navmeshBase != null) navmeshBase.navmeshUpdateData.RemoveClipper(obj);
  139. }
  140. lastUpdateTime = float.NegativeInfinity;
  141. }
  142. /// <summary>Update is called once per frame</summary>
  143. internal void Update () {
  144. if (AstarPath.active.isScanning) return;
  145. Profiler.BeginSample("Navmesh cutting");
  146. bool anyInvalidHandlers = false;
  147. var graphs = AstarPath.active.graphs;
  148. for (int i = 0; i < graphs.Length; i++) {
  149. var navmeshBase = graphs[i] as NavmeshBase;
  150. if (navmeshBase != null) {
  151. navmeshBase.navmeshUpdateData.Refresh();
  152. anyInvalidHandlers = navmeshBase.navmeshUpdateData.forcedReloadRects.Count > 0;
  153. }
  154. }
  155. if ((updateInterval >= 0 && Time.realtimeSinceStartup - lastUpdateTime > updateInterval) || anyInvalidHandlers) {
  156. ForceUpdate();
  157. }
  158. Profiler.EndSample();
  159. }
  160. /// <summary>
  161. /// Checks all NavmeshCut instances and updates graphs if needed.
  162. /// Note: This schedules updates for all necessary tiles to happen as soon as possible.
  163. /// The pathfinding threads will continue to calculate the paths that they were calculating when this function
  164. /// was called and then they will be paused and the graph updates will be carried out (this may be several frames into the
  165. /// future and the graph updates themselves may take several frames to complete).
  166. /// If you want to force all navmesh cutting to be completed in a single frame call this method
  167. /// and immediately after call AstarPath.FlushWorkItems.
  168. ///
  169. /// <code>
  170. /// // Schedule pending updates to be done as soon as the pathfinding threads
  171. /// // are done with what they are currently doing.
  172. /// AstarPath.active.navmeshUpdates.ForceUpdate();
  173. /// // Block until the updates have finished
  174. /// AstarPath.active.FlushGraphUpdates();
  175. /// </code>
  176. /// </summary>
  177. public void ForceUpdate () {
  178. lastUpdateTime = Time.realtimeSinceStartup;
  179. List<NavmeshClipper> hasBeenUpdated = null;
  180. var graphs = AstarPath.active.graphs;
  181. for (int graphIndex = 0; graphIndex < graphs.Length; graphIndex++) {
  182. var navmeshBase = graphs[graphIndex] as NavmeshBase;
  183. if (navmeshBase == null) continue;
  184. // Done in Update as well, but users may call ForceUpdate directly
  185. navmeshBase.navmeshUpdateData.Refresh();
  186. var handler = navmeshBase.navmeshUpdateData.handler;
  187. if (handler == null) continue;
  188. var forcedReloadRects = navmeshBase.navmeshUpdateData.forcedReloadRects;
  189. // Get all navmesh cuts in the scene
  190. var allCuts = handler.cuts.AllItems;
  191. if (forcedReloadRects.Count == 0) {
  192. bool any = false;
  193. // Check if any navmesh cuts need updating
  194. for (var cut = allCuts; cut != null; cut = cut.next) {
  195. if (cut.obj.RequiresUpdate()) {
  196. any = true;
  197. break;
  198. }
  199. }
  200. // Nothing needs to be done for now
  201. if (!any) continue;
  202. }
  203. // Start batching tile updates which is good for performance
  204. // if we are updating a lot of them
  205. handler.StartBatchLoad();
  206. for (int i = 0; i < forcedReloadRects.Count; i++) {
  207. handler.ReloadInBounds(forcedReloadRects[i]);
  208. }
  209. forcedReloadRects.ClearFast();
  210. if (hasBeenUpdated == null) hasBeenUpdated = ListPool<NavmeshClipper>.Claim();
  211. // Reload all bounds touching the previous bounds and current bounds
  212. // of navmesh cuts that have moved or changed in some other way
  213. for (var cut = allCuts; cut != null; cut = cut.next) {
  214. if (cut.obj.RequiresUpdate()) {
  215. // Make sure the tile where it was is updated
  216. handler.ReloadInBounds(cut.previousBounds);
  217. var newGraphSpaceBounds = cut.obj.GetBounds(handler.graph.transform);
  218. var newTouchingTiles = handler.graph.GetTouchingTilesInGraphSpace(newGraphSpaceBounds);
  219. handler.cuts.Move(cut.obj, newTouchingTiles);
  220. handler.ReloadInBounds(newTouchingTiles);
  221. hasBeenUpdated.Add(cut.obj);
  222. }
  223. }
  224. handler.EndBatchLoad();
  225. }
  226. if (hasBeenUpdated != null) {
  227. // Notify navmesh cuts that they have been updated
  228. // This will cause RequiresUpdate to return false
  229. // until it is changed again.
  230. // Note: This is not as efficient as it could be when multiple graphs are used
  231. // because every navmesh cut will be added to the list once for every graph.
  232. for (int i = 0; i < hasBeenUpdated.Count; i++) {
  233. hasBeenUpdated[i].NotifyUpdated();
  234. }
  235. ListPool<NavmeshClipper>.Release(ref hasBeenUpdated);
  236. }
  237. }
  238. }
  239. }