From 99224251dc0cf4f83b60ef8f5cbd2d3554b64c18 Mon Sep 17 00:00:00 2001 From: ikpil Date: Thu, 23 May 2024 02:16:03 +0900 Subject: [PATCH] Changed `List` to `Span` for enhanced memory efficiency --- CHANGELOG.md | 1 + src/DotRecast.Detour.Crowd/DtCrowd.cs | 10 +-- src/DotRecast.Detour.Crowd/DtCrowdAgent.cs | 25 +++--- src/DotRecast.Detour.Crowd/DtPathCorridor.cs | 50 ++++++------ src/DotRecast.Detour/DtNavMeshQuery.cs | 68 +++++++++-------- src/DotRecast.Detour/DtNavMeshQueryMock.cs | 34 +++++++++ src/DotRecast.Detour/DtPathUtils.cs | 10 +-- .../Tools/CrowdSampleTool.cs | 8 +- .../Tools/TestNavmeshSampleTool.cs | 21 ++--- .../Tools/RcTestNavMeshTool.cs | 25 +++--- .../DtPathCorridorTest.cs | 76 ++++++------------- test/DotRecast.Detour.Test/FindPathTest.cs | 8 +- .../TileCacheFindPathTest.cs | 10 +-- 13 files changed, 179 insertions(+), 167 deletions(-) create mode 100644 src/DotRecast.Detour/DtNavMeshQueryMock.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5288a1b..3f8f729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Changed `DtTileCacheLayerHeaderReader` to a static class - Changed `Dictionary>` to `DtMeshTile[]` to optimize memory usage - Changed `MAX_STEER_POINTS` from class constant to local. +- Changed `List` to `Span` for enhanced memory efficiency ### Removed - Nothing diff --git a/src/DotRecast.Detour.Crowd/DtCrowd.cs b/src/DotRecast.Detour.Crowd/DtCrowd.cs index 2bbb1c1..985ac55 100644 --- a/src/DotRecast.Detour.Crowd/DtCrowd.cs +++ b/src/DotRecast.Detour.Crowd/DtCrowd.cs @@ -944,13 +944,13 @@ namespace DotRecast.Detour.Crowd } // Find corners for steering - ag.corridor.FindCorners(ref ag.corners, DtCrowdConst.DT_CROWDAGENT_MAX_CORNERS, _navQuery, _filters[ag.option.queryFilterType]); + ag.ncorners = ag.corridor.FindCorners(ag.corners, DtCrowdConst.DT_CROWDAGENT_MAX_CORNERS, _navQuery, _filters[ag.option.queryFilterType]); // Check to see if the corner after the next corner is directly visible, // and short cut to there. - if ((ag.option.updateFlags & DtCrowdAgentUpdateFlags.DT_CROWD_OPTIMIZE_VIS) != 0 && ag.corners.Count > 0) + if ((ag.option.updateFlags & DtCrowdAgentUpdateFlags.DT_CROWD_OPTIMIZE_VIS) != 0 && ag.ncorners > 0) { - RcVec3f target = ag.corners[Math.Min(1, ag.corners.Count - 1)].pos; + RcVec3f target = ag.corners[Math.Min(1, ag.ncorners - 1)].pos; ag.corridor.OptimizePathVisibility(target, ag.option.pathOptimizationRange, _navQuery, _filters[ag.option.queryFilterType]); @@ -1000,7 +1000,7 @@ namespace DotRecast.Detour.Crowd // Adjust the path over the off-mesh connection. long[] refs = new long[2]; - if (ag.corridor.MoveOverOffmeshConnection(ag.corners[ag.corners.Count - 1].refs, refs, ref anim.startPos, + if (ag.corridor.MoveOverOffmeshConnection(ag.corners[ag.ncorners - 1].refs, refs, ref anim.startPos, ref anim.endPos, _navQuery)) { anim.initPos = ag.npos; @@ -1010,7 +1010,7 @@ namespace DotRecast.Detour.Crowd anim.tmax = (RcVecUtils.Dist2D(anim.startPos, anim.endPos) / ag.option.maxSpeed) * 0.5f; ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_OFFMESH; - ag.corners.Clear(); + ag.ncorners = 0; ag.neis.Clear(); continue; } diff --git a/src/DotRecast.Detour.Crowd/DtCrowdAgent.cs b/src/DotRecast.Detour.Crowd/DtCrowdAgent.cs index 0e2e3f8..ab63346 100644 --- a/src/DotRecast.Detour.Crowd/DtCrowdAgent.cs +++ b/src/DotRecast.Detour.Crowd/DtCrowdAgent.cs @@ -61,8 +61,11 @@ namespace DotRecast.Detour.Crowd public DtCrowdAgentParams option; /// The local path corridor corners for the agent. - public List corners = new List(); - + public DtStraightPath[] corners = new DtStraightPath[DtCrowdConst.DT_CROWDAGENT_MAX_CORNERS]; + + /// The number of corners. + public int ncorners; + public DtMoveRequestState targetState; // < State of the movement request. public long targetRef; // < Target polyref of the movement request. public RcVec3f targetPos = new RcVec3f(); // < Target position of the movement request (or velocity in case of DT_CROWDAGENT_TARGET_VELOCITY). @@ -100,16 +103,16 @@ namespace DotRecast.Detour.Crowd public bool OverOffmeshConnection(float radius) { - if (0 == corners.Count) + if (0 == ncorners) return false; - bool offMeshConnection = ((corners[corners.Count - 1].flags + bool offMeshConnection = ((corners[ncorners - 1].flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) ? true : false; if (offMeshConnection) { - float distSq = RcVecUtils.Dist2DSqr(npos, corners[corners.Count - 1].pos); + float distSq = RcVecUtils.Dist2DSqr(npos, corners[ncorners - 1].pos); if (distSq < radius * radius) return true; } @@ -119,12 +122,12 @@ namespace DotRecast.Detour.Crowd public float GetDistanceToGoal(float range) { - if (0 == corners.Count) + if (0 == ncorners) return range; - bool endOfPath = ((corners[corners.Count - 1].flags & DtStraightPathFlags.DT_STRAIGHTPATH_END) != 0) ? true : false; + bool endOfPath = ((corners[ncorners - 1].flags & DtStraightPathFlags.DT_STRAIGHTPATH_END) != 0) ? true : false; if (endOfPath) - return Math.Min(RcVecUtils.Dist2D(npos, corners[corners.Count - 1].pos), range); + return Math.Min(RcVecUtils.Dist2D(npos, corners[ncorners - 1].pos), range); return range; } @@ -132,10 +135,10 @@ namespace DotRecast.Detour.Crowd public RcVec3f CalcSmoothSteerDirection() { RcVec3f dir = new RcVec3f(); - if (0 < corners.Count) + if (0 < ncorners) { int ip0 = 0; - int ip1 = Math.Min(1, corners.Count - 1); + int ip1 = Math.Min(1, ncorners - 1); var p0 = corners[ip0].pos; var p1 = corners[ip1].pos; @@ -161,7 +164,7 @@ namespace DotRecast.Detour.Crowd public RcVec3f CalcStraightSteerDirection() { RcVec3f dir = new RcVec3f(); - if (0 < corners.Count) + if (0 < ncorners) { dir = RcVec3f.Subtract(corners[0].pos, npos); dir.Y = 0; diff --git a/src/DotRecast.Detour.Crowd/DtPathCorridor.cs b/src/DotRecast.Detour.Crowd/DtPathCorridor.cs index 76020ba..9d543dc 100644 --- a/src/DotRecast.Detour.Crowd/DtPathCorridor.cs +++ b/src/DotRecast.Detour.Crowd/DtPathCorridor.cs @@ -133,42 +133,42 @@ namespace DotRecast.Detour.Crowd /// @param[in] navquery The query object used to build the corridor. /// @param[in] filter The filter to apply to the operation. /// @return The number of corners returned in the corner buffers. [0 <= value <= @p maxCorners] - public int FindCorners(ref List corners, int maxCorners, DtNavMeshQuery navquery, IDtQueryFilter filter) + public int FindCorners(Span corners, int maxCorners, DtNavMeshQuery navquery, IDtQueryFilter filter) { const float MIN_TARGET_DIST = 0.01f; - var result = navquery.FindStraightPath(m_pos, m_target, m_path, m_npath, ref corners, maxCorners, 0); - if (result.Succeeded()) + int ncorners = 0; + navquery.FindStraightPath(m_pos, m_target, m_path, m_npath, corners, out ncorners, maxCorners, 0); + + // Prune points in the beginning of the path which are too close. + while (0 < ncorners) { - // Prune points in the beginning of the path which are too close. - int start = 0; - foreach (DtStraightPath spi in corners) + if ((corners[0].flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0 || + RcVecUtils.Dist2DSqr(corners[0].pos, m_pos) > RcMath.Sqr(MIN_TARGET_DIST)) { - if ((spi.flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0 - || RcVecUtils.Dist2DSqr(spi.pos, m_pos) > RcMath.Sqr(MIN_TARGET_DIST)) - { - break; - } - - start++; + break; } - int end = corners.Count; - // Prune points after an off-mesh connection. - for (int i = start; i < corners.Count; i++) + ncorners--; + if (0 < ncorners) { - DtStraightPath spi = corners[i]; - if ((spi.flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) - { - end = i + 1; - break; - } + RcSpans.Move(corners, 1, 0, 3); } - - corners = corners.GetRange(start, end - start); } - return corners.Count; + + // Prune points after an off-mesh connection. + for (int i = 0; i < ncorners; ++i) + { + if ((corners[i].flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) + { + ncorners = i + 1; + break; + } + } + + + return ncorners; } /** diff --git a/src/DotRecast.Detour/DtNavMeshQuery.cs b/src/DotRecast.Detour/DtNavMeshQuery.cs index c73cd30..3bf0cc3 100644 --- a/src/DotRecast.Detour/DtNavMeshQuery.cs +++ b/src/DotRecast.Detour/DtNavMeshQuery.cs @@ -20,9 +20,7 @@ freely, subject to the following restrictions: using System; using System.Collections.Generic; -using System.Reflection; using DotRecast.Core; -using DotRecast.Core.Collections; using DotRecast.Core.Numerics; namespace DotRecast.Detour @@ -1485,24 +1483,27 @@ namespace DotRecast.Detour return DtStatus.DT_SUCCESS | details; } - protected DtStatus AppendVertex(RcVec3f pos, int flags, long refs, ref List straightPath, - int maxStraightPath) + protected DtStatus AppendVertex(RcVec3f pos, int flags, long refs, Span straightPath, ref int straightPathCount, int maxStraightPath) { - if (straightPath.Count > 0 && DtUtils.VEqual(straightPath[straightPath.Count - 1].pos, pos)) + if (straightPathCount > 0 && DtUtils.VEqual(straightPath[straightPathCount - 1].pos, pos)) { // The vertices are equal, update flags and poly. - straightPath[straightPath.Count - 1] = new DtStraightPath(straightPath[straightPath.Count - 1].pos, flags, refs); + straightPath[straightPathCount - 1] = new DtStraightPath(straightPath[straightPathCount - 1].pos, flags, refs); } else { - if (straightPath.Count < maxStraightPath) + // Append new vertex. + straightPath[straightPathCount] = new DtStraightPath(pos, flags, refs); + straightPathCount++; + + // If there is no space to append more vertices, return. + if (straightPathCount >= maxStraightPath) { - // Append new vertex. - straightPath.Add(new DtStraightPath(pos, flags, refs)); + return DtStatus.DT_SUCCESS | DtStatus.DT_BUFFER_TOO_SMALL; } - // If reached end of path or there is no space to append more vertices, return. - if (flags == DtStraightPathFlags.DT_STRAIGHTPATH_END || straightPath.Count >= maxStraightPath) + // If reached end of path, return. + if (flags == DtStraightPathFlags.DT_STRAIGHTPATH_END) { return DtStatus.DT_SUCCESS; } @@ -1512,9 +1513,9 @@ namespace DotRecast.Detour } protected DtStatus AppendPortals(int startIdx, int endIdx, RcVec3f endPos, List path, - ref List straightPath, int maxStraightPath, int options) + Span straightPath, ref int straightPathCount, int maxStraightPath, int options) { - var startPos = straightPath[straightPath.Count - 1].pos; + var startPos = straightPath[straightPathCount - 1].pos; // Append or update last vertex DtStatus stat; for (int i = startIdx; i < endIdx; i++) @@ -1553,7 +1554,7 @@ namespace DotRecast.Detour if (DtUtils.IntersectSegSeg2D(startPos, endPos, left, right, out var _, out var t)) { var pt = RcVec3f.Lerp(left, right, t); - stat = AppendVertex(pt, 0, path[i + 1], ref straightPath, maxStraightPath); + stat = AppendVertex(pt, 0, path[i + 1], straightPath, ref straightPathCount, maxStraightPath); if (!stat.InProgress()) { return stat; @@ -1590,17 +1591,22 @@ namespace DotRecast.Detour /// @param[in] maxStraightPath The maximum number of points the straight path arrays can hold. [Limit: > 0] /// @param[in] options Query options. (see: #dtStraightPathOptions) /// @returns The status flags for the query. - public virtual DtStatus FindStraightPath(RcVec3f startPos, RcVec3f endPos, List path, int pathSize, - ref List straightPath, - int maxStraightPath, int options) + public virtual DtStatus FindStraightPath(RcVec3f startPos, RcVec3f endPos, + List path, int pathSize, + Span straightPath, out int straightPathCount, int maxStraightPath, + int options) { - if (!startPos.IsFinite() || !endPos.IsFinite() || null == straightPath - || null == path || pathSize <= 0 || path[0] == 0 || maxStraightPath <= 0) + straightPathCount = 0; + + if (!startPos.IsFinite() || !endPos.IsFinite() || + null == straightPath || + null == path || pathSize <= 0 || path[0] == 0 + || maxStraightPath <= 0) { return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM; } - straightPath.Clear(); + DtStatus stat = DtStatus.DT_STATUS_NOTHING; // TODO: Should this be callers responsibility? var closestStartPosRes = ClosestPointOnPolyBoundary(path[0], startPos, out var closestStartPos); @@ -1616,7 +1622,7 @@ namespace DotRecast.Detour } // Add start point. - DtStatus stat = AppendVertex(closestStartPos, DtStraightPathFlags.DT_STRAIGHTPATH_START, path[0], ref straightPath, maxStraightPath); + stat = AppendVertex(closestStartPos, DtStraightPathFlags.DT_STRAIGHTPATH_START, path[0], straightPath, ref straightPathCount, maxStraightPath); if (!stat.InProgress()) { return stat; @@ -1663,13 +1669,13 @@ namespace DotRecast.Detour if ((options & (DtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | DtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) { // Ignore status return value as we're just about to return anyway. - AppendPortals(apexIndex, i, closestEndPos, path, ref straightPath, maxStraightPath, options); + AppendPortals(apexIndex, i, closestEndPos, path, straightPath, ref straightPathCount, maxStraightPath, options); } // Ignore status return value as we're just about to return anyway. - AppendVertex(closestEndPos, 0, path[i], ref straightPath, maxStraightPath); + AppendVertex(closestEndPos, 0, path[i], straightPath, ref straightPathCount, maxStraightPath); - return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM | (straightPath.Count >= maxStraightPath ? DtStatus.DT_BUFFER_TOO_SMALL : DtStatus.DT_STATUS_NOTHING); + return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM | (straightPathCount >= maxStraightPath ? DtStatus.DT_BUFFER_TOO_SMALL : DtStatus.DT_STATUS_NOTHING); } // If starting really close the portal, advance. @@ -1705,7 +1711,7 @@ namespace DotRecast.Detour // Append portals along the current straight path segment. if ((options & (DtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | DtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) { - stat = AppendPortals(apexIndex, leftIndex, portalLeft, path, ref straightPath, maxStraightPath, options); + stat = AppendPortals(apexIndex, leftIndex, portalLeft, path, straightPath, ref straightPathCount, maxStraightPath, options); if (!stat.InProgress()) { return stat; @@ -1728,7 +1734,7 @@ namespace DotRecast.Detour long refs = leftPolyRef; // Append or update vertex - stat = AppendVertex(portalApex, flags, refs, ref straightPath, maxStraightPath); + stat = AppendVertex(portalApex, flags, refs, straightPath, ref straightPathCount, maxStraightPath); if (!stat.InProgress()) { return stat; @@ -1761,7 +1767,7 @@ namespace DotRecast.Detour // Append portals along the current straight path segment. if ((options & (DtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | DtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) { - stat = AppendPortals(apexIndex, rightIndex, portalRight, path, ref straightPath, maxStraightPath, options); + stat = AppendPortals(apexIndex, rightIndex, portalRight, path, straightPath, ref straightPathCount, maxStraightPath, options); if (!stat.InProgress()) { return stat; @@ -1784,7 +1790,7 @@ namespace DotRecast.Detour long refs = rightPolyRef; // Append or update vertex - stat = AppendVertex(portalApex, flags, refs, ref straightPath, maxStraightPath); + stat = AppendVertex(portalApex, flags, refs, straightPath, ref straightPathCount, maxStraightPath); if (!stat.InProgress()) { return stat; @@ -1806,7 +1812,7 @@ namespace DotRecast.Detour // Append portals along the current straight path segment. if ((options & (DtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | DtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) { - stat = AppendPortals(apexIndex, pathSize - 1, closestEndPos, path, ref straightPath, maxStraightPath, options); + stat = AppendPortals(apexIndex, pathSize - 1, closestEndPos, path, straightPath, ref straightPathCount, maxStraightPath, options); if (!stat.InProgress()) { return stat; @@ -1815,8 +1821,8 @@ namespace DotRecast.Detour } // Ignore status return value as we're just about to return anyway. - AppendVertex(closestEndPos, DtStraightPathFlags.DT_STRAIGHTPATH_END, 0, ref straightPath, maxStraightPath); - return DtStatus.DT_SUCCESS | (straightPath.Count >= maxStraightPath ? DtStatus.DT_BUFFER_TOO_SMALL : DtStatus.DT_STATUS_NOTHING); + AppendVertex(closestEndPos, DtStraightPathFlags.DT_STRAIGHTPATH_END, 0, straightPath, ref straightPathCount, maxStraightPath); + return DtStatus.DT_SUCCESS | (straightPathCount >= maxStraightPath ? DtStatus.DT_BUFFER_TOO_SMALL : DtStatus.DT_STATUS_NOTHING); } /// @par diff --git a/src/DotRecast.Detour/DtNavMeshQueryMock.cs b/src/DotRecast.Detour/DtNavMeshQueryMock.cs new file mode 100644 index 0000000..d9e6275 --- /dev/null +++ b/src/DotRecast.Detour/DtNavMeshQueryMock.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using DotRecast.Core.Numerics; + +namespace DotRecast.Detour +{ + public class DtNavMeshQueryMock : DtNavMeshQuery + { + private readonly DtStraightPath[] _straightPath; + private readonly DtStatus _status; + + public DtNavMeshQueryMock(DtStraightPath[] straightPath, DtStatus status) + : base(null) + { + _straightPath = straightPath; + _status = status; + } + + public override DtStatus FindStraightPath(RcVec3f startPos, RcVec3f endPos, + List path, int pathSize, + Span straightPath, out int straightPathCount, int maxStraightPath, + int options) + { + straightPathCount = 0; + for (int i = 0; i < _straightPath.Length && i < maxStraightPath; ++i) + { + straightPath[i] = _straightPath[i]; + straightPathCount += 1; + } + + return _status; + } + } +} \ No newline at end of file diff --git a/src/DotRecast.Detour/DtPathUtils.cs b/src/DotRecast.Detour/DtPathUtils.cs index 7dbaa6b..fe366b7 100644 --- a/src/DotRecast.Detour/DtPathUtils.cs +++ b/src/DotRecast.Detour/DtPathUtils.cs @@ -34,14 +34,14 @@ namespace DotRecast.Detour out RcVec3f steerPos, out int steerPosFlag, out long steerPosRef) { const int MAX_STEER_POINTS = 3; - + steerPos = RcVec3f.Zero; steerPosFlag = 0; steerPosRef = 0; // Find steer target. - var straightPath = new List(MAX_STEER_POINTS); - var result = navQuery.FindStraightPath(startPos, endPos, path, pathSize, ref straightPath, MAX_STEER_POINTS, 0); + Span straightPath = stackalloc DtStraightPath[MAX_STEER_POINTS]; + var result = navQuery.FindStraightPath(startPos, endPos, path, pathSize, straightPath, out var nsteerPath, MAX_STEER_POINTS, 0); if (result.Failed()) { return false; @@ -49,7 +49,7 @@ namespace DotRecast.Detour // Find vertex far enough to steer to. int ns = 0; - while (ns < straightPath.Count) + while (ns < nsteerPath) { // Stop at Off-Mesh link or when point is further than slop away. if (((straightPath[ns].flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) @@ -59,7 +59,7 @@ namespace DotRecast.Detour } // Failed to find good point to steer to. - if (ns >= straightPath.Count) + if (ns >= nsteerPath) return false; steerPos = straightPath[ns].pos; diff --git a/src/DotRecast.Recast.Demo/Tools/CrowdSampleTool.cs b/src/DotRecast.Recast.Demo/Tools/CrowdSampleTool.cs index e33ec2c..9c5d96f 100644 --- a/src/DotRecast.Recast.Demo/Tools/CrowdSampleTool.cs +++ b/src/DotRecast.Recast.Demo/Tools/CrowdSampleTool.cs @@ -251,10 +251,10 @@ public class CrowdSampleTool : ISampleTool if (_showCorners) { - if (0 < ag.corners.Count) + if (0 < ag.ncorners) { dd.Begin(LINES, 2.0f); - for (int j = 0; j < ag.corners.Count; ++j) + for (int j = 0; j < ag.ncorners; ++j) { RcVec3f va = j == 0 ? pos : ag.corners[j - 1].pos; RcVec3f vb = ag.corners[j].pos; @@ -262,10 +262,10 @@ public class CrowdSampleTool : ISampleTool dd.Vertex(vb.X, vb.Y + radius, vb.Z, DuRGBA(128, 0, 0, 192)); } - if ((ag.corners[ag.corners.Count - 1].flags + if ((ag.corners[ag.ncorners - 1].flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) { - RcVec3f v = ag.corners[ag.corners.Count - 1].pos; + RcVec3f v = ag.corners[ag.ncorners - 1].pos; dd.Vertex(v.X, v.Y, v.Z, DuRGBA(192, 0, 0, 192)); dd.Vertex(v.X, v.Y + radius * 2, v.Z, DuRGBA(192, 0, 0, 192)); } diff --git a/src/DotRecast.Recast.Demo/Tools/TestNavmeshSampleTool.cs b/src/DotRecast.Recast.Demo/Tools/TestNavmeshSampleTool.cs index d692d46..917bdb7 100644 --- a/src/DotRecast.Recast.Demo/Tools/TestNavmeshSampleTool.cs +++ b/src/DotRecast.Recast.Demo/Tools/TestNavmeshSampleTool.cs @@ -57,7 +57,8 @@ public class TestNavmeshSampleTool : ISampleTool private bool m_hitResult; private float m_distanceToWall; - private List m_straightPath; + private DtStraightPath[] m_straightPath; + private int m_straightPathCount; private List m_polys; private List m_parent; private float m_neighbourhoodRadius; @@ -77,6 +78,8 @@ public class TestNavmeshSampleTool : ISampleTool SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 1f, 1f, 1f, 2f, 1.5f } ); + m_straightPath = new DtStraightPath[MAX_POLYS]; + m_straightPathCount = 0; } public void Layout() @@ -284,7 +287,7 @@ public class TestNavmeshSampleTool : ISampleTool int spathCol = DuRGBA(64, 16, 0, 220); int offMeshCol = DuRGBA(128, 96, 0, 220); dd.Begin(LINES, 2.0f); - for (int i = 0; i < m_straightPath.Count - 1; ++i) + for (int i = 0; i < m_straightPathCount - 1; ++i) { DtStraightPath straightPathItem = m_straightPath[i]; DtStraightPath straightPathItem2 = m_straightPath[i + 1]; @@ -304,7 +307,7 @@ public class TestNavmeshSampleTool : ISampleTool dd.End(); dd.Begin(POINTS, 6.0f); - for (int i = 0; i < m_straightPath.Count; ++i) + for (int i = 0; i < m_straightPathCount; ++i) { DtStraightPath straightPathItem = m_straightPath[i]; int col; @@ -349,7 +352,7 @@ public class TestNavmeshSampleTool : ISampleTool dd.DepthMask(false); int spathCol = m_hitResult ? DuRGBA(64, 16, 0, 220) : DuRGBA(240, 240, 240, 220); dd.Begin(LINES, 2.0f); - for (int i = 0; i < m_straightPath.Count - 1; ++i) + for (int i = 0; i < m_straightPathCount - 1; ++i) { DtStraightPath straightPathItem = m_straightPath[i]; DtStraightPath straightPathItem2 = m_straightPath[i + 1]; @@ -359,7 +362,7 @@ public class TestNavmeshSampleTool : ISampleTool dd.End(); dd.Begin(POINTS, 4.0f); - for (int i = 0; i < m_straightPath.Count; ++i) + for (int i = 0; i < m_straightPathCount; ++i) { DtStraightPath straightPathItem = m_straightPath[i]; dd.Vertex(straightPathItem.pos.X, straightPathItem.pos.Y + 0.4f, straightPathItem.pos.Z, spathCol); @@ -666,18 +669,18 @@ public class TestNavmeshSampleTool : ISampleTool else if (_mode == RcTestNavmeshToolMode.PATHFIND_STRAIGHT) { _tool.FindStraightPath(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _enableRaycast, - ref m_polys, ref m_straightPath, _straightPathOption); + ref m_polys, m_straightPath, out m_straightPathCount, MAX_POLYS, _straightPathOption); } else if (_mode == RcTestNavmeshToolMode.PATHFIND_SLICED) { m_polys?.Clear(); - m_straightPath?.Clear(); + m_straightPathCount = 0; m_pathFindStatus = _tool.InitSlicedFindPath(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _enableRaycast); } else if (_mode == RcTestNavmeshToolMode.RAYCAST) { _tool.Raycast(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, - ref m_polys, ref m_straightPath, ref m_hitPos, ref m_hitNormal, ref m_hitResult); + ref m_polys, m_straightPath, out m_straightPathCount, MAX_POLYS, ref m_hitPos, ref m_hitNormal, ref m_hitResult); } else if (_mode == RcTestNavmeshToolMode.DISTANCE_TO_WALL) { @@ -712,7 +715,7 @@ public class TestNavmeshSampleTool : ISampleTool if (m_pathFindStatus.InProgress()) { - m_pathFindStatus = _tool.UpdateSlicedFindPath(navQuery, 1, m_endRef, m_spos, m_epos, ref m_polys, ref m_straightPath); + m_pathFindStatus = _tool.UpdateSlicedFindPath(navQuery, 1, m_endRef, m_spos, m_epos, ref m_polys, m_straightPath, out m_straightPathCount, MAX_POLYS); } } } diff --git a/src/DotRecast.Recast.Toolset/Tools/RcTestNavMeshTool.cs b/src/DotRecast.Recast.Toolset/Tools/RcTestNavMeshTool.cs index ef8b369..37a7b56 100644 --- a/src/DotRecast.Recast.Toolset/Tools/RcTestNavMeshTool.cs +++ b/src/DotRecast.Recast.Toolset/Tools/RcTestNavMeshTool.cs @@ -11,7 +11,6 @@ namespace DotRecast.Recast.Toolset.Tools public const int MAX_POLYS = 256; public const int MAX_SMOOTH = 2048; - public RcTestNavMeshTool() { } @@ -172,15 +171,15 @@ namespace DotRecast.Recast.Toolset.Tools } public DtStatus FindStraightPath(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPt, RcVec3f endPt, IDtQueryFilter filter, bool enableRaycast, - ref List polys, ref List straightPath, int straightPathOptions) + ref List polys, Span straightPath, out int straightPathCount, int maxStraightPath, int straightPathOptions) { + straightPathCount = 0; if (startRef == 0 || endRef == 0) { return DtStatus.DT_FAILURE; } polys ??= new List(); - straightPath ??= new List(); polys.Clear(); straightPath.Clear(); @@ -202,7 +201,7 @@ namespace DotRecast.Recast.Toolset.Tools } } - navQuery.FindStraightPath(startPt, epos, polys, polys.Count, ref straightPath, MAX_POLYS, straightPathOptions); + navQuery.FindStraightPath(startPt, epos, polys, polys.Count, straightPath, out straightPathCount, maxStraightPath, straightPathOptions); return DtStatus.DT_SUCCESS; } @@ -221,8 +220,9 @@ namespace DotRecast.Recast.Toolset.Tools } public DtStatus UpdateSlicedFindPath(DtNavMeshQuery navQuery, int maxIter, long endRef, RcVec3f startPos, RcVec3f endPos, - ref List path, ref List straightPath) + ref List path, Span straightPath, out int straightPathCount, int maxStraightPath) { + straightPathCount = 0; var status = navQuery.UpdateSlicedFindPath(maxIter, out _); if (!status.Succeeded()) @@ -232,7 +232,6 @@ namespace DotRecast.Recast.Toolset.Tools navQuery.FinalizeSlicedFindPath(ref path); - straightPath?.Clear(); if (path != null) { // In case of partial path, make sure the end point is clamped to the last polygon. @@ -246,8 +245,7 @@ namespace DotRecast.Recast.Toolset.Tools } } - straightPath = new List(MAX_POLYS); - navQuery.FindStraightPath(startPos, epos, path, path.Count, ref straightPath, MAX_POLYS, DtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS); + navQuery.FindStraightPath(startPos, epos, path, path.Count, straightPath, out straightPathCount, maxStraightPath, DtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS); } return DtStatus.DT_SUCCESS; @@ -255,13 +253,12 @@ namespace DotRecast.Recast.Toolset.Tools public DtStatus Raycast(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, - ref List polys, ref List straightPath, ref RcVec3f hitPos, ref RcVec3f hitNormal, ref bool hitResult) + ref List polys, Span straightPath, out int straightPathCount, int maxStraightPath, ref RcVec3f hitPos, ref RcVec3f hitNormal, ref bool hitResult) { + straightPathCount = 0; if (startRef == 0 || endRef == 0) { polys?.Clear(); - straightPath?.Clear(); - return DtStatus.DT_FAILURE; } @@ -299,10 +296,8 @@ namespace DotRecast.Recast.Toolset.Tools } } - straightPath ??= new List(); - straightPath.Clear(); - straightPath.Add(new DtStraightPath(startPos, 0, 0)); - straightPath.Add(new DtStraightPath(hitPos, 0, 0)); + straightPath[straightPathCount++] = new DtStraightPath(startPos, 0, 0); + straightPath[straightPathCount++] = new DtStraightPath(hitPos, 0, 0); return status; } diff --git a/test/DotRecast.Detour.Crowd.Test/DtPathCorridorTest.cs b/test/DotRecast.Detour.Crowd.Test/DtPathCorridorTest.cs index c77eb28..00b1763 100644 --- a/test/DotRecast.Detour.Crowd.Test/DtPathCorridorTest.cs +++ b/test/DotRecast.Detour.Crowd.Test/DtPathCorridorTest.cs @@ -17,15 +17,13 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. */ +using System; using System.Collections.Generic; using DotRecast.Core.Numerics; - -using Moq; using NUnit.Framework; namespace DotRecast.Detour.Crowd.Test; - public class DtPathCorridorTest { private readonly DtPathCorridor corridor = new DtPathCorridor(); @@ -41,63 +39,35 @@ public class DtPathCorridorTest [Test] public void ShouldKeepOriginalPathInFindCornersWhenNothingCanBePruned() { - List straightPath = new(); - straightPath.Add(new DtStraightPath(new RcVec3f(11, 20, 30.00001f), 0, 0)); - straightPath.Add(new DtStraightPath(new RcVec3f(12, 20, 30.00002f), 0, 0)); - straightPath.Add(new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0)); - straightPath.Add(new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0)); - var mockQuery = new Mock(It.IsAny()); - mockQuery.Setup(q => q.FindStraightPath( - It.IsAny(), - It.IsAny(), - It.IsAny>(), - It.IsAny(), - ref It.Ref>.IsAny, - It.IsAny(), - It.IsAny()) - ) - .Callback((RcVec3f startPos, RcVec3f endPos, List path, int pathSize, - ref List refStraightPath, int maxStraightPath, int options) => - { - refStraightPath = straightPath; - }) - .Returns(() => DtStatus.DT_SUCCESS); + var straightPath = new DtStraightPath[4]; + straightPath[0] = new DtStraightPath(new RcVec3f(11, 20, 30.00001f), 0, 0); + straightPath[1] = new DtStraightPath(new RcVec3f(12, 20, 30.00002f), 0, 0); + straightPath[2] = new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0); + straightPath[3] = new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0); + var query = new DtNavMeshQueryMock(straightPath, DtStatus.DT_SUCCESS); - var path = new List(); - corridor.FindCorners(ref path, int.MaxValue, mockQuery.Object, filter); - Assert.That(path.Count, Is.EqualTo(4)); - Assert.That(path, Is.EqualTo(straightPath)); + Span path = stackalloc DtStraightPath[8]; + var npath = corridor.FindCorners(path, 8, query, filter); + Assert.That(npath, Is.EqualTo(4)); + Assert.That(path.Slice(0, npath).ToArray(), Is.EqualTo(straightPath)); } + [Test] public void ShouldPrunePathInFindCorners() { - List straightPath = new(); - straightPath.Add(new DtStraightPath(new RcVec3f(10, 20, 30.00001f), 0, 0)); // too close - straightPath.Add(new DtStraightPath(new RcVec3f(10, 20, 30.00002f), 0, 0)); // too close - straightPath.Add(new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0)); - straightPath.Add(new DtStraightPath(new RcVec3f(12f, 22, 33f), DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION, 0)); // offmesh - straightPath.Add(new DtStraightPath(new RcVec3f(11f, 21, 32f), DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION, 0)); // offmesh + DtStraightPath[] straightPath = new DtStraightPath[5]; + straightPath[0] = (new DtStraightPath(new RcVec3f(10, 20, 30.00001f), 0, 0)); // too close + straightPath[1] = (new DtStraightPath(new RcVec3f(10, 20, 30.00002f), 0, 0)); // too close + straightPath[2] = (new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0)); + straightPath[3] = (new DtStraightPath(new RcVec3f(12f, 22, 33f), DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION, 0)); // offmesh + straightPath[4] = (new DtStraightPath(new RcVec3f(11f, 21, 32f), DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION, 0)); // offmesh - var mockQuery = new Mock(It.IsAny()); - mockQuery.Setup(q => q.FindStraightPath( - It.IsAny(), - It.IsAny(), - It.IsAny>(), - It.IsAny(), - ref It.Ref>.IsAny, - It.IsAny(), - It.IsAny()) - ).Callback((RcVec3f startPos, RcVec3f endPos, List path, int pathSize, - ref List refStraightPath, int maxStraightPath, int options) => - { - refStraightPath = straightPath; - }) - .Returns(() => DtStatus.DT_SUCCESS); + var query = new DtNavMeshQueryMock(straightPath, DtStatus.DT_SUCCESS); - var path = new List(); - corridor.FindCorners(ref path, int.MaxValue, mockQuery.Object, filter); - Assert.That(path.Count, Is.EqualTo(2)); - Assert.That(path, Is.EqualTo(new List { straightPath[2], straightPath[3] })); + Span path = stackalloc DtStraightPath[8]; + int npath = corridor.FindCorners(path, 8, query, filter); + Assert.That(npath, Is.EqualTo(2)); + Assert.That(path.Slice(0, npath).ToArray(), Is.EqualTo(new DtStraightPath[] { straightPath[2], straightPath[3] })); } } \ No newline at end of file diff --git a/test/DotRecast.Detour.Test/FindPathTest.cs b/test/DotRecast.Detour.Test/FindPathTest.cs index 32ad91a..92069d1 100644 --- a/test/DotRecast.Detour.Test/FindPathTest.cs +++ b/test/DotRecast.Detour.Test/FindPathTest.cs @@ -16,13 +16,13 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. */ +using System; using System.Collections.Generic; using DotRecast.Core.Numerics; using NUnit.Framework; namespace DotRecast.Detour.Test; - public class FindPathTest : AbstractDetourTest { private static readonly DtStatus[] STATUSES = @@ -184,6 +184,7 @@ public class FindPathTest : AbstractDetourTest { IDtQueryFilter filter = new DtQueryDefaultFilter(); var path = new List(); + Span straightPath = stackalloc DtStraightPath[256]; for (int i = 0; i < STRAIGHT_PATHS.Length; i++) { // startRefs.Length; i++) { @@ -192,9 +193,8 @@ public class FindPathTest : AbstractDetourTest var startPos = startPoss[i]; var endPos = endPoss[i]; var status = query.FindPath(startRef, endRef, startPos, endPos, filter, ref path, DtFindPathOption.NoOption); - var straightPath = new List(); - query.FindStraightPath(startPos, endPos, path, path.Count, ref straightPath, int.MaxValue, 0); - Assert.That(straightPath.Count, Is.EqualTo(STRAIGHT_PATHS[i].Length)); + query.FindStraightPath(startPos, endPos, path, path.Count, straightPath, out var nstraightPath, 256, 0); + Assert.That(nstraightPath, Is.EqualTo(STRAIGHT_PATHS[i].Length)); for (int j = 0; j < STRAIGHT_PATHS[i].Length; j++) { Assert.That(straightPath[j].refs, Is.EqualTo(STRAIGHT_PATHS[i][j].refs)); diff --git a/test/DotRecast.Detour.TileCache.Test/TileCacheFindPathTest.cs b/test/DotRecast.Detour.TileCache.Test/TileCacheFindPathTest.cs index 2139074..eab34e0 100644 --- a/test/DotRecast.Detour.TileCache.Test/TileCacheFindPathTest.cs +++ b/test/DotRecast.Detour.TileCache.Test/TileCacheFindPathTest.cs @@ -18,6 +18,7 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. */ +using System; using System.Collections.Generic; using System.IO; using DotRecast.Core; @@ -29,7 +30,6 @@ using NUnit.Framework; namespace DotRecast.Detour.TileCache.Test; - public class TileCacheFindPathTest : AbstractTileCacheTest { private readonly RcVec3f start = new RcVec3f(39.44734f, 9.998177f, -0.784811f); @@ -56,11 +56,11 @@ public class TileCacheFindPathTest : AbstractTileCacheTest var path = new List(); var status = query.FindPath(startRef, endRef, startPos, endPos, filter, ref path, DtFindPathOption.NoOption); - int maxStraightPath = 256; + const int maxStraightPath = 256; int options = 0; - var pathStr = new List(); - query.FindStraightPath(startPos, endPos, path, path.Count, ref pathStr, maxStraightPath, options); - Assert.That(pathStr.Count, Is.EqualTo(8)); + Span pathStr = stackalloc DtStraightPath[maxStraightPath]; + query.FindStraightPath(startPos, endPos, path, path.Count, pathStr, out var npathStr, maxStraightPath, options); + Assert.That(npathStr, Is.EqualTo(8)); } } \ No newline at end of file