Rented array struct

This commit is contained in:
wrenge 2024-11-26 20:50:44 +03:00
parent 1315de063f
commit 96ffed87e3
2 changed files with 91 additions and 7 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; _owner = null;
_array = null; _array = null;
} }
} }
} }
}

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());
}
}
} }