#pragma warning disable CS8618 using System; using System.Collections.Generic; using System.Threading; namespace MessagePipe.Internal { internal sealed class FreeList : IDisposable where T : class { const int InitialCapacity = 4; const int MinShrinkStart = 8; T[] values; int count; FastQueue 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)); 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. } } } /// /// Dispose and get cleared count. /// 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(); count = 0; } } // [MemberNotNull(nameof(freeIndex), nameof(values))] void Initialize() { freeIndex = new FastQueue(InitialCapacity); for (int i = 0; i < InitialCapacity; i++) { freeIndex.Enqueue(i); } count = 0; var v = new T[InitialCapacity]; Volatile.Write(ref values, v); } } }