forked from mirror/DotRecast
Changed from List<T> to RcCyclicBuffer in DtCrowdTelemetry execution timing sampling @wrenge
- https://github.com/ikpil/DotRecast/issues/51
This commit is contained in:
parent
a359686171
commit
ad504fe217
|
@ -7,11 +7,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
## [Unreleased] - yyyy-mm-dd
|
||||
|
||||
### Added
|
||||
- Added RcCircularBuffer<T> [@ikpil](https://github.com/ikpil)
|
||||
|
||||
### Fixed
|
||||
|
||||
### Changed
|
||||
- Added DtPathCorridor.Init(int maxPath) function to allow setting the maximum path [@ikpil](https://github.com/ikpil)
|
||||
- Changed DtPathCorridor.Init(int maxPath) function to allow setting the maximum path [@ikpil](https://github.com/ikpil)
|
||||
- Changed from List<T> to RcCyclicBuffer in DtCrowdTelemetry execution timing sampling [@wrenge](https://github.com/wrenge)
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
|
@ -1,48 +1,244 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Security;
|
||||
|
||||
namespace DotRecast.Core.Buffers
|
||||
{
|
||||
// https://github.com/joaoportela/CircularBuffer-CSharp/blob/master/CircularBuffer/CircularBuffer.cs
|
||||
public class RcCyclicBuffer<T>
|
||||
{
|
||||
public int MinIndex { get; private set; }
|
||||
public int MaxIndex { get; private set; }
|
||||
public int Count => MaxIndex - MinIndex + 1;
|
||||
public readonly int Size;
|
||||
|
||||
public T this[int index] => Get(index);
|
||||
|
||||
private readonly T[] _buffer;
|
||||
|
||||
public RcCyclicBuffer(in int size)
|
||||
private int _start;
|
||||
private int _end;
|
||||
private int _size;
|
||||
|
||||
public RcCyclicBuffer(int capacity)
|
||||
: this(capacity, new T[] { })
|
||||
{
|
||||
_buffer = new T[size];
|
||||
Size = size;
|
||||
MinIndex = 0;
|
||||
MaxIndex = -1;
|
||||
}
|
||||
|
||||
public void Add(in T item)
|
||||
public RcCyclicBuffer(int capacity, T[] items)
|
||||
{
|
||||
MaxIndex++;
|
||||
var index = MaxIndex % Size;
|
||||
if (capacity < 1)
|
||||
{
|
||||
throw new ArgumentException("RcCyclicBuffer cannot have negative or zero capacity.", nameof(capacity));
|
||||
}
|
||||
|
||||
if (MaxIndex >= Size)
|
||||
MinIndex = MaxIndex - Size + 1;
|
||||
if (items == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
}
|
||||
|
||||
_buffer[index] = item;
|
||||
if (items.Length > capacity)
|
||||
{
|
||||
throw new ArgumentException("Too many items to fit RcCyclicBuffer", nameof(items));
|
||||
}
|
||||
|
||||
_buffer = new T[capacity];
|
||||
|
||||
Array.Copy(items, _buffer, items.Length);
|
||||
_size = items.Length;
|
||||
|
||||
_start = 0;
|
||||
_end = _size == capacity ? 0 : _size;
|
||||
}
|
||||
|
||||
public T Get(in int index)
|
||||
{
|
||||
if (index < MinIndex || index > MaxIndex)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
public int Capacity => _buffer.Length;
|
||||
public bool IsFull => Size == Capacity;
|
||||
public bool IsEmpty => Size == 0;
|
||||
|
||||
return _buffer[index % Size];
|
||||
public int Size => _size;
|
||||
|
||||
public T Front()
|
||||
{
|
||||
ThrowIfEmpty();
|
||||
return _buffer[_start];
|
||||
}
|
||||
|
||||
public Span<T> AsSpan()
|
||||
public T Back()
|
||||
{
|
||||
return _buffer.AsSpan(0, Count);
|
||||
ThrowIfEmpty();
|
||||
return _buffer[(_end != 0 ? _end : Capacity) - 1];
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
throw new IndexOutOfRangeException($"Cannot access index {index}. Buffer is empty");
|
||||
}
|
||||
|
||||
if (index >= _size)
|
||||
{
|
||||
throw new IndexOutOfRangeException($"Cannot access index {index}. Buffer size is {_size}");
|
||||
}
|
||||
|
||||
int actualIndex = InternalIndex(index);
|
||||
return _buffer[actualIndex];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
throw new IndexOutOfRangeException($"Cannot access index {index}. Buffer is empty");
|
||||
}
|
||||
|
||||
if (index >= _size)
|
||||
{
|
||||
throw new IndexOutOfRangeException($"Cannot access index {index}. Buffer size is {_size}");
|
||||
}
|
||||
|
||||
int actualIndex = InternalIndex(index);
|
||||
_buffer[actualIndex] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void PushBack(T item)
|
||||
{
|
||||
if (IsFull)
|
||||
{
|
||||
_buffer[_end] = item;
|
||||
Increment(ref _end);
|
||||
_start = _end;
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffer[_end] = item;
|
||||
Increment(ref _end);
|
||||
++_size;
|
||||
}
|
||||
}
|
||||
|
||||
public void PushFront(T item)
|
||||
{
|
||||
if (IsFull)
|
||||
{
|
||||
Decrement(ref _start);
|
||||
_end = _start;
|
||||
_buffer[_start] = item;
|
||||
}
|
||||
else
|
||||
{
|
||||
Decrement(ref _start);
|
||||
_buffer[_start] = item;
|
||||
++_size;
|
||||
}
|
||||
}
|
||||
|
||||
public void PopBack()
|
||||
{
|
||||
ThrowIfEmpty("Cannot take elements from an empty buffer.");
|
||||
Decrement(ref _end);
|
||||
_buffer[_end] = default(T);
|
||||
--_size;
|
||||
}
|
||||
|
||||
public void PopFront()
|
||||
{
|
||||
ThrowIfEmpty("Cannot take elements from an empty buffer.");
|
||||
_buffer[_start] = default(T);
|
||||
Increment(ref _start);
|
||||
--_size;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
// to clear we just reset everything.
|
||||
_start = 0;
|
||||
_end = 0;
|
||||
_size = 0;
|
||||
Array.Clear(_buffer, 0, _buffer.Length);
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
int idx = 0;
|
||||
T[] newArray = new T[Size];
|
||||
|
||||
ForEach(x => newArray[idx++] = x);
|
||||
|
||||
return newArray;
|
||||
}
|
||||
|
||||
public void ForEach(Action<T> action)
|
||||
{
|
||||
var spanOne = ArrayOne();
|
||||
foreach (var item in spanOne)
|
||||
{
|
||||
action.Invoke(item);
|
||||
}
|
||||
|
||||
var spanTwo = ArrayTwo();
|
||||
foreach (var item in spanTwo)
|
||||
{
|
||||
action.Invoke(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfEmpty(string message = "Cannot access an empty buffer.")
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void Increment(ref int index)
|
||||
{
|
||||
if (++index == Capacity)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void Decrement(ref int index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
index = Capacity;
|
||||
}
|
||||
|
||||
index--;
|
||||
}
|
||||
|
||||
private int InternalIndex(int index)
|
||||
{
|
||||
return _start + (index < (Capacity - _start)
|
||||
? index
|
||||
: index - Capacity);
|
||||
}
|
||||
|
||||
private Span<T> ArrayOne()
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
return new Span<T>(Array.Empty<T>());
|
||||
}
|
||||
|
||||
if (_start < _end)
|
||||
{
|
||||
return new Span<T>(_buffer, _start, _end - _start);
|
||||
}
|
||||
|
||||
return new Span<T>(_buffer, _start, _buffer.Length - _start);
|
||||
}
|
||||
|
||||
private Span<T> ArrayTwo()
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
return new Span<T>(Array.Empty<T>());
|
||||
}
|
||||
|
||||
if (_start < _end)
|
||||
{
|
||||
return new Span<T>(_buffer, _end, 0);
|
||||
}
|
||||
|
||||
return new Span<T>(_buffer, 0, _end);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,12 +19,9 @@ freely, subject to the following restrictions:
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using DotRecast.Core;
|
||||
using DotRecast.Core.Buffers;
|
||||
using DotRecast.Core.Numerics;
|
||||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
@ -86,25 +83,25 @@ namespace DotRecast.Detour.Crowd
|
|||
private void Stop(DtCrowdTimerLabel name)
|
||||
{
|
||||
long duration = RcFrequency.Ticks - _executionTimings[name];
|
||||
if (!_executionTimingSamples.TryGetValue(name, out var s))
|
||||
if (!_executionTimingSamples.TryGetValue(name, out var cb))
|
||||
{
|
||||
s = new RcCyclicBuffer<long>(TIMING_SAMPLES);
|
||||
_executionTimingSamples.Add(name, s);
|
||||
cb = new RcCyclicBuffer<long>(TIMING_SAMPLES);
|
||||
_executionTimingSamples.Add(name, cb);
|
||||
}
|
||||
|
||||
s.Add(duration);
|
||||
_executionTimings[name] = CalculateAverage(s.AsSpan());
|
||||
cb.PushBack(duration);
|
||||
_executionTimings[name] = CalculateAverage(cb);
|
||||
}
|
||||
|
||||
private static long CalculateAverage(Span<long> buffer)
|
||||
private static long CalculateAverage(RcCyclicBuffer<long> buffer)
|
||||
{
|
||||
long sum = 0L;
|
||||
foreach (var item in buffer)
|
||||
buffer.ForEach(item =>
|
||||
{
|
||||
sum += item;
|
||||
}
|
||||
});
|
||||
|
||||
return sum / buffer.Length;
|
||||
return sum / buffer.Size;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
using System;
|
||||
using DotRecast.Core.Buffers;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace DotRecast.Core.Test;
|
||||
|
||||
// https://github.com/joaoportela/CircularBuffer-CSharp/blob/master/CircularBuffer.Tests/CircularBufferTests.cs
|
||||
public class RcCyclicBufferTests
|
||||
{
|
||||
[Test]
|
||||
public void RcCyclicBuffer_GetEnumeratorConstructorCapacity_ReturnsEmptyCollection()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<string>(5);
|
||||
Assert.That(buffer.ToArray(), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_ConstructorSizeIndexAccess_CorrectContent()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3 });
|
||||
|
||||
Assert.That(buffer.Capacity, Is.EqualTo(5));
|
||||
Assert.That(buffer.Size, Is.EqualTo(4));
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Assert.That(buffer[i], Is.EqualTo(i));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_Constructor_ExceptionWhenSourceIsLargerThanCapacity()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new RcCyclicBuffer<int>(3, new[] { 0, 1, 2, 3 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_GetEnumeratorConstructorDefinedArray_CorrectContent()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3 });
|
||||
|
||||
int x = 0;
|
||||
buffer.ForEach(item =>
|
||||
{
|
||||
Assert.That(item, Is.EqualTo(x));
|
||||
x++;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_PushBack_CorrectContent()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
buffer.PushBack(i);
|
||||
}
|
||||
|
||||
Assert.That(buffer.Front(), Is.EqualTo(0));
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Assert.That(buffer[i], Is.EqualTo(i));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_PushBackOverflowingBuffer_CorrectContent()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
buffer.PushBack(i);
|
||||
}
|
||||
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 5, 6, 7, 8, 9 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_GetEnumeratorOverflowedArray_CorrectContent()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
buffer.PushBack(i);
|
||||
}
|
||||
|
||||
// buffer should have [5,6,7,8,9]
|
||||
int x = 5;
|
||||
buffer.ForEach(item =>
|
||||
{
|
||||
Assert.That(item, Is.EqualTo(x));
|
||||
x++;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_ToArrayConstructorDefinedArray_CorrectContent()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3 });
|
||||
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 0, 1, 2, 3 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_ToArrayOverflowedBuffer_CorrectContent()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
buffer.PushBack(i);
|
||||
}
|
||||
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 5, 6, 7, 8, 9 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_PushFront_CorrectContent()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
buffer.PushFront(i);
|
||||
}
|
||||
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 4, 3, 2, 1, 0 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_PushFrontAndOverflow_CorrectContent()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
buffer.PushFront(i);
|
||||
}
|
||||
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 9, 8, 7, 6, 5 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_Front_CorrectItem()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
|
||||
|
||||
Assert.That(buffer.Front(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_Back_CorrectItem()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
|
||||
Assert.That(buffer.Back(), Is.EqualTo(4));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_BackOfBufferOverflowByOne_CorrectItem()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
|
||||
buffer.PushBack(42);
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4, 42 }));
|
||||
Assert.That(buffer.Back(), Is.EqualTo(42));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_Front_EmptyBufferThrowsException()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => buffer.Front());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_Back_EmptyBufferThrowsException()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5);
|
||||
Assert.Throws<InvalidOperationException>(() => buffer.Back());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_PopBack_RemovesBackElement()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
|
||||
|
||||
Assert.That(buffer.Size, Is.EqualTo(5));
|
||||
|
||||
buffer.PopBack();
|
||||
|
||||
Assert.That(buffer.Size, Is.EqualTo(4));
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 0, 1, 2, 3 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_PopBackInOverflowBuffer_RemovesBackElement()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
|
||||
buffer.PushBack(5);
|
||||
|
||||
Assert.That(buffer.Size, Is.EqualTo(5));
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4, 5 }));
|
||||
|
||||
buffer.PopBack();
|
||||
|
||||
Assert.That(buffer.Size, Is.EqualTo(4));
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_PopFront_RemovesBackElement()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
|
||||
|
||||
Assert.That(buffer.Size, Is.EqualTo(5));
|
||||
|
||||
buffer.PopFront();
|
||||
|
||||
Assert.That(buffer.Size, Is.EqualTo(4));
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_PopFrontInOverflowBuffer_RemovesBackElement()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
|
||||
buffer.PushFront(5);
|
||||
|
||||
Assert.That(buffer.Size, Is.EqualTo(5));
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 5, 0, 1, 2, 3 }));
|
||||
|
||||
buffer.PopFront();
|
||||
|
||||
Assert.That(buffer.Size, Is.EqualTo(4));
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 0, 1, 2, 3 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_SetIndex_ReplacesElement()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
|
||||
|
||||
buffer[1] = 10;
|
||||
buffer[3] = 30;
|
||||
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 0, 10, 2, 30, 4 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_WithDifferentSizeAndCapacity_BackReturnsLastArrayPosition()
|
||||
{
|
||||
// test to confirm this issue does not happen anymore:
|
||||
// https://github.com/joaoportela/RcCyclicBuffer-CSharp/issues/2
|
||||
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
|
||||
|
||||
buffer.PopFront(); // (make size and capacity different)
|
||||
|
||||
Assert.That(buffer.Back(), Is.EqualTo(4));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_Clear_ClearsContent()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 4, 3, 2, 1, 0 });
|
||||
|
||||
buffer.Clear();
|
||||
|
||||
Assert.That(buffer.Size, Is.EqualTo(0));
|
||||
Assert.That(buffer.Capacity, Is.EqualTo(5));
|
||||
Assert.That(buffer.ToArray(), Is.EqualTo(new int[0]));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RcCyclicBuffer_Clear_WorksNormallyAfterClear()
|
||||
{
|
||||
var buffer = new RcCyclicBuffer<int>(5, new[] { 4, 3, 2, 1, 0 });
|
||||
|
||||
buffer.Clear();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
buffer.PushBack(i);
|
||||
}
|
||||
|
||||
Assert.That(buffer.Front(), Is.EqualTo(0));
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Assert.That(buffer[i], Is.EqualTo(i));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue