Compare commits

...

6 Commits

Author SHA1 Message Date
wrenge 96ffed87e3 Rented array struct 2024-11-26 20:50:44 +03:00
wrenge 1315de063f Replaced arrays with spans in query. Replaced array allocation with buffer rent in query. 2024-11-26 20:05:00 +03:00
wrenge a6db4344e4 Queries change
(cherry picked from commit 2397f23fc3)
2024-11-26 20:00:22 +03:00
wrenge a7b9b772c4 Use rented array instead of allocating
(cherry picked from commit ff930712ee)
2024-11-26 19:59:47 +03:00
wrenge 7aeb31d369 Span access 2024-11-26 19:59:22 +03:00
wrenge 18b544845f Pass delegate 2024-11-26 19:54:21 +03:00
11 changed files with 169 additions and 41 deletions

View File

@ -1,31 +1,96 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
namespace DotRecast.Core.Buffers namespace DotRecast.Core.Buffers
{ {
public static class RcRentedArray public static class RcRentedArray
{ {
private sealed class RentIdPool
{
private int[] _generations;
private readonly Queue<int> _freeIds;
private int _maxId;
public RentIdPool(int capacity)
{
_generations = new int[capacity];
_freeIds = new Queue<int>(capacity);
}
internal RentIdGen AcquireId()
{
if (!_freeIds.TryDequeue(out int id))
{
id = _maxId++;
if(_generations.Length <= id)
Array.Resize(ref _generations, _generations.Length << 1);
}
return new RentIdGen(id, _generations[id]);
}
internal void ReturnId(int id)
{
_generations[id]++;
_freeIds.Enqueue(id);
}
internal int GetGeneration(int id)
{
return _generations.Length <= id ? 0 : _generations[id];
}
}
public const int START_RENT_ID_POOL_CAPACITY = 16;
private static readonly ThreadLocal<RentIdPool> _rentPool = new ThreadLocal<RentIdPool>(() => new RentIdPool(START_RENT_ID_POOL_CAPACITY));
public static RcRentedArray<T> Rent<T>(int minimumLength) public static RcRentedArray<T> Rent<T>(int minimumLength)
{ {
var array = ArrayPool<T>.Shared.Rent(minimumLength); var array = ArrayPool<T>.Shared.Rent(minimumLength);
return new RcRentedArray<T>(ArrayPool<T>.Shared, array, minimumLength); return new RcRentedArray<T>(ArrayPool<T>.Shared, _rentPool.Value.AcquireId(), array, minimumLength);
}
internal static bool IsDisposed(RentIdGen rentIdGen)
{
return _rentPool.Value.GetGeneration(rentIdGen.Id) != rentIdGen.Gen;
}
internal static void ReturnId(RentIdGen rentIdGen)
{
_rentPool.Value.ReturnId(rentIdGen.Id);
} }
} }
public class RcRentedArray<T> : IDisposable public readonly struct RentIdGen
{
public readonly int Id;
public readonly int Gen;
public RentIdGen(int id, int gen)
{
Id = id;
Gen = gen;
}
}
public struct RcRentedArray<T> : IDisposable
{ {
private ArrayPool<T> _owner; private ArrayPool<T> _owner;
private T[] _array; private T[] _array;
private readonly RentIdGen _rentIdGen;
public int Length { get; } public int Length { get; }
public bool IsDisposed => null == _owner || null == _array; public bool IsDisposed => null == _owner || null == _array || RcRentedArray.IsDisposed(_rentIdGen);
internal RcRentedArray(ArrayPool<T> owner, T[] array, int length) internal RcRentedArray(ArrayPool<T> owner, RentIdGen rentIdGen, T[] array, int length)
{ {
_owner = owner; _owner = owner;
_array = array; _array = array;
Length = length; Length = length;
_rentIdGen = rentIdGen;
} }
public ref T this[int index] public ref T this[int index]
@ -34,6 +99,8 @@ namespace DotRecast.Core.Buffers
get get
{ {
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length); RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
if (IsDisposed)
throw new NullReferenceException();
return ref _array[index]; return ref _array[index];
} }
} }
@ -51,12 +118,14 @@ namespace DotRecast.Core.Buffers
public void Dispose() public void Dispose()
{ {
if (null != _owner && null != _array) if (null != _owner && null != _array && !RcRentedArray.IsDisposed(_rentIdGen))
{ {
RcRentedArray.ReturnId(_rentIdGen);
_owner.Return(_array, true); _owner.Return(_array, true);
_owner = null;
_array = null;
} }
_owner = null;
_array = null;
} }
} }
} }

View File

@ -27,12 +27,12 @@ namespace DotRecast.Core.Collections
{ {
private bool _dirty; private bool _dirty;
private readonly List<T> _items; private readonly List<T> _items;
private readonly Comparer<T> _comparer; private readonly Comparison<T> _comparison;
public RcSortedQueue(Comparison<T> comp) public RcSortedQueue(Comparison<T> comp)
{ {
_items = new List<T>(); _items = new List<T>();
_comparer = Comparer<T>.Create((x, y) => comp.Invoke(x, y) * -1); _comparison = (x, y) => comp(x, y) * -1;
} }
public int Count() public int Count()
@ -55,7 +55,7 @@ namespace DotRecast.Core.Collections
{ {
if (_dirty) if (_dirty)
{ {
_items.Sort(_comparer); // reverse _items.Sort(_comparison); // reverse
_dirty = false; _dirty = false;
} }
} }

View File

@ -52,25 +52,12 @@ namespace DotRecast.Detour.Extras.Jumplink
RcVec3f halfExtents = new RcVec3f { X = cs, Y = heightRange, Z = cs }; RcVec3f halfExtents = new RcVec3f { X = cs, Y = heightRange, Z = cs };
float maxHeight = pt.Y + heightRange; float maxHeight = pt.Y + heightRange;
RcAtomicBoolean found = new RcAtomicBoolean(); var query = new DtHeightSamplePolyQuery(navMeshQuery, pt, pt.Y, maxHeight);
RcAtomicFloat minHeight = new RcAtomicFloat(pt.Y); navMeshQuery.QueryPolygons(pt, halfExtents, DtQueryNoOpFilter.Shared, ref query);
navMeshQuery.QueryPolygons(pt, halfExtents, DtQueryNoOpFilter.Shared, new DtCallbackPolyQuery((tile, poly, refs) => if (query.Found)
{ {
var status = navMeshQuery.GetPolyHeight(refs, pt, out var h); height = query.MinHeight;
if (status.Succeeded())
{
if (h > minHeight.Get() && h < maxHeight)
{
minHeight.Exchange(h);
found.Set(true);
}
}
}));
if (found.Get())
{
height = minHeight.Get();
return true; return true;
} }

View File

@ -2,7 +2,7 @@ using System;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public class DtCallbackPolyQuery : IDtPolyQuery public struct DtCallbackPolyQuery : IDtPolyQuery
{ {
private readonly Action<DtMeshTile, DtPoly, long> _callback; private readonly Action<DtMeshTile, DtPoly, long> _callback;
@ -11,7 +11,7 @@ namespace DotRecast.Detour
_callback = callback; _callback = callback;
} }
public void Process(DtMeshTile tile, DtPoly[] poly, Span<long> refs, int count) public void Process(DtMeshTile tile, Span<DtPoly> poly, Span<long> refs, int count)
{ {
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {

View File

@ -26,7 +26,7 @@ namespace DotRecast.Detour
return m_overflow; return m_overflow;
} }
public void Process(DtMeshTile tile, DtPoly[] poly, Span<long> refs, int count) public void Process(DtMeshTile tile, Span<DtPoly> poly, Span<long> refs, int count)
{ {
int numLeft = m_maxPolys - m_numCollected; int numLeft = m_maxPolys - m_numCollected;
int toCopy = count; int toCopy = count;

View File

@ -3,7 +3,7 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public class DtFindNearestPolyQuery : IDtPolyQuery public struct DtFindNearestPolyQuery : IDtPolyQuery
{ {
private readonly DtNavMeshQuery _query; private readonly DtNavMeshQuery _query;
private readonly RcVec3f _center; private readonly RcVec3f _center;
@ -18,9 +18,12 @@ namespace DotRecast.Detour
_center = center; _center = center;
_nearestDistanceSqr = float.MaxValue; _nearestDistanceSqr = float.MaxValue;
_nearestPoint = center; _nearestPoint = center;
_nearestRef = default;
_overPoly = default;
} }
public void Process(DtMeshTile tile, DtPoly[] poly, Span<long> refs, int count) public void Process(DtMeshTile tile, Span<DtPoly> poly, Span<long> refs, int count)
{ {
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {

View File

@ -0,0 +1,45 @@
using System;
using DotRecast.Core;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour
{
public struct DtHeightSamplePolyQuery : IDtPolyQuery
{
private readonly DtNavMeshQuery _navMeshQuery;
private readonly RcVec3f _pt;
private readonly float _maxHeight;
public float MinHeight { get; private set; }
public bool Found { get; private set; }
public DtHeightSamplePolyQuery(DtNavMeshQuery navMeshQuery, RcVec3f pt, float minHeight, float maxHeight)
{
_navMeshQuery = navMeshQuery;
_pt = pt;
MinHeight = minHeight;
_maxHeight = maxHeight;
Found = default;
}
public void Process(DtMeshTile tile, Span<DtPoly> poly, Span<long> refs, int count)
{
for (int i = 0; i < count; i++)
{
ProcessSingle(refs[i]);
}
}
private void ProcessSingle(long refs)
{
var status = _navMeshQuery.GetPolyHeight(refs, _pt, out var h);
if (!status.Succeeded())
return;
if (!(h > MinHeight) || !(h < _maxHeight))
return;
MinHeight = h;
Found = true;
}
}
}

View File

@ -1365,6 +1365,11 @@ namespace DotRecast.Detour
} }
public int GetTilesAt(int x, int y, DtMeshTile[] tiles, int maxTiles) public int GetTilesAt(int x, int y, DtMeshTile[] tiles, int maxTiles)
{
return GetTilesAt(x, y, (Span<DtMeshTile>)tiles, maxTiles);
}
public int GetTilesAt(int x, int y, Span<DtMeshTile> tiles, int maxTiles)
{ {
int n = 0; int n = 0;

View File

@ -589,7 +589,7 @@ namespace DotRecast.Detour
// Get nearby polygons from proximity grid. // Get nearby polygons from proximity grid.
DtFindNearestPolyQuery query = new DtFindNearestPolyQuery(this, center); DtFindNearestPolyQuery query = new DtFindNearestPolyQuery(this, center);
DtStatus status = QueryPolygons(center, halfExtents, filter, query); DtStatus status = QueryPolygons(center, halfExtents, filter, ref query);
if (status.Failed()) if (status.Failed())
{ {
return status; return status;
@ -603,11 +603,13 @@ namespace DotRecast.Detour
} }
/// Queries polygons within a tile. /// Queries polygons within a tile.
protected void QueryPolygonsInTile(DtMeshTile tile, RcVec3f qmin, RcVec3f qmax, IDtQueryFilter filter, IDtPolyQuery query) protected void QueryPolygonsInTile<TQuery>(DtMeshTile tile, RcVec3f qmin, RcVec3f qmax, IDtQueryFilter filter, ref TQuery query)
where TQuery : IDtPolyQuery
{ {
const int batchSize = 32; const int batchSize = 32;
Span<long> polyRefs = stackalloc long[batchSize]; Span<long> polyRefs = stackalloc long[batchSize];
DtPoly[] polys = new DtPoly[batchSize]; using RcRentedArray<DtPoly> polysRent = RcRentedArray.Rent<DtPoly>(batchSize);
Span<DtPoly> polys = polysRent.AsSpan();
int n = 0; int n = 0;
if (tile.data.bvTree != null) if (tile.data.bvTree != null)
@ -758,7 +760,7 @@ namespace DotRecast.Detour
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM; return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
DtCollectPolysQuery collector = new DtCollectPolysQuery(polys, maxPolys); DtCollectPolysQuery collector = new DtCollectPolysQuery(polys, maxPolys);
DtStatus status = QueryPolygons(center, halfExtents, filter, collector); DtStatus status = QueryPolygons(center, halfExtents, filter, ref collector);
if (status.Failed()) if (status.Failed())
return status; return status;
@ -780,7 +782,8 @@ namespace DotRecast.Detour
/// @param[in] halfExtents The search distance along each axis. [(x, y, z)] /// @param[in] halfExtents The search distance along each axis. [(x, y, z)]
/// @param[in] filter The polygon filter to apply to the query. /// @param[in] filter The polygon filter to apply to the query.
/// @param[in] query The query. Polygons found will be batched together and passed to this query. /// @param[in] query The query. Polygons found will be batched together and passed to this query.
public DtStatus QueryPolygons(RcVec3f center, RcVec3f halfExtents, IDtQueryFilter filter, IDtPolyQuery query) public DtStatus QueryPolygons<TQuery>(RcVec3f center, RcVec3f halfExtents, IDtQueryFilter filter, ref TQuery query)
where TQuery : IDtPolyQuery
{ {
if (!center.IsFinite() || !halfExtents.IsFinite() || null == filter) if (!center.IsFinite() || !halfExtents.IsFinite() || null == filter)
{ {
@ -796,7 +799,8 @@ namespace DotRecast.Detour
m_nav.CalcTileLoc(bmax, out var maxx, out var maxy); m_nav.CalcTileLoc(bmax, out var maxx, out var maxy);
const int MAX_NEIS = 32; const int MAX_NEIS = 32;
DtMeshTile[] neis = new DtMeshTile[MAX_NEIS]; using RcRentedArray<DtMeshTile> neisRent = RcRentedArray.Rent<DtMeshTile>(MAX_NEIS);
Span<DtMeshTile> neis = neisRent.AsSpan();
for (int y = miny; y <= maxy; ++y) for (int y = miny; y <= maxy; ++y)
{ {
@ -805,7 +809,7 @@ namespace DotRecast.Detour
int nneis = m_nav.GetTilesAt(x, y, neis, MAX_NEIS); int nneis = m_nav.GetTilesAt(x, y, neis, MAX_NEIS);
for (int j = 0; j < nneis; ++j) for (int j = 0; j < nneis; ++j)
{ {
QueryPolygonsInTile(neis[j], bmin, bmax, filter, query); QueryPolygonsInTile(neis[j], bmin, bmax, filter, ref query);
} }
} }
} }

View File

@ -9,6 +9,6 @@ namespace DotRecast.Detour
{ {
/// Called for each batch of unique polygons touched by the search area in dtNavMeshQuery::queryPolygons. /// Called for each batch of unique polygons touched by the search area in dtNavMeshQuery::queryPolygons.
/// This can be called multiple times for a single query. /// This can be called multiple times for a single query.
void Process(DtMeshTile tile, DtPoly[] poly, Span<long> refs, int count); void Process(DtMeshTile tile, Span<DtPoly> poly, Span<long> refs, int count);
} }
} }

View File

@ -103,4 +103,19 @@ public class RcRentedArrayTest
Assert.That(r1.IsDisposed, Is.EqualTo(true)); Assert.That(r1.IsDisposed, Is.EqualTo(true));
Assert.That(r1.AsArray(), Is.Null); Assert.That(r1.AsArray(), Is.Null);
} }
[Test]
public void TestIdPoolCapacity()
{
var buffer = new RcRentedArray<int>[RcRentedArray.START_RENT_ID_POOL_CAPACITY + 2];
for (int i = 0; i < buffer.Length; i++)
{
Assert.DoesNotThrow(() => buffer[i] = RcRentedArray.Rent<int>(4));
}
for (int i = 0; i < buffer.Length; i++)
{
Assert.DoesNotThrow(() => buffer[i].Dispose());
}
}
} }