Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pr/various mem optimizations #85

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/DotRecast.Core/Buffers/RcObjectPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;

namespace DotRecast.Core.Buffers
{
// This implementation is thread unsafe
public class RcObjectPool<T> where T : class
{
private readonly Queue<T> _items = new Queue<T>();
private readonly Func<T> _createFunc;

public RcObjectPool(Func<T> createFunc)
{
_createFunc = createFunc;
}

public T Get()
{
if (_items.TryDequeue(out var result))
return result;

return _createFunc();
}

public void Return(T obj)
{
_items.Enqueue(obj);
}
}
}
90 changes: 82 additions & 8 deletions src/DotRecast.Core/Buffers/RcRentedArray.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,96 @@
using System;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;

namespace DotRecast.Core.Buffers
{
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)
{
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 T[] _array;
private readonly RentIdGen _rentIdGen;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tricky one. Since RcRentedArray now a value type, it will copy values. That means when we dispose it, all copies will remain reference to owner and array.
To fix that we introduce rent id. When rent array, get vacant id and generation. On dispose check the generation. If generation is different, that means this rent was already freed. Otherwise release array and increase the generation.

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;
_array = array;
Length = length;
_rentIdGen = rentIdGen;
}

public ref T this[int index]
Expand All @@ -34,6 +99,8 @@ public ref T this[int index]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
if (IsDisposed)
throw new NullReferenceException();
return ref _array[index];
}
}
Expand All @@ -43,15 +110,22 @@ public T[] AsArray()
return _array;
}

public Span<T> AsSpan()
{
return new Span<T>(_array, 0, Length);
}


public void Dispose()
{
if (null != _owner && null != _array)
if (null != _owner && null != _array && !RcRentedArray.IsDisposed(_rentIdGen))
{
RcRentedArray.ReturnId(_rentIdGen);
_owner.Return(_array, true);
_owner = null;
_array = null;
}

_owner = null;
_array = null;
}
}
}
6 changes: 6 additions & 0 deletions src/DotRecast.Core/Collections/CollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,11 @@ public static void Shuffle<T>(this IList<T> list)
(list[k], list[n]) = (list[n], list[k]);
}
}

public static void AddRange<T>(this IList<T> list, Span<T> span)
{
foreach (var i in span)
list.Add(i);
}
}
}
8 changes: 4 additions & 4 deletions src/DotRecast.Core/Collections/RcSortedQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ public class RcSortedQueue<T>
{
private bool _dirty;
private readonly List<T> _items;
private readonly Comparer<T> _comparer;
private readonly Comparison<T> _comparison;

public RcSortedQueue(Comparison<T> comp)
{
_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()
Expand All @@ -55,7 +55,7 @@ private void Balance()
{
if (_dirty)
{
_items.Sort(_comparer); // reverse
_items.Sort(_comparison); // reverse
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List.Sort(IComparer) allocates Comparison, so we pass delegate directly to avoid allocations.

_dirty = false;
}
}
Expand Down Expand Up @@ -88,7 +88,7 @@ public bool Remove(T item)
return false;

//int idx = _items.BinarySearch(item, _comparer); // don't use this! Because reference types can be reused externally.
int idx = _items.FindLastIndex(x => item.Equals(x));
int idx = _items.LastIndexOf(item);
if (0 > idx)
return false;

Expand Down
2 changes: 1 addition & 1 deletion src/DotRecast.Core/Numerics/RcVec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public static float Dist2DSqr(RcVec3f v1, RcVec3f v2)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Dist2DSqr(RcVec3f p, float[] verts, int i)
public static float Dist2DSqr(RcVec3f p, Span<float> verts, int i)
{
float dx = verts[i] - p.X;
float dz = verts[i + 2] - p.Z;
Expand Down
13 changes: 9 additions & 4 deletions src/DotRecast.Detour.Crowd/DtCrowd.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public class DtCrowd
private readonly DtObstacleAvoidanceParams[] _obstacleQueryParams;
private readonly DtObstacleAvoidanceQuery _obstacleQuery;

private DtProximityGrid _grid;
private readonly DtProximityGrid _grid;

private int _maxPathResult;
private readonly RcVec3f _agentPlacementHalfExtents;
Expand Down Expand Up @@ -174,6 +174,7 @@ public DtCrowd(DtCrowdConfig config, DtNavMesh nav, Func<int, IDtQueryFilter> qu
_agentIdx = new RcAtomicInteger(0);
_agents = new Dictionary<int, DtCrowdAgent>();
_activeAgents = new List<DtCrowdAgent>();
_grid = new DtProximityGrid(_config.maxAgentRadius * 3);

// The navQuery is mostly used for local searches, no need for large node pool.
SetNavMesh(nav);
Expand Down Expand Up @@ -564,14 +565,18 @@ private void CheckPathValidity(IList<DtCrowdAgent> agents, float dt)
}
}

private readonly RcSortedQueue<DtCrowdAgent> UpdateMoveRequest_queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.targetReplanTime.CompareTo(a1.targetReplanTime));
private readonly List<long> UpdateMoveRequest_reqPath = new List<long>();
private void UpdateMoveRequest(IList<DtCrowdAgent> agents, float dt)
{
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateMoveRequest);

RcSortedQueue<DtCrowdAgent> queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.targetReplanTime.CompareTo(a1.targetReplanTime));
RcSortedQueue<DtCrowdAgent> queue = UpdateMoveRequest_queue;
queue.Clear();

// Fire off new requests.
List<long> reqPath = new List<long>();
List<long> reqPath = UpdateMoveRequest_reqPath;
reqPath.Clear();
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
Expand Down Expand Up @@ -864,7 +869,7 @@ private void BuildProximityGrid(IList<DtCrowdAgent> agents)
{
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildProximityGrid);

_grid = new DtProximityGrid(_config.maxAgentRadius * 3);
_grid.Clear();

for (var i = 0; i < agents.Count; i++)
{
Expand Down
23 changes: 14 additions & 9 deletions src/DotRecast.Detour.Crowd/DtPathCorridor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class DtPathCorridor
private RcVec3f m_pos;
private RcVec3f m_target;

private List<long> m_path;
private List<long> m_path = new List<long>();
private int m_npath;
private int m_maxPath;

Expand Down Expand Up @@ -89,7 +89,8 @@ public DtPathCorridor()
/// @return True if the initialization succeeded.
public bool Init(int maxPath)
{
m_path = new List<long>(maxPath);
if (m_path.Capacity < maxPath)
m_path.Capacity = maxPath;
m_npath = 0;
m_maxPath = maxPath;
return true;
Expand Down Expand Up @@ -218,7 +219,7 @@ public void OptimizePathVisibility(RcVec3f next, float pathOptimizationRange, Dt
{
if (res.Count > 1 && t > 0.99f)
{
m_npath = DtPathUtils.MergeCorridorStartShortcut(ref m_path, m_npath, m_maxPath, res, res.Count);
m_npath = DtPathUtils.MergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, res.Count);
}
}
}
Expand Down Expand Up @@ -250,7 +251,7 @@ public bool OptimizePathTopology(DtNavMeshQuery navquery, IDtQueryFilter filter,

if (status.Succeeded() && res.Count > 0)
{
m_npath = DtPathUtils.MergeCorridorStartShortcut(ref m_path, m_npath, m_maxPath, res, res.Count);
m_npath = DtPathUtils.MergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, res.Count);
return true;
}

Expand All @@ -276,7 +277,7 @@ public bool MoveOverOffmeshConnection(long offMeshConRef, long[] refs, ref RcVec
}

// Prune path
m_path = m_path.GetRange(npos, m_npath - npos);
m_path.RemoveRange(0, npos);
m_npath -= npos;

refs[0] = prevRef;
Expand Down Expand Up @@ -322,7 +323,7 @@ public bool MovePosition(RcVec3f npos, DtNavMeshQuery navquery, IDtQueryFilter f
var status = navquery.MoveAlongSurface(m_path[0], m_pos, npos, filter, out var result, visited, out var nvisited, MAX_VISITED);
if (status.Succeeded())
{
m_npath = DtPathUtils.MergeCorridorStartMoved(ref m_path, m_npath, m_maxPath, visited, nvisited);
m_npath = DtPathUtils.MergeCorridorStartMoved(m_path, m_npath, m_maxPath, visited, nvisited);

// Adjust the position to stay on top of the navmesh.
m_pos = result;
Expand Down Expand Up @@ -366,7 +367,7 @@ public bool MoveTargetPosition(RcVec3f npos, DtNavMeshQuery navquery, IDtQueryFi
var status = navquery.MoveAlongSurface(m_path[^1], m_target, npos, filter, out var result, visited, out nvisited, MAX_VISITED);
if (status.Succeeded())
{
m_npath = DtPathUtils.MergeCorridorEndMoved(ref m_path, m_npath, m_maxPath, visited, nvisited);
m_npath = DtPathUtils.MergeCorridorEndMoved(m_path, m_npath, m_maxPath, visited, nvisited);

// TODO: should we do that?
// Adjust the position to stay on top of the navmesh.
Expand Down Expand Up @@ -394,7 +395,11 @@ public bool MoveTargetPosition(RcVec3f npos, DtNavMeshQuery navquery, IDtQueryFi
public void SetCorridor(RcVec3f target, List<long> path)
{
m_target = target;
m_path = new List<long>(path);
if(path != m_path)
{
m_path.Clear();
m_path.AddRange(path);
}
m_npath = path.Count;
}

Expand Down Expand Up @@ -444,7 +449,7 @@ public bool TrimInvalidPath(long safeRef, float[] safePos, DtNavMeshQuery navque
else if (n < m_npath)
{
// The path is partially usable.
m_path = m_path.GetRange(0, n);
m_path.RemoveRange(n, m_path.Count - n);
m_npath = n;
}

Expand Down
8 changes: 7 additions & 1 deletion src/DotRecast.Detour.Crowd/DtProximityGrid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ 3. This notice may not be removed or altered from any source distribution.
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using DotRecast.Core.Buffers;

namespace DotRecast.Detour.Crowd
{
Expand All @@ -30,12 +31,14 @@ public class DtProximityGrid
private readonly float _cellSize;
private readonly float _invCellSize;
private readonly Dictionary<long, List<DtCrowdAgent>> _items;
private readonly RcObjectPool<List<DtCrowdAgent>> _listPool;

public DtProximityGrid(float cellSize)
{
_cellSize = cellSize;
_invCellSize = 1.0f / cellSize;
_items = new Dictionary<long, List<DtCrowdAgent>>();
_listPool = new RcObjectPool<List<DtCrowdAgent>>(() => new List<DtCrowdAgent>());
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand All @@ -57,6 +60,8 @@ public static void DecomposeKey(long key, out int x, out int y)

public void Clear()
{
foreach (var pair in _items)
_listPool.Return(pair.Value);
_items.Clear();
}

Expand All @@ -74,7 +79,8 @@ public void AddItem(DtCrowdAgent agent, float minx, float miny, float maxx, floa
long key = CombineKey(x, y);
if (!_items.TryGetValue(key, out var ids))
{
ids = new List<DtCrowdAgent>();
ids = _listPool.Get();
ids.Clear();
_items.Add(key, ids);
}

Expand Down
Loading
Loading