diff --git a/README.md b/README.md index 81c658a..f72b87d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Recyclable.Collections -`Recyclable.Collections` project is an open source framework for operating dynamic lists at performance close to raw arrays, but fairly unlimited in size. It aims at providing minimal memory footprint. It implements `IList`'s interface and is targeted as direct replacements of `List`, `SortableList`, `PriorityQueue` & similar. +`Recyclable.Collections` project is an open source framework for operating dynamic lists at performance close to raw arrays, but fairly unlimited in size. It aims at providing minimal memory footprint. It implements `IList` interface and is targeted as direct replacements of `List`, `SortableList`, `PriorityQueue` & similar. ## Included * `RecyclableList` @@ -78,14 +78,14 @@ 1. ✅ Add support for `ReadOnlySpan` 1. ✅ Release 0.0.3-alpha 1. 👉 Implement `List` interfaces - 1. 🅿️ `ICollection` - 1. 🅿️ `IEnumerable` - 1. 🅿️ `IEnumerable` - 1. 🅿️ `IList` + 1. ✅ `IList` + 1. ✅ `ICollection` + 1. ✅ `IEnumerable` + 1. ✅ `IEnumerable` + 1. 👉 `IReadOnlyList` 1. 🅿️ `IReadOnlyCollection` - 1. 🅿️ `IReadOnlyList` - 1. 🅿️ `ICollection` 1. 🅿️ `IList` + 1. 🅿️ `ICollection` 1. 🅿️ Implement list versioning to allow data change identification 1. 🅿️ Make sure that `NeedsClearing` is used & items are cleared in 1. 🅿️ `Clear` @@ -106,6 +106,7 @@ 1. 🅿️ Replace `blockSize` sums by powers of 2, minus 1 1. 🅿️ Remove type castings, if possible 1. 🅿️ Convert generic methods to non-generic + 1. 🅿️ Replace integer comparisons with comparisons to 0 / 1, if possible 1. 🅿️ Overflow review 1. 🅿️ Add type casting to `long` for `<<` & `>>` operations, where required 1. 🅿️ Make type castings `checked` @@ -148,6 +149,10 @@ 1. 🅿️ `SpinLockSlimmer` 1. 🅿️ Multi-threading benchmarks 1. 🅿️ Release 0.0.9-beta +1. 🅿️ Extend unit tests + 1. 🅿️ `.Add` / `.AddRange` must allow `null` values + 1. 🅿️ `.Remove` / `.RemoveAt` / `.Clear` must clear reference when reference type + 1. 🅿️ `RecyclableLongListExtensions.CopyTo` 1. 🅿️ Cleanup 1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field 1. 🅿️ Cleanup `RecyclableLongListExtensions` @@ -157,6 +162,7 @@ 1. 🅿️ `ListExtensions` 1. 🅿️ `MathUtils` 1. 🅿️ `SystemRandomNumberGenerator` + 1. 🅿️ `RecyclableLongListExtensions` 1. 🅿️ Review and remove warnings & hints 1. 🅿️ Warnings 1. 🅿️ Hints @@ -166,6 +172,7 @@ 1. 🅿️ Release 1.0.0 1. 🅿️ Optimize 1. 🅿️ `RecyclableLongList.Resize` + 1. 🅿️ `RecyclableLongList.CopyTo` 1. 🅿️ Check if we can benefit from Sse2 in `.IndexOf`/`.Contains` methods as given in [MS blog](https://devblogs.microsoft.com/dotnet/hardware-intrinsics-in-net-core/). 1. 🅿️ Add support for `ICollection` interface in `.AddRange` & `constructor` 1. 🅿️ Release 2.0.0 @@ -425,9 +432,3 @@ public void RecyclableListExample() list.Clear(); } ``` -## `RecyclableList` - -## `RecyclableList` - -## `RecyclableList` - diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index 9394aa9..23e4abf 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -9,12 +9,15 @@ using Recyclable.Collections.Pools; namespace Recyclable.Collections { - public partial class RecyclableLongList : IDisposable, IList + public partial class RecyclableLongList : IDisposable, IList, IReadOnlyList, IList { private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; private static readonly T[] _emptyBlockArray = new T[0]; + private static readonly bool _defaultIsNull = default(T) == null; + private static void ThrowIndexOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + private static void ThrowArgumentOutOfRange(in string argumentName, in string message) => throw new ArgumentOutOfRangeException(argumentName, message); internal int _blockSize; internal byte _blockSizePow2BitShift; @@ -102,8 +105,6 @@ namespace Recyclable.Collections } } - private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); - protected void AddRangeEnumerated(IEnumerable source, int growByCount) { long i, copied = _longCount; @@ -1223,7 +1224,7 @@ namespace Recyclable.Collections { if (index >= _longCount || index < 0) { - ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + ThrowIndexOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); } _longCount--; @@ -1262,7 +1263,7 @@ namespace Recyclable.Collections { if (index >= _longCount || index < 0) { - ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + ThrowIndexOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); } _longCount--; @@ -1317,5 +1318,120 @@ namespace Recyclable.Collections GC.SuppressFinalize(this); } } + + #region IList implementation + + public bool IsFixedSize => false; + public bool IsSynchronized => false; + + private object? _syncRoot; + public object SyncRoot + { + get + { + if (_syncRoot == null) + { + _ = Interlocked.CompareExchange(ref _syncRoot, new object(), null); + } + + return _syncRoot; + } + } + + object? IList.this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + + set + { + if (value is T typedValue) + { + new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = typedValue; + } + else if (value == null && _defaultIsNull) + { +#nullable disable + new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = default; +#nullable restore + } + else + { + ThrowArgumentOutOfRange(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + } + } + } + + public int Add(object? value) + { + if (value is T typeValue) + { + Add(typeValue); + return (int)(_longCount - 1); + } + else if (value == null && _defaultIsNull) + { + Add(null); + return (int)(_longCount - 1); + } + else + { + ThrowArgumentOutOfRange(nameof(value), $"Argument is invalid. { (value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.") }"); + return RecyclableDefaults.ItemNotFoundIndex; + } + } + + public bool Contains(object? value) => value is T typeValue + ? Contains(typeValue) +#nullable disable + : value == null && _defaultIsNull && Contains(default); +#nullable restore + + public int IndexOf(object? value) => value is T typeValue + ? IndexOf(typeValue) + : value == null && _defaultIsNull +#nullable disable + ? IndexOf(default) +#nullable restore + : RecyclableDefaults.ItemNotFoundIndex; + + public void Insert(int index, object? value) + { + if (value is T typeValue) + { + Insert(index, typeValue); + } + else if (value == null && _defaultIsNull) + { +#nullable disable + Insert(index, default); +#nullable restore + } + else + { + ThrowArgumentOutOfRange(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + } + } + + public void Remove(object? value) + { + if (value is T typeValue) + { + _ = Remove(typeValue); + } + else if (value == null && _defaultIsNull) + { +#nullable disable + _ = Remove(default); +#nullable restore + } + else + { + ThrowArgumentOutOfRange(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + } + } + + public void CopyTo(Array array, int index) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, index); + + #endregion } } diff --git a/Recyclable.Collections/RecyclableLongListExtensions.cs b/Recyclable.Collections/RecyclableLongListExtensions.cs index e4e05b0..da6abdc 100644 --- a/Recyclable.Collections/RecyclableLongListExtensions.cs +++ b/Recyclable.Collections/RecyclableLongListExtensions.cs @@ -6,16 +6,6 @@ namespace Recyclable.Collections internal static class RecyclableLongListExtensions { private const int _minPooledArraySize = RecyclableDefaults.MinPooledArrayLength; - private const long ItemNotFoundIndexLong = -1L; - private const int ItemNotFoundIndex = -1; - - public static void CopyItems(this T[] memory, long startingIndex, long count, ref T[] destArray, long offset = 1) - { - for (long destItemIndex = startingIndex, sourceItemIndex = offset; destItemIndex < startingIndex + count; destItemIndex++, sourceItemIndex++) - { - destArray[destItemIndex] = memory[sourceItemIndex]; - } - } public static void CopyTo(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, T[] destinationArray, int destinationArrayIndex) { @@ -45,6 +35,30 @@ namespace Recyclable.Collections } } + public static void CopyTo(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, Array destinationArray, int destinationArrayIndex) + { + if (itemsCount <= 0) + { + return; + } + + int startingBlockIndex = (int)(startingIndex / blockSize); + int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize > 0 ? 1 : 0)) - 1; + + var destinationIndex = destinationArrayIndex; + Span sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1); + for (int memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++) + { + Array.ConstrainedCopy(sourceMemoryBlocksSpan[memoryBlockIndex], 0, destinationArray, destinationIndex, blockSize); + destinationIndex += blockSize; + } + + if (itemsCount - destinationIndex > 0) + { + Array.ConstrainedCopy(sourceMemoryBlocksSpan[lastBlockIndex], 0, destinationArray, destinationIndex, (int)(itemsCount - destinationIndex)); + } + } + //[MethodImpl(MethodImplOptions.AggressiveInlining)] //public static bool Contains(this RecyclableLongList arrays, T item) => arrays.Any(x => x.Contains(item));