From 5c0ba9dba1adadbc4984e5721c71933312370544 Mon Sep 17 00:00:00 2001 From: wrenge Date: Wed, 13 Nov 2024 13:14:32 +0300 Subject: [PATCH 1/5] Reuse grid instead of creating new --- src/DotRecast.Detour.Crowd/DtCrowd.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/DotRecast.Detour.Crowd/DtCrowd.cs b/src/DotRecast.Detour.Crowd/DtCrowd.cs index d90f4ec..c005424 100644 --- a/src/DotRecast.Detour.Crowd/DtCrowd.cs +++ b/src/DotRecast.Detour.Crowd/DtCrowd.cs @@ -129,7 +129,7 @@ namespace DotRecast.Detour.Crowd private readonly DtObstacleAvoidanceParams[] _obstacleQueryParams; private readonly DtObstacleAvoidanceQuery _obstacleQuery; - private DtProximityGrid _grid; + private readonly DtProximityGrid _grid; private int _maxPathResult; private readonly RcVec3f _agentPlacementHalfExtents; @@ -174,6 +174,7 @@ namespace DotRecast.Detour.Crowd _agentIdx = new RcAtomicInteger(0); _agents = new Dictionary(); _activeAgents = new List(); + _grid = new DtProximityGrid(_config.maxAgentRadius * 3); // The navQuery is mostly used for local searches, no need for large node pool. SetNavMesh(nav); @@ -864,7 +865,7 @@ namespace DotRecast.Detour.Crowd { using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildProximityGrid); - _grid = new DtProximityGrid(_config.maxAgentRadius * 3); + _grid.Clear(); for (var i = 0; i < agents.Count; i++) { From b2a217d4a3248d8b76b86f9021b49280901daf09 Mon Sep 17 00:00:00 2001 From: wrenge Date: Wed, 13 Nov 2024 13:21:41 +0300 Subject: [PATCH 2/5] Object pool --- src/DotRecast.Core/Buffers/RcObjectPool.cs | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/DotRecast.Core/Buffers/RcObjectPool.cs diff --git a/src/DotRecast.Core/Buffers/RcObjectPool.cs b/src/DotRecast.Core/Buffers/RcObjectPool.cs new file mode 100644 index 0000000..c37cf60 --- /dev/null +++ b/src/DotRecast.Core/Buffers/RcObjectPool.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace DotRecast.Core.Buffers +{ + // This implementation is thread unsafe + public class RcObjectPool where T : class + { + private readonly Queue _items = new Queue(); + private readonly Func _createFunc; + + public RcObjectPool(Func createFunc) + { + _createFunc = createFunc; + } + + public T Get() + { + if (_items.TryDequeue(out var result)) + return result; + + return _createFunc(); + } + + public void Return(T obj) + { + _items.Enqueue(obj); + } + } +} \ No newline at end of file From 592ecebe1e99f6770113429bbc4ddfa6f571db40 Mon Sep 17 00:00:00 2001 From: wrenge Date: Wed, 13 Nov 2024 13:25:13 +0300 Subject: [PATCH 3/5] Proximity grid list reuse --- src/DotRecast.Detour.Crowd/DtProximityGrid.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/DotRecast.Detour.Crowd/DtProximityGrid.cs b/src/DotRecast.Detour.Crowd/DtProximityGrid.cs index 3cd182c..e394982 100644 --- a/src/DotRecast.Detour.Crowd/DtProximityGrid.cs +++ b/src/DotRecast.Detour.Crowd/DtProximityGrid.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using DotRecast.Core.Buffers; namespace DotRecast.Detour.Crowd { @@ -30,12 +31,14 @@ namespace DotRecast.Detour.Crowd private readonly float _cellSize; private readonly float _invCellSize; private readonly Dictionary> _items; + private readonly RcObjectPool> _listPool; public DtProximityGrid(float cellSize) { _cellSize = cellSize; _invCellSize = 1.0f / cellSize; _items = new Dictionary>(); + _listPool = new RcObjectPool>(() => new List()); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -57,6 +60,8 @@ namespace DotRecast.Detour.Crowd public void Clear() { + foreach (var pair in _items) + _listPool.Return(pair.Value); _items.Clear(); } @@ -74,7 +79,7 @@ namespace DotRecast.Detour.Crowd long key = CombineKey(x, y); if (!_items.TryGetValue(key, out var ids)) { - ids = new List(); + ids = _listPool.Get(); _items.Add(key, ids); } From 815a83e3cb87841a20dace847ae4927ef6456735 Mon Sep 17 00:00:00 2001 From: wrenge Date: Wed, 13 Nov 2024 13:42:43 +0300 Subject: [PATCH 4/5] Use precached queue instead of linked list --- src/DotRecast.Detour/DtNavMeshQuery.cs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/DotRecast.Detour/DtNavMeshQuery.cs b/src/DotRecast.Detour/DtNavMeshQuery.cs index ee0e699..7ff5d4b 100644 --- a/src/DotRecast.Detour/DtNavMeshQuery.cs +++ b/src/DotRecast.Detour/DtNavMeshQuery.cs @@ -1837,6 +1837,7 @@ namespace DotRecast.Detour return DtStatus.DT_SUCCESS | (straightPathCount >= maxStraightPath ? DtStatus.DT_BUFFER_TOO_SMALL : DtStatus.DT_STATUS_NOTHING); } + private readonly Queue MoveAlongSurface_queue = new Queue(); /// @par /// /// This method is optimized for small delta movement and a small number of @@ -1892,8 +1893,8 @@ namespace DotRecast.Detour startNode.total = 0; startNode.id = startRef; startNode.flags = DtNodeFlags.DT_NODE_CLOSED; - LinkedList stack = new LinkedList(); - stack.AddLast(startNode); + MoveAlongSurface_queue.Clear(); + MoveAlongSurface_queue.Enqueue(startNode); RcVec3f bestPos = new RcVec3f(); float bestDist = float.MaxValue; @@ -1909,11 +1910,10 @@ namespace DotRecast.Detour const int MAX_NEIS = 8; Span neis = stackalloc long[MAX_NEIS]; - while (0 < stack.Count) + while (0 < MoveAlongSurface_queue.Count) { // Pop front. - DtNode curNode = stack.First?.Value; - stack.RemoveFirst(); + DtNode curNode = MoveAlongSurface_queue.Dequeue(); // Get poly and tile. // The API input has been checked already, skip checking internal data. @@ -2012,7 +2012,7 @@ namespace DotRecast.Detour // Mark as the node as visited and push to queue. neighbourNode.pidx = m_tinyNodePool.GetNodeIdx(curNode); neighbourNode.flags |= DtNodeFlags.DT_NODE_CLOSED; - stack.AddLast(neighbourNode); + MoveAlongSurface_queue.Enqueue(neighbourNode); } } } @@ -2903,6 +2903,7 @@ namespace DotRecast.Detour return DtStatus.DT_SUCCESS; } + private readonly Queue FindLocalNeighbourhood_queue = new Queue(); /// @par /// /// This method is optimized for a small search radius and small number of result @@ -2954,8 +2955,8 @@ namespace DotRecast.Detour startNode.pidx = 0; startNode.id = startRef; startNode.flags = DtNodeFlags.DT_NODE_CLOSED; - LinkedList stack = new LinkedList(); - stack.AddLast(startNode); + FindLocalNeighbourhood_queue.Clear(); + FindLocalNeighbourhood_queue.Enqueue(startNode); resultRef.Add(startNode.id); resultParent.Add(0L); @@ -2965,11 +2966,11 @@ namespace DotRecast.Detour Span pa = stackalloc float[m_nav.GetMaxVertsPerPoly() * 3]; Span pb = stackalloc float[m_nav.GetMaxVertsPerPoly() * 3]; - while (0 < stack.Count) + while (0 < FindLocalNeighbourhood_queue.Count) { // Pop front. - DtNode curNode = stack.First?.Value; - stack.RemoveFirst(); + + DtNode curNode = FindLocalNeighbourhood_queue.Dequeue(); // Get poly and tile. // The API input has been checked already, skip checking internal data. @@ -3082,7 +3083,7 @@ namespace DotRecast.Detour resultRef.Add(neighbourRef); resultParent.Add(curRef); - stack.AddLast(neighbourNode); + FindLocalNeighbourhood_queue.Enqueue(neighbourNode); } } From 1fa0320845409380171b62570e5fd6e83045b26d Mon Sep 17 00:00:00 2001 From: wrenge Date: Wed, 13 Nov 2024 13:55:02 +0300 Subject: [PATCH 5/5] precache collections --- src/DotRecast.Detour.Crowd/DtCrowd.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/DotRecast.Detour.Crowd/DtCrowd.cs b/src/DotRecast.Detour.Crowd/DtCrowd.cs index c005424..d148a86 100644 --- a/src/DotRecast.Detour.Crowd/DtCrowd.cs +++ b/src/DotRecast.Detour.Crowd/DtCrowd.cs @@ -565,14 +565,18 @@ namespace DotRecast.Detour.Crowd } } + private readonly RcSortedQueue UpdateMoveRequest_queue = new RcSortedQueue((a1, a2) => a2.targetReplanTime.CompareTo(a1.targetReplanTime)); + private readonly List UpdateMoveRequest_reqPath = new List(); private void UpdateMoveRequest(IList agents, float dt) { using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateMoveRequest); - RcSortedQueue queue = new RcSortedQueue((a1, a2) => a2.targetReplanTime.CompareTo(a1.targetReplanTime)); + RcSortedQueue queue = UpdateMoveRequest_queue; + queue.Clear(); // Fire off new requests. - List reqPath = new List(); + List reqPath = UpdateMoveRequest_reqPath; + reqPath.Clear(); for (var i = 0; i < agents.Count; i++) { var ag = agents[i];