diff --git a/src/DotRecast.Core/Buffers/RcRentedArray.cs b/src/DotRecast.Core/Buffers/RcRentedArray.cs index cf20f928..a26391d2 100644 --- a/src/DotRecast.Core/Buffers/RcRentedArray.cs +++ b/src/DotRecast.Core/Buffers/RcRentedArray.cs @@ -1,41 +1,108 @@ 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 _freeIds; + private int _maxId; + + public RentIdPool(int capacity) + { + _generations = new int[capacity]; + _freeIds = new Queue(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 _rentPool = new ThreadLocal(() => new RentIdPool(START_RENT_ID_POOL_CAPACITY)); + public static RcRentedArray Rent(int minimumLength) { var array = ArrayPool.Shared.Rent(minimumLength); - return new RcRentedArray(ArrayPool.Shared, array, minimumLength); + return new RcRentedArray(ArrayPool.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 : 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 : IDisposable { private ArrayPool _owner; private T[] _array; + private readonly RentIdGen _rentIdGen; public int Length { get; } - public bool IsDisposed => null == _owner || null == _array; + public bool IsDisposed => null == _owner || null == _array || RcRentedArray.IsDisposed(_rentIdGen); - internal RcRentedArray(ArrayPool owner, T[] array, int length) + internal RcRentedArray(ArrayPool owner, RentIdGen rentIdGen, T[] array, int length) { _owner = owner; _array = array; Length = length; + _rentIdGen = rentIdGen; } 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; } public ref T this[int index] @@ -44,6 +111,8 @@ public ref T this[int index] get { RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length); + if (IsDisposed) + throw new NullReferenceException(); return ref _array[index]; } } diff --git a/test/DotRecast.Core.Test/RcRentedArrayTest.cs b/test/DotRecast.Core.Test/RcRentedArrayTest.cs index 6ba6febf..9372d72a 100644 --- a/test/DotRecast.Core.Test/RcRentedArrayTest.cs +++ b/test/DotRecast.Core.Test/RcRentedArrayTest.cs @@ -103,4 +103,19 @@ public void TestDispose() Assert.That(r1.IsDisposed, Is.EqualTo(true)); Assert.That(r1.AsArray(), Is.Null); } + + [Test] + public void TestIdPoolCapacity() + { + var buffer = new RcRentedArray[RcRentedArray.START_RENT_ID_POOL_CAPACITY + 2]; + for (int i = 0; i < buffer.Length; i++) + { + Assert.DoesNotThrow(() => buffer[i] = RcRentedArray.Rent(4)); + } + + for (int i = 0; i < buffer.Length; i++) + { + Assert.DoesNotThrow(() => buffer[i].Dispose()); + } + } } \ No newline at end of file