135 lines
3.5 KiB
C#

#pragma warning disable CS8618
using System;
using System.Collections.Generic;
using System.Threading;
namespace MessagePipe.Internal
{
internal sealed class FreeList<T> : IDisposable
where T : class
{
const int InitialCapacity = 4;
const int MinShrinkStart = 8;
T[] values;
int count;
FastQueue<int> freeIndex;
bool isDisposed;
readonly object gate = new object();
public FreeList()
{
Initialize();
}
public T[] GetValues() => values; // no lock, safe for iterate
public int GetCount()
{
lock (gate)
{
return count;
}
}
public int Add(T value)
{
lock (gate)
{
if (isDisposed) throw new ObjectDisposedException(nameof(FreeList<T>));
if (freeIndex.Count != 0)
{
var index = freeIndex.Dequeue();
values[index] = value;
count++;
return index;
}
else
{
// resize
var newValues = new T[values.Length * 2];
Array.Copy(values, 0, newValues, 0, values.Length);
freeIndex.EnsureNewCapacity(newValues.Length);
for (int i = values.Length; i < newValues.Length; i++)
{
freeIndex.Enqueue(i);
}
var index = freeIndex.Dequeue();
newValues[values.Length] = value;
count++;
Volatile.Write(ref values, newValues);
return index;
}
}
}
public void Remove(int index, bool shrinkWhenEmpty)
{
lock (gate)
{
if (isDisposed) return; // do nothing
ref var v = ref values[index];
if (v == null) throw new KeyNotFoundException($"key index {index} is not found.");
v = null;
freeIndex.Enqueue(index);
count--;
if (shrinkWhenEmpty && count == 0 && values.Length > MinShrinkStart)
{
Initialize(); // re-init.
}
}
}
/// <summary>
/// Dispose and get cleared count.
/// </summary>
public bool TryDispose(out int clearedCount)
{
lock (gate)
{
if (isDisposed)
{
clearedCount = 0;
return false;
}
clearedCount = count;
Dispose();
return true;
}
}
public void Dispose()
{
lock (gate)
{
if (isDisposed) return;
isDisposed = true;
freeIndex = null;
values = Array.Empty<T>();
count = 0;
}
}
// [MemberNotNull(nameof(freeIndex), nameof(values))]
void Initialize()
{
freeIndex = new FastQueue<int>(InitialCapacity);
for (int i = 0; i < InitialCapacity; i++)
{
freeIndex.Enqueue(i);
}
count = 0;
var v = new T[InitialCapacity];
Volatile.Write(ref values, v);
}
}
}