From a00b14730462bc5fbd297fbc0795bd56352867e9 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 19 Feb 2024 20:38:20 +0100 Subject: [PATCH] Initial RecyclableLongList.InsertRange() implementation. Before optimization. --- .../RecyclableLongList.AddRange.cs | 30 +- .../RecyclableLongList.Helpers.cs | 12 +- .../RecyclableLongList.InsertRange.cs | 2299 ++++++++++++++++- Recyclable.Collections/RecyclableLongList.cs | 34 +- .../Recyclable.CollectionsTests.csproj | 4 +- .../RecyclableLongList.Helpers.Tests.cs | 100 - .../RecyclableLongListTests.cs | 79 + 7 files changed, 2410 insertions(+), 148 deletions(-) delete mode 100644 Recyclable.CollectionsTests/RecyclableLongList.Helpers.Tests.cs diff --git a/Recyclable.Collections/RecyclableLongList.AddRange.cs b/Recyclable.Collections/RecyclableLongList.AddRange.cs index eaa2f36..b288f76 100644 --- a/Recyclable.Collections/RecyclableLongList.AddRange.cs +++ b/Recyclable.Collections/RecyclableLongList.AddRange.cs @@ -32,7 +32,7 @@ namespace Recyclable.Collections { if (copied + blockSize > capacity) { - capacity = RecyclableLongList.Helpers.Resize(targetList, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(capacity + blockSize)))); + capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(capacity + blockSize)))); memoryBlocksSpan = targetList._memoryBlocks; available = capacity - copied; } @@ -45,7 +45,7 @@ namespace Recyclable.Collections if (!enumerator.MoveNext()) { targetList._lastBlockWithData = targetBlockIdx; - targetList._longCount += copied + i - targetList._longCount; + targetList._longCount = copied + i; if (targetItemIdx < blockSize) { targetList._nextItemBlockIndex = targetBlockIdx; @@ -93,7 +93,7 @@ namespace Recyclable.Collections if (targetList._capacity < requiredCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)requiredCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)requiredCapacity))); } ReadOnlySpan memoryBlocksSpan = targetList._memoryBlocks; @@ -137,7 +137,7 @@ namespace Recyclable.Collections if (targetList._capacity < targetCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } ReadOnlySpan memoryBlocksSpan = targetList._memoryBlocks; @@ -186,7 +186,7 @@ namespace Recyclable.Collections long targetCapacity = targetList._longCount + items.Count; if (targetList._capacity < targetCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } // We're better off to temporarily copy it to a fixed array, @@ -252,7 +252,7 @@ namespace Recyclable.Collections long targetCapacity = targetList._longCount + items.Count; if (targetList._capacity < targetCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } // We're better off to temporarily copy it to a fixed array, @@ -332,7 +332,7 @@ namespace Recyclable.Collections { if (copied + blockSize > capacity) { - capacity = RecyclableLongList.Helpers.Resize(targetList, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(capacity + blockSize)))); + capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(capacity + blockSize)))); memoryBlocksSpan = targetList._memoryBlocks; available = capacity - copied; } @@ -345,7 +345,7 @@ namespace Recyclable.Collections if (!enumerator.MoveNext()) { targetList._lastBlockWithData = targetBlockIdx; - targetList._longCount += copied + i - targetList._longCount; + targetList._longCount = copied + i; if (targetItemIdx < blockSize) { targetList._nextItemBlockIndex = targetBlockIdx; @@ -445,7 +445,7 @@ namespace Recyclable.Collections if (targetList._capacity < requiredCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)requiredCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)requiredCapacity))); } ReadOnlySpan memoryBlocksSpan = targetList._memoryBlocks; @@ -488,7 +488,7 @@ namespace Recyclable.Collections long targetCapacity = targetList._longCount + items.Count; if (targetList._capacity < targetCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } ReadOnlySpan memoryBlocksSpan = targetList._memoryBlocks; @@ -534,7 +534,7 @@ namespace Recyclable.Collections long targetCapacity = targetList._longCount + items.Length; if (targetList._capacity < targetCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } ReadOnlySpan memoryBlocksSpan = targetList._memoryBlocks; @@ -589,7 +589,7 @@ namespace Recyclable.Collections long targetCapacity = targetList._longCount + items._count; if (targetList._capacity < targetCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } ReadOnlySpan itemsSpan = new(items._memoryBlock, 0, items._count); @@ -651,7 +651,7 @@ namespace Recyclable.Collections if (targetList._capacity < targetCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } ReadOnlySpan sourceMemoryBlocksSpan = items._memoryBlocks; @@ -719,7 +719,7 @@ namespace Recyclable.Collections if (targetList._capacity < targetCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } ReadOnlySpan memoryBlocksSpan = targetList._memoryBlocks; @@ -774,7 +774,7 @@ namespace Recyclable.Collections long targetCapacity = targetList._longCount + items.LongLength; if (targetList._capacity < targetCapacity) { - targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + targetList._capacity = RecyclableLongList.Helpers.Resize(ref targetList._memoryBlocks, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } ReadOnlySpan itemsSpan = items; diff --git a/Recyclable.Collections/RecyclableLongList.Helpers.cs b/Recyclable.Collections/RecyclableLongList.Helpers.cs index 6015f77..54e883e 100644 --- a/Recyclable.Collections/RecyclableLongList.Helpers.cs +++ b/Recyclable.Collections/RecyclableLongList.Helpers.cs @@ -142,9 +142,9 @@ namespace Recyclable.Collections /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static long Resize(RecyclableLongList list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + public static long Resize(ref T[][] memoryBlocks, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) { - int sourceBlockCount = list._memoryBlocks?.Length ?? 0; + int sourceBlockCount = memoryBlocks?.Length ?? 0; int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) != 0 ? 1 : 0)); int blockIndex; @@ -157,19 +157,19 @@ namespace Recyclable.Collections // Copy arrays from the old memory block for all arrays if (sourceBlockCount > 0) { - Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + Array.Copy(memoryBlocks!, newMemoryBlocks, sourceBlockCount); // We can now return the old memory block for all arrays itself if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) { - RecyclableArrayPool.ReturnShared(list._memoryBlocks!, true); + RecyclableArrayPool.ReturnShared(memoryBlocks!, true); } } - list._memoryBlocks = newMemoryBlocks; + memoryBlocks = newMemoryBlocks; } else { - newMemoryBlocks = list._memoryBlocks!; + newMemoryBlocks = memoryBlocks!; } // Allocate arrays for any new blocks diff --git a/Recyclable.Collections/RecyclableLongList.InsertRange.cs b/Recyclable.Collections/RecyclableLongList.InsertRange.cs index 25093fc..25b0be4 100644 --- a/Recyclable.Collections/RecyclableLongList.InsertRange.cs +++ b/Recyclable.Collections/RecyclableLongList.InsertRange.cs @@ -10,7 +10,7 @@ namespace Recyclable.Collections #pragma warning restore IDE1006 { [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static void InsertRange(this RecyclableLongList list, long startingItemIndex, T[] items) + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, Array items) { if (items.Length == 0) { @@ -19,13 +19,13 @@ namespace Recyclable.Collections T[][] oldMemoryBlocks = list._memoryBlocks; Span oldMemoryBlocksSpan = new(oldMemoryBlocks); - int newItemIndex = (int)(startingItemIndex & list._blockSizeMinus1), - sourceBlockIndex = (int)(startingItemIndex >> list._blockSizePow2BitShift); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + sourceBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; long newCapacity = list._longCount + items.LongLength; - list._capacity = (int)BitOperations.RoundUpToPowerOf2((ulong)newCapacity); + list._capacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); - // This is "requiredCapacity". We're reusing blockSize var to save on the total no. of variables. + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. int blockSize = (int)(list._capacity >> list._blockSizePow2BitShift) + 1; T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; @@ -54,7 +54,7 @@ namespace Recyclable.Collections } // Add new items to the list - ReadOnlySpan itemsSpan = items; + ReadOnlySpan itemsSpan = new((T[])items); Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); //sourceItemIndex = 0; @@ -89,7 +89,7 @@ namespace Recyclable.Collections } newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = new T[blockSize]; - } while (itemsSpan.Length >= blockSize) ; + } while (itemsSpan.Length >= blockSize); } } @@ -197,8 +197,2291 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static void InsertRange(this RecyclableLongList list, long startingItemIndex, ICollection items) + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, ICollection items) { + long itemsCount = items.Count; + if (itemsCount == 0) + { + return; + } + + T[] tempArray = itemsCount >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(BitOperations.IsPow2(itemsCount) ? (int)itemsCount : checked((int)BitOperations.RoundUpToPowerOf2((uint)itemsCount))) : new T[itemsCount]; + try + { + items.CopyTo(tempArray, 0); + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + sourceBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; + + long newCapacity = list._longCount + items.Count; + list._capacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = (int)(list._capacity >> list._blockSizePow2BitShift) + 1; + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, sourceBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + int newBlockIndex = sourceBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[sourceBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedCount = newItemIndex; + } + else + { + copiedCount = 0; + } + + // Add new items to the list + ReadOnlySpan itemsSpan = new(tempArray, 0, (int)itemsCount); + + itemsCount = list._longCount - startingItemIndex; + Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + //sourceItemIndex = 0; + if (itemsSpan.Length >= newBlockArraySpan.Length) + { + newItemIndex = 0; + if (usePool) + { + do + { + itemsSpan[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + itemsSpan = itemsSpan[newBlockArraySpan.Length..]; + newBlockIndex++; + if (itemsSpan.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = RecyclableArrayPool.RentShared(blockSize); + } while (itemsSpan.Length >= blockSize); + } + else + { + do + { + itemsSpan[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + itemsSpan = itemsSpan[newBlockArraySpan.Length..]; + newBlockIndex++; + if (itemsSpan.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = new T[blockSize]; + } while (itemsSpan.Length >= blockSize); + } + } + + // Copy the rest of the new items to the current block, if any + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(newBlockArraySpan); + newItemIndex += itemsSpan.Length; + } + else + { + newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + if (itemsCount > 0) + { + // Copy the rest of items from the list after the new items + int toCopy; + itemsSpan = new(oldMemoryBlocksSpan[sourceBlockIndex], (int)copiedCount, (int)(blockSize - copiedCount)); + copiedCount = 0; + newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (newBlockArraySpan.Length < toCopy) + { + toCopy = newBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(newBlockArraySpan); + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(newBlockArraySpan); + newBlockArraySpan = newBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.Length == 0 && sourceBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = oldMemoryBlocksSpan[sourceBlockIndex]; + } + + copiedCount += toCopy; + if (newBlockArraySpan.Length == 0 && copiedCount < itemsCount) + { + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCapacity & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCapacity & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + bool needsClearing = RecyclableLongList._needsClearing; + while (sourceBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[sourceBlockIndex++], needsClearing); + } + } + + // Allocate unused memory block arrays up to the capacity + newBlockIndex++; + if (usePool) + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = RecyclableArrayPool.RentShared(blockSize); + } + } + else + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = new T[blockSize]; + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + finally + { + RecyclableArrayPool.ReturnShared(tempArray, RecyclableLongList._needsClearing); + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, ICollection items) + { + long itemsCount = items.Count; + if (itemsCount == 0) + { + return; + } + + T[] tempArray = itemsCount >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(BitOperations.IsPow2(itemsCount) ? (int)itemsCount : checked((int)BitOperations.RoundUpToPowerOf2((uint)itemsCount))) : new T[itemsCount]; + try + { + items.CopyTo(tempArray, 0); + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + sourceBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; + + long newCapacity = list._longCount + items.Count; + list._capacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = (int)(list._capacity >> list._blockSizePow2BitShift) + 1; + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, sourceBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + int newBlockIndex = sourceBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[sourceBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedCount = newItemIndex; + } + else + { + copiedCount = 0; + } + + // Add new items to the list + ReadOnlySpan itemsSpan = new(tempArray, 0, (int)itemsCount); + + itemsCount = list._longCount - startingItemIndex; + Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + //sourceItemIndex = 0; + if (itemsSpan.Length >= newBlockArraySpan.Length) + { + newItemIndex = 0; + if (usePool) + { + do + { + itemsSpan[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + itemsSpan = itemsSpan[newBlockArraySpan.Length..]; + newBlockIndex++; + if (itemsSpan.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = RecyclableArrayPool.RentShared(blockSize); + } while (itemsSpan.Length >= blockSize); + } + else + { + do + { + itemsSpan[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + itemsSpan = itemsSpan[newBlockArraySpan.Length..]; + newBlockIndex++; + if (itemsSpan.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = new T[blockSize]; + } while (itemsSpan.Length >= blockSize); + } + } + + // Copy the rest of the new items to the current block, if any + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(newBlockArraySpan); + newItemIndex += itemsSpan.Length; + } + else + { + newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + if (itemsCount > 0) + { + // Copy the rest of items from the list after the new items + int toCopy; + itemsSpan = new(oldMemoryBlocksSpan[sourceBlockIndex], (int)copiedCount, (int)(blockSize - copiedCount)); + copiedCount = 0; + newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (newBlockArraySpan.Length < toCopy) + { + toCopy = newBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(newBlockArraySpan); + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(newBlockArraySpan); + newBlockArraySpan = newBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.Length == 0 && sourceBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = oldMemoryBlocksSpan[sourceBlockIndex]; + } + + copiedCount += toCopy; + if (newBlockArraySpan.Length == 0 && copiedCount < itemsCount) + { + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCapacity & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCapacity & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + bool needsClearing = RecyclableLongList._needsClearing; + while (sourceBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[sourceBlockIndex++], needsClearing); + } + } + + // Allocate unused memory block arrays up to the capacity + newBlockIndex++; + if (usePool) + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = RecyclableArrayPool.RentShared(blockSize); + } + } + else + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = new T[blockSize]; + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + finally + { + RecyclableArrayPool.ReturnShared(tempArray, RecyclableLongList._needsClearing); + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, IEnumerable items) + { + IEnumerator enumerator = items.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + newBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; + + long newCapacity = Math.Max(list._longCount + 1, RecyclableDefaults.BlockSize); + newCapacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = Math.Max((int)(newCapacity >> list._blockSizePow2BitShift), 1); + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, newBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + int oldBlockIndex = newBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedFromStartCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[oldBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedFromStartCount = newItemIndex; + } + else + { + copiedFromStartCount = 0; + } + + // Add new items to the list + long newCount = list._longCount, + available = newCapacity - newCount, + i; + + Span newBlockSpan; + while (true) + { + if (newCount + blockSize > newCapacity) + { + newCapacity = RecyclableLongList.Helpers.Resize(ref newMemoryBlocks, blockSize, list._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(newCapacity + blockSize)))); + newMemoryBlocksSpan = newMemoryBlocks; + available = newCapacity - newCount; + } + + newBlockSpan = newMemoryBlocksSpan[newBlockIndex]; + for (i = 1; i <= available; i++) + { + newBlockSpan[newItemIndex++] = (T)enumerator.Current; + + if (!enumerator.MoveNext()) + { + newCount += i; + if (newItemIndex >= blockSize) + { + newBlockIndex++; + newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + goto postIteration; + } + + if (newItemIndex == blockSize) + { + newBlockIndex++; + newItemIndex = 0; + if (i == available) + { + break; + } + + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + + newCount += available; + } + + // Copy the rest of the new items to the current block, if any + postIteration: + long toCopy = list._longCount - startingItemIndex; + if (toCopy > 0) + { + // Copy the rest of items from the list after the new items + newCapacity = RecyclableLongList.Helpers.Resize(ref newMemoryBlocks, blockSize, list._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCount))); + newMemoryBlocksSpan = newMemoryBlocks; + + Span itemsSpan = new(oldMemoryBlocksSpan[oldBlockIndex], (int)copiedFromStartCount, (int)(blockSize - copiedFromStartCount)); + Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + copiedFromStartCount = 0; + + while (copiedFromStartCount < toCopy) + { + // "blockSize" will now become "toCopy". This is to save on 1 less variable. + blockSize = (int)Math.Min(toCopy - copiedFromStartCount, itemsSpan.Length); + if (newBlockArraySpan.Length < blockSize) + { + blockSize = newBlockArraySpan.Length; + itemsSpan[..blockSize].CopyTo(newBlockArraySpan); + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex]; + itemsSpan = itemsSpan[blockSize..]; + copiedFromStartCount += blockSize; + } + else + { + itemsSpan[..blockSize].CopyTo(newBlockArraySpan); + newBlockArraySpan = newBlockArraySpan[blockSize..]; + itemsSpan = itemsSpan[blockSize..]; + if (itemsSpan.Length == 0 && oldBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + oldBlockIndex++; + itemsSpan = oldMemoryBlocksSpan[oldBlockIndex]; + } + + copiedFromStartCount += blockSize; + if (newBlockArraySpan.Length == 0 && copiedFromStartCount < toCopy) + { + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCount & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCount & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCount; + list._capacity = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + // We're reusing variable to save on 1 less variable. + usePool = RecyclableLongList._needsClearing; + while (oldBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[oldBlockIndex++], usePool); + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + + public static void InsertRange(this RecyclableLongList targetList, int startingItemIndex, IEnumerable items) + { + switch (items) + { + case RecyclableList sourceRecyclableList: + targetList.InsertRange(startingItemIndex, sourceRecyclableList); + return; + + case RecyclableLongList sourceRecyclableLongList: + targetList.InsertRange(startingItemIndex, sourceRecyclableLongList); + return; + + case T[] sourceArray: + targetList.InsertRange(startingItemIndex, sourceArray); + return; + + case Array sourceArrayWithObjects: + targetList.InsertRange(startingItemIndex, sourceArrayWithObjects); + return; + + case List sourceList: + targetList.InsertRange(startingItemIndex, sourceList); + return; + + case ICollection sourceICollection: + targetList.InsertRange(startingItemIndex, sourceICollection); + return; + + case ICollection sourceICollectionWithObjects: + targetList.InsertRange(startingItemIndex, sourceICollectionWithObjects); + return; + + case IReadOnlyList sourceIReadOnlyList: + targetList.InsertRange(startingItemIndex, sourceIReadOnlyList); + return; + + default: + targetList.InsertRangeEnumerated(startingItemIndex, items); + return; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, IList items) + { + long itemsCount = items.Count; + if (itemsCount == 0) + { + return; + } + + T[] tempArray = itemsCount >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(BitOperations.IsPow2(itemsCount) ? (int)itemsCount : checked((int)BitOperations.RoundUpToPowerOf2((uint)itemsCount))) : new T[itemsCount]; + try + { + items.CopyTo(tempArray, 0); + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + sourceBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; + + long newCapacity = list._longCount + items.Count; + list._capacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = (int)(list._capacity >> list._blockSizePow2BitShift) + 1; + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, sourceBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + int newBlockIndex = sourceBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[sourceBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedCount = newItemIndex; + } + else + { + copiedCount = 0; + } + + // Add new items to the list + ReadOnlySpan itemsSpan = new(tempArray, 0, (int)itemsCount); + + itemsCount = list._longCount - startingItemIndex; + Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + //sourceItemIndex = 0; + if (itemsSpan.Length >= newBlockArraySpan.Length) + { + newItemIndex = 0; + if (usePool) + { + do + { + itemsSpan[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + itemsSpan = itemsSpan[newBlockArraySpan.Length..]; + newBlockIndex++; + if (itemsSpan.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = RecyclableArrayPool.RentShared(blockSize); + } while (itemsSpan.Length >= blockSize); + } + else + { + do + { + itemsSpan[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + itemsSpan = itemsSpan[newBlockArraySpan.Length..]; + newBlockIndex++; + if (itemsSpan.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = new T[blockSize]; + } while (itemsSpan.Length >= blockSize); + } + } + + // Copy the rest of the new items to the current block, if any + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(newBlockArraySpan); + newItemIndex += itemsSpan.Length; + } + else + { + newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + if (itemsCount > 0) + { + // Copy the rest of items from the list after the new items + int toCopy; + itemsSpan = new(oldMemoryBlocksSpan[sourceBlockIndex], (int)copiedCount, (int)(blockSize - copiedCount)); + copiedCount = 0; + newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (newBlockArraySpan.Length < toCopy) + { + toCopy = newBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(newBlockArraySpan); + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(newBlockArraySpan); + newBlockArraySpan = newBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.Length == 0 && sourceBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = oldMemoryBlocksSpan[sourceBlockIndex]; + } + + copiedCount += toCopy; + if (newBlockArraySpan.Length == 0 && copiedCount < itemsCount) + { + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCapacity & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCapacity & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + bool needsClearing = RecyclableLongList._needsClearing; + while (sourceBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[sourceBlockIndex++], needsClearing); + } + } + + // Allocate unused memory block arrays up to the capacity + newBlockIndex++; + if (usePool) + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = RecyclableArrayPool.RentShared(blockSize); + } + } + else + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = new T[blockSize]; + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + finally + { + RecyclableArrayPool.ReturnShared(tempArray, RecyclableLongList._needsClearing); + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, IReadOnlyList items) + { + if (items.Count == 0) + { + return; + } + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + newBlockIndex = startingItemIndex >> list._blockSizePow2BitShift, + oldBlockIndex = newBlockIndex + (newItemIndex + 1 == 0 ? 1 : 0); + + long newCapacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)(list._longCount + items.Count))); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = Math.Max((int)(newCapacity >> list._blockSizePow2BitShift), 1); + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, newBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedFromStartCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[newBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedFromStartCount = newItemIndex; + } + else + { + copiedFromStartCount = 0; + } + + // Add new items to the list + int sourceBlockIndex = newBlockIndex; + long additionalCount = items.Count; + + int i = 0; + + Span newBlockSpan = newMemoryBlocksSpan[newBlockIndex]; + while (i < additionalCount) + { + newBlockSpan[newItemIndex++] = items[i++]; + if (i == additionalCount) + { + if (newItemIndex >= blockSize) + { + newBlockIndex++; + newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + break; + } + + if (newItemIndex == blockSize) + { + newBlockIndex++; + newItemIndex = 0; + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + + long itemsCount = list._longCount - startingItemIndex; + if (itemsCount > 0) + { + // Copy the rest of items from the list after the new items + int toCopy; + Span itemsSpan = new(oldMemoryBlocksSpan[sourceBlockIndex], (int)copiedFromStartCount, (int)(blockSize - copiedFromStartCount)); + copiedFromStartCount = 0; + newBlockSpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedFromStartCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedFromStartCount, itemsSpan.Length); + if (newBlockSpan.Length < toCopy) + { + toCopy = newBlockSpan.Length; + itemsSpan[..toCopy].CopyTo(newBlockSpan); + newBlockIndex++; + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + itemsSpan = itemsSpan[toCopy..]; + copiedFromStartCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(newBlockSpan); + newBlockSpan = newBlockSpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.Length == 0 && sourceBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = oldMemoryBlocksSpan[sourceBlockIndex]; + } + + copiedFromStartCount += toCopy; + if (newBlockSpan.Length == 0 && copiedFromStartCount < itemsCount) + { + newBlockIndex++; + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + additionalCount = list._longCount + items.Count; + if ((additionalCount & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(additionalCount & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = additionalCount; + list._capacity = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + // We're reusing variable to save on 1 less variable. + usePool = RecyclableLongList._needsClearing; + while (oldBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[oldBlockIndex++], usePool); + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, List items) + { + int sourceItemsCount = items.Count; + if (sourceItemsCount == 0) + { + return; + } + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + sourceBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; + + long newCapacity = list._longCount + sourceItemsCount; + list._capacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = Math.Max((int)(list._capacity >> list._blockSizePow2BitShift), 1); + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + if (sourceBlockIndex > 0) + { + Array.Copy(oldMemoryBlocks, newMemoryBlocks, sourceBlockIndex); + } + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + int newBlockIndex = sourceBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[sourceBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedCount = newItemIndex; + } + else + { + copiedCount = 0; + } + + // Add new items to the list + //Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + T[] newBlockArray = newMemoryBlocksSpan[newBlockIndex]; + int newBlockArrayLength = blockSize - newItemIndex; + int sourceItemIndex = 0; + if (sourceItemsCount >= newBlockArrayLength + sourceItemIndex) + { + if (usePool) + { + do + { + items.CopyTo(sourceItemIndex, newBlockArray, newItemIndex, newBlockArrayLength); + sourceItemIndex += newBlockArrayLength; + newBlockIndex++; + if (newItemIndex != 0) + { + newItemIndex = 0; + newBlockArrayLength = blockSize; + } + + if (sourceItemIndex == sourceItemsCount) + { + break; + } + + newBlockArray = newMemoryBlocksSpan[newBlockIndex] = RecyclableArrayPool.RentShared(blockSize); + } while (sourceItemsCount >= blockSize + sourceItemIndex); + } + else + { + do + { + items.CopyTo(sourceItemIndex, newBlockArray, newItemIndex, newBlockArrayLength); + sourceItemIndex += newBlockArrayLength; + newBlockIndex++; + if (newItemIndex != 0) + { + newItemIndex = 0; + newBlockArrayLength = blockSize; + } + + if (sourceItemIndex == sourceItemsCount) + { + break; + } + + newBlockArray = newMemoryBlocksSpan[newBlockIndex] = new T[blockSize]; + } while (sourceItemsCount >= blockSize + sourceItemIndex); + } + } + + // Copy the rest of the new items to the current block, if any + if (sourceItemsCount > sourceItemIndex) + { + items.CopyTo(sourceItemIndex, newBlockArray, newItemIndex, sourceItemsCount - sourceItemIndex); + newItemIndex += sourceItemsCount - sourceItemIndex; + } + else + { + //newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + long itemsCount = list._longCount - startingItemIndex; + if (itemsCount > 0) + { + // Copy the rest of items from the list after the new items + int toCopy; + Span itemsSpan = new(oldMemoryBlocksSpan[sourceBlockIndex], (int)copiedCount, (int)(blockSize - copiedCount)); + copiedCount = 0; + var newBlockSpan = new Span(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (newBlockSpan.Length < toCopy) + { + toCopy = newBlockSpan.Length; + itemsSpan[..toCopy].CopyTo(newBlockSpan); + newBlockIndex++; + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(newBlockSpan); + newBlockSpan = newBlockSpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.Length == 0 && sourceBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = oldMemoryBlocksSpan[sourceBlockIndex]; + } + + copiedCount += toCopy; + if (newBlockSpan.Length == 0 && copiedCount < itemsCount) + { + newBlockIndex++; + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCapacity & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCapacity & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + bool needsClearing = RecyclableLongList._needsClearing; + while (sourceBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[sourceBlockIndex++], needsClearing); + } + } + + // Allocate unused memory block arrays up to the capacity + newBlockIndex++; + if (usePool) + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = RecyclableArrayPool.RentShared(blockSize); + } + } + else + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = new T[blockSize]; + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, ReadOnlySpan items) + { + if (items.Length == 0) + { + return; + } + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + sourceBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; + + long newCapacity = list._longCount + items.Length; + list._capacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = (int)(list._capacity >> list._blockSizePow2BitShift) + 1; + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, sourceBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + int newBlockIndex = sourceBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[sourceBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedCount = newItemIndex; + } + else + { + copiedCount = 0; + } + + // Add new items to the list + Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + //sourceItemIndex = 0; + if (items.Length >= newBlockArraySpan.Length) + { + newItemIndex = 0; + if (usePool) + { + do + { + items[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + items = items[newBlockArraySpan.Length..]; + newBlockIndex++; + if (items.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = RecyclableArrayPool.RentShared(blockSize); + } while (items.Length >= blockSize); + } + else + { + do + { + items[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + items = items[newBlockArraySpan.Length..]; + newBlockIndex++; + if (items.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = new T[blockSize]; + } while (items.Length >= blockSize); + } + } + + // Copy the rest of the new items to the current block, if any + if (items.Length > 0) + { + items.CopyTo(newBlockArraySpan); + newItemIndex += items.Length; + } + else + { + newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + long itemsCount = list._longCount - startingItemIndex; + if (itemsCount > 0) + { + // Copy the rest of items from the list after the new items + int toCopy; + items = new(oldMemoryBlocksSpan[sourceBlockIndex], (int)copiedCount, (int)(blockSize - copiedCount)); + copiedCount = 0; + newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, items.Length); + if (newBlockArraySpan.Length < toCopy) + { + toCopy = newBlockArraySpan.Length; + items[..toCopy].CopyTo(newBlockArraySpan); + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + items = items[toCopy..]; + copiedCount += toCopy; + } + else + { + items[..toCopy].CopyTo(newBlockArraySpan); + newBlockArraySpan = newBlockArraySpan[toCopy..]; + items = items[toCopy..]; + if (items.Length == 0 && sourceBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + items = oldMemoryBlocksSpan[sourceBlockIndex]; + } + + copiedCount += toCopy; + if (newBlockArraySpan.Length == 0 && copiedCount < itemsCount) + { + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCapacity & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCapacity & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + bool needsClearing = RecyclableLongList._needsClearing; + while (sourceBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[sourceBlockIndex++], needsClearing); + } + } + + // Allocate unused memory block arrays up to the capacity + newBlockIndex++; + if (usePool) + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = RecyclableArrayPool.RentShared(blockSize); + } + } + else + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = new T[blockSize]; + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, RecyclableList items) + { + if (items._count == 0) + { + return; + } + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + sourceBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; + + long newCapacity = list._longCount + items._count; + list._capacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = (int)(list._capacity >> list._blockSizePow2BitShift) + 1; + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, sourceBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + int newBlockIndex = sourceBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[sourceBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedCount = newItemIndex; + } + else + { + copiedCount = 0; + } + + // Add new items to the list + ReadOnlySpan itemsSpan = new(items._memoryBlock, 0, items._count); + Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + //sourceItemIndex = 0; + if (itemsSpan.Length >= newBlockArraySpan.Length) + { + newItemIndex = 0; + if (usePool) + { + do + { + itemsSpan[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + itemsSpan = itemsSpan[newBlockArraySpan.Length..]; + newBlockIndex++; + if (itemsSpan.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = RecyclableArrayPool.RentShared(blockSize); + } while (itemsSpan.Length >= blockSize); + } + else + { + do + { + itemsSpan[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + itemsSpan = itemsSpan[newBlockArraySpan.Length..]; + newBlockIndex++; + if (itemsSpan.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = new T[blockSize]; + } while (itemsSpan.Length >= blockSize); + } + } + + // Copy the rest of the new items to the current block, if any + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(newBlockArraySpan); + newItemIndex += itemsSpan.Length; + } + else + { + newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + long itemsCount = list._longCount - startingItemIndex; + if (itemsCount > 0) + { + // Copy the rest of items from the list after the new items + int toCopy; + itemsSpan = new(oldMemoryBlocksSpan[sourceBlockIndex], (int)copiedCount, (int)(blockSize - copiedCount)); + copiedCount = 0; + newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (newBlockArraySpan.Length < toCopy) + { + toCopy = newBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(newBlockArraySpan); + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(newBlockArraySpan); + newBlockArraySpan = newBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.Length == 0 && sourceBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = oldMemoryBlocksSpan[sourceBlockIndex]; + } + + copiedCount += toCopy; + if (newBlockArraySpan.Length == 0 && copiedCount < itemsCount) + { + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCapacity & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCapacity & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + bool needsClearing = RecyclableLongList._needsClearing; + while (sourceBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[sourceBlockIndex++], needsClearing); + } + } + + // Allocate unused memory block arrays up to the capacity + newBlockIndex++; + if (usePool) + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = RecyclableArrayPool.RentShared(blockSize); + } + } + else + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = new T[blockSize]; + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, RecyclableLongList items) + { + if (items._longCount == 0) + { + return; + } + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + sourceBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; + + long newCapacity = list._longCount + items._longCount; + list._capacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = (int)(list._capacity >> list._blockSizePow2BitShift) + 1; + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, sourceBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + int newBlockIndex = sourceBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[sourceBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedCount = newItemIndex; + } + else + { + copiedCount = 0; + } + + // Add new items to the list + int sourceBlockIndex2 = 0, + sourceBlockSize = items._blockSize, + toCopy; + + long copiedCount2 = 0, + itemsCount2 = items._longCount; + + ReadOnlySpan sourceMemoryBlocksSpan = items._memoryBlocks; + ReadOnlySpan itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex2], 0, (int)Math.Min(sourceBlockSize, itemsCount2)); + Span newBlockSpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedCount2 < itemsCount2) + { + toCopy = (int)Math.Min(itemsCount2 - copiedCount2, itemsSpan.Length); + if (newBlockSpan.Length < toCopy) + { + toCopy = newBlockSpan.Length; + itemsSpan[..toCopy].CopyTo(newBlockSpan); + newBlockIndex++; + newItemIndex = 0; + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + itemsSpan = itemsSpan[toCopy..]; + copiedCount2 += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(newBlockSpan); + itemsSpan = itemsSpan[toCopy..]; + copiedCount2 += toCopy; + newItemIndex += toCopy; + if (itemsSpan.Length == 0 && sourceBlockIndex2 + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex2++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex2], 0, sourceBlockSize); + } + + if (newItemIndex == blockSize) + { + newBlockIndex++; + newItemIndex = 0; + if (copiedCount2 < itemsCount2) + { + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + else + { + newBlockSpan = newBlockSpan[toCopy..]; + } + } + } + + // Copy the rest of the new items to the current block, if any + //if (itemsSpan.Length > 0) + //{ + // itemsSpan.CopyTo(newBlockSpan); + // newItemIndex += itemsSpan.Length; + //} + //else + //{ + // newItemIndex = 0; + // newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + //} + + long itemsCount = list._longCount - startingItemIndex; + if (itemsCount > 0) + { + if (newMemoryBlocksSpan[newBlockIndex] == null) + { + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + // Copy the rest of items from the list after the new items + itemsSpan = new(oldMemoryBlocksSpan[sourceBlockIndex], (int)copiedCount, (int)(blockSize - copiedCount)); + copiedCount = 0; + newBlockSpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (newBlockSpan.Length < toCopy) + { + toCopy = newBlockSpan.Length; + itemsSpan[..toCopy].CopyTo(newBlockSpan); + newBlockIndex++; + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(newBlockSpan); + newBlockSpan = newBlockSpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.Length == 0 && sourceBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = oldMemoryBlocksSpan[sourceBlockIndex]; + } + + copiedCount += toCopy; + if (newBlockSpan.Length == 0 && copiedCount < itemsCount) + { + newBlockIndex++; + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCapacity & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCapacity & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + bool needsClearing = RecyclableLongList._needsClearing; + while (sourceBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[sourceBlockIndex++], needsClearing); + } + } + + // Allocate unused memory block arrays up to the capacity + newBlockIndex++; + if (usePool) + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = RecyclableArrayPool.RentShared(blockSize); + } + } + else + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = new T[blockSize]; + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, Span items) + { + if (items.Length == 0) + { + return; + } + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + sourceBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; + + long newCapacity = list._longCount + items.Length; + list._capacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = (int)(list._capacity >> list._blockSizePow2BitShift) + 1; + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, sourceBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + int newBlockIndex = sourceBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[sourceBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedCount = newItemIndex; + } + else + { + copiedCount = 0; + } + + // Add new items to the list + Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + //sourceItemIndex = 0; + if (items.Length >= newBlockArraySpan.Length) + { + newItemIndex = 0; + if (usePool) + { + do + { + items[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + items = items[newBlockArraySpan.Length..]; + newBlockIndex++; + if (items.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = RecyclableArrayPool.RentShared(blockSize); + } while (items.Length >= blockSize); + } + else + { + do + { + items[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + items = items[newBlockArraySpan.Length..]; + newBlockIndex++; + if (items.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = new T[blockSize]; + } while (items.Length >= blockSize); + } + } + + // Copy the rest of the new items to the current block, if any + if (items.Length > 0) + { + items.CopyTo(newBlockArraySpan); + newItemIndex += items.Length; + } + else + { + newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + long itemsCount = list._longCount - startingItemIndex; + if (itemsCount > 0) + { + // Copy the rest of items from the list after the new items + int toCopy; + items = new(oldMemoryBlocksSpan[sourceBlockIndex], (int)copiedCount, (int)(blockSize - copiedCount)); + copiedCount = 0; + newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, items.Length); + if (newBlockArraySpan.Length < toCopy) + { + toCopy = newBlockArraySpan.Length; + items[..toCopy].CopyTo(newBlockArraySpan); + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + items = items[toCopy..]; + copiedCount += toCopy; + } + else + { + items[..toCopy].CopyTo(newBlockArraySpan); + newBlockArraySpan = newBlockArraySpan[toCopy..]; + items = items[toCopy..]; + if (items.Length == 0 && sourceBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + items = oldMemoryBlocksSpan[sourceBlockIndex]; + } + + copiedCount += toCopy; + if (newBlockArraySpan.Length == 0 && copiedCount < itemsCount) + { + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCapacity & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCapacity & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + bool needsClearing = RecyclableLongList._needsClearing; + while (sourceBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[sourceBlockIndex++], needsClearing); + } + } + + // Allocate unused memory block arrays up to the capacity + newBlockIndex++; + if (usePool) + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = RecyclableArrayPool.RentShared(blockSize); + } + } + else + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = new T[blockSize]; + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRange(this RecyclableLongList list, int startingItemIndex, T[] items) + { + if (items.Length == 0) + { + return; + } + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + sourceBlockIndex = startingItemIndex >> list._blockSizePow2BitShift; + + long newCapacity = list._longCount + items.LongLength; + list._capacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCapacity)); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = (int)(list._capacity >> list._blockSizePow2BitShift) + 1; + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, sourceBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + int newBlockIndex = sourceBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[sourceBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedCount = newItemIndex; + } + else + { + copiedCount = 0; + } + + // Add new items to the list + ReadOnlySpan itemsSpan = items; + Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + //sourceItemIndex = 0; + if (itemsSpan.Length >= newBlockArraySpan.Length) + { + newItemIndex = 0; + if (usePool) + { + do + { + itemsSpan[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + itemsSpan = itemsSpan[newBlockArraySpan.Length..]; + newBlockIndex++; + if (itemsSpan.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = RecyclableArrayPool.RentShared(blockSize); + } while (itemsSpan.Length >= blockSize); + } + else + { + do + { + itemsSpan[..newBlockArraySpan.Length].CopyTo(newBlockArraySpan); + itemsSpan = itemsSpan[newBlockArraySpan.Length..]; + newBlockIndex++; + if (itemsSpan.Length == 0) + { + break; + } + + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = new T[blockSize]; + } while (itemsSpan.Length >= blockSize); + } + } + + // Copy the rest of the new items to the current block, if any + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(newBlockArraySpan); + newItemIndex += itemsSpan.Length; + } + else + { + newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + long itemsCount = list._longCount - startingItemIndex; + if (itemsCount > 0) + { + // Copy the rest of items from the list after the new items + int toCopy; + itemsSpan = new(oldMemoryBlocksSpan[sourceBlockIndex], (int)copiedCount, (int)(blockSize - copiedCount)); + copiedCount = 0; + newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (newBlockArraySpan.Length < toCopy) + { + toCopy = newBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(newBlockArraySpan); + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(newBlockArraySpan); + newBlockArraySpan = newBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.Length == 0 && sourceBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = oldMemoryBlocksSpan[sourceBlockIndex]; + } + + copiedCount += toCopy; + if (newBlockArraySpan.Length == 0 && copiedCount < itemsCount) + { + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCapacity & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCapacity & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + bool needsClearing = RecyclableLongList._needsClearing; + while (sourceBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[sourceBlockIndex++], needsClearing); + } + } + + // Allocate unused memory block arrays up to the capacity + newBlockIndex++; + if (usePool) + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = RecyclableArrayPool.RentShared(blockSize); + } + } + else + { + while (newBlockIndex < newMemoryBlocksSpan.Length) + { + newMemoryBlocksSpan[newBlockIndex++] = new T[blockSize]; + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InsertRangeEnumerated(this RecyclableLongList list, int startingItemIndex, IEnumerable items) + { + using IEnumerator enumerator = items.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + T[][] oldMemoryBlocks = list._memoryBlocks; + Span oldMemoryBlocksSpan = new(oldMemoryBlocks); + int newItemIndex = startingItemIndex & list._blockSizeMinus1, + newBlockIndex = startingItemIndex >> list._blockSizePow2BitShift, + oldBlockIndex = newBlockIndex;// + (newItemIndex + 1 == 0 ? 1 : 0); + + long newCapacity = checked((long)BitOperations.RoundUpToPowerOf2((ulong)Math.Max(list._longCount + 1, RecyclableDefaults.BlockSize))); + + // This is "requiredBlockCount". We're reusing blockSize var to save on the total no. of variables. + int blockSize = Math.Max((int)(newCapacity >> list._blockSizePow2BitShift), 1); + T[][] newMemoryBlocks = blockSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize][]; + + blockSize = list._blockSize; + + // Copy references to memory block arrays which stay as-is + Array.Copy(oldMemoryBlocks, newMemoryBlocks, newBlockIndex); + + // Prepare the first block for items in the new memory blocks + bool usePool = blockSize >= RecyclableDefaults.MinPooledArrayLength; + Span newMemoryBlocksSpan = new(newMemoryBlocks); + + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + + // Copy the starting items from the list in the current block, but to a new block + long copiedFromStartCount; + if (newItemIndex > 0) + { + Array.Copy(oldMemoryBlocksSpan[oldBlockIndex], newMemoryBlocks[newBlockIndex], newItemIndex); + copiedFromStartCount = newItemIndex; + } + else + { + copiedFromStartCount = 0; + } + + // Add new items to the list + long newCount = list._longCount, + available = newCapacity - newCount, + i; + + Span newBlockSpan; + while (true) + { + if (newCount + blockSize > newCapacity) + { + newCapacity = RecyclableLongList.Helpers.Resize(ref newMemoryBlocks, blockSize, list._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(newCapacity + blockSize)))); + newMemoryBlocksSpan = newMemoryBlocks; + available = newCapacity - newCount; + } + + newBlockSpan = newMemoryBlocksSpan[newBlockIndex]; + for (i = 1; i <= available; i++) + { + newBlockSpan[newItemIndex++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + newCount += i; + if (newItemIndex >= blockSize) + { + newBlockIndex++; + newItemIndex = 0; + newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + + goto postIteration; + } + + if (newItemIndex == blockSize) + { + newBlockIndex++; + newItemIndex = 0; + if (i == available) + { + break; + } + + newBlockSpan = newMemoryBlocksSpan[newBlockIndex] = usePool ? RecyclableArrayPool.RentShared(blockSize) : new T[blockSize]; + } + } + + newCount += available; + } + + // Copy the rest of the new items to the current block, if any + postIteration: + long toCopy = list._longCount - startingItemIndex; + if (toCopy > 0) + { + // Copy the rest of items from the list after the new items + newCapacity = RecyclableLongList.Helpers.Resize(ref newMemoryBlocks, blockSize, list._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)newCount))); + newMemoryBlocksSpan = newMemoryBlocks; + + Span itemsSpan = new(oldMemoryBlocksSpan[oldBlockIndex], (int)copiedFromStartCount, (int)(blockSize - copiedFromStartCount)); + Span newBlockArraySpan = new(newMemoryBlocksSpan[newBlockIndex], newItemIndex, blockSize - newItemIndex); + copiedFromStartCount = 0; + + while (copiedFromStartCount < toCopy) + { + // "blockSize" will now become "toCopy". This is to save on 1 less variable. + blockSize = (int)Math.Min(toCopy - copiedFromStartCount, itemsSpan.Length); + if (newBlockArraySpan.Length < blockSize) + { + blockSize = newBlockArraySpan.Length; + itemsSpan[..blockSize].CopyTo(newBlockArraySpan); + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex]; + itemsSpan = itemsSpan[blockSize..]; + copiedFromStartCount += blockSize; + } + else + { + itemsSpan[..blockSize].CopyTo(newBlockArraySpan); + newBlockArraySpan = newBlockArraySpan[blockSize..]; + itemsSpan = itemsSpan[blockSize..]; + if (itemsSpan.Length == 0 && oldBlockIndex + 1 < oldMemoryBlocksSpan.Length) + { + oldBlockIndex++; + itemsSpan = oldMemoryBlocksSpan[oldBlockIndex]; + } + + copiedFromStartCount += blockSize; + if (newBlockArraySpan.Length == 0 && copiedFromStartCount < toCopy) + { + newBlockIndex++; + newBlockArraySpan = newMemoryBlocksSpan[newBlockIndex]; + } + } + } + } + + list._lastBlockWithData = newBlockIndex; + if ((newCount & list._blockSizeMinus1) == 0) + { + list._nextItemIndex = 0; + list._nextItemBlockIndex = newBlockIndex + 1; + } + else + { + list._nextItemIndex = (int)(newCount & list._blockSizeMinus1); + list._nextItemBlockIndex = newBlockIndex; + } + + list._memoryBlocks = newMemoryBlocks; + list._longCount = newCount; + list._capacity = newCapacity; + list._version++; + + // Return unused memory block arrays to the pool + if (usePool) + { + // We're reusing variable to save on 1 less variable. + usePool = RecyclableLongList._needsClearing; + while (oldBlockIndex < oldMemoryBlocksSpan.Length) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocksSpan[oldBlockIndex++], usePool); + } + } + + if (oldMemoryBlocksSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(oldMemoryBlocks, true); + } } } } diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index cd89622..fb9c0f8 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -40,7 +40,7 @@ namespace Recyclable.Collections _version++; if (_capacity != value) { - _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)value))); + _capacity = Helpers.Resize(ref _memoryBlocks, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)value))); _version++; } } @@ -62,7 +62,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)initialCapacity))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)initialCapacity))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; } @@ -75,7 +75,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.LongLength))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.LongLength))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -90,7 +90,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.LongLength))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.LongLength))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -105,7 +105,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -120,7 +120,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Length))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Length))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -135,7 +135,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -150,7 +150,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -165,7 +165,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Length))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Length))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -180,7 +180,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -195,7 +195,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -210,7 +210,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -225,7 +225,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)initialCapacity))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)initialCapacity))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -240,7 +240,7 @@ namespace Recyclable.Collections } _blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)initialCapacity))); + _capacity = Helpers.Resize(ref _memoryBlocks, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)initialCapacity))); _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; @@ -274,7 +274,7 @@ namespace Recyclable.Collections { if (_capacity < _longCount + 1) { - _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(_longCount + 1)))); + _capacity = Helpers.Resize(ref _memoryBlocks, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(_longCount + 1)))); } _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; @@ -505,7 +505,7 @@ namespace Recyclable.Collections if (_capacity < ++_longCount) { - _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)_longCount))); + _capacity = Helpers.Resize(ref _memoryBlocks, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)_longCount))); } if (index + 1 < _longCount) @@ -544,7 +544,7 @@ namespace Recyclable.Collections if (_capacity < ++_longCount) { - _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)_longCount))); + _capacity = Helpers.Resize(ref _memoryBlocks, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)_longCount))); } if (index + 1 < _longCount) diff --git a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj index 9851ec6..fd60ec3 100644 --- a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj +++ b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj @@ -16,8 +16,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all diff --git a/Recyclable.CollectionsTests/RecyclableLongList.Helpers.Tests.cs b/Recyclable.CollectionsTests/RecyclableLongList.Helpers.Tests.cs deleted file mode 100644 index 1626df2..0000000 --- a/Recyclable.CollectionsTests/RecyclableLongList.Helpers.Tests.cs +++ /dev/null @@ -1,100 +0,0 @@ -using FluentAssertions; -using Recyclable.Collections; -using Recyclable.Collections.TestData.xUnit; -using System.Collections; - -namespace Recyclable.CollectionsTests -{ - - public class RecyclableLongListHelperTests - { - [Theory] - [ClassData(typeof(SourceDataWithBlockSizeWithItemIndexWithRangeTheoryData))] - public void MakeRoomAndSetShouldMoveItems(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in (long ItemIndex, long RangedItemsCount)[] itemRanges) - { - // Prepare - var expectedData = testData.Reverse().ToArray(); - - foreach ((var itemIndex, var rangedItemsCount) in itemRanges) - { - using var list = new RecyclableLongList(testData.Reverse(), minBlockSize, itemsCount); - var expected = expectedData.ToList(); - var rangedTestData = expected.GetRange((int)itemIndex, (int)rangedItemsCount).ToArray(); - expected.InsertRange((int)itemIndex, rangedTestData); - - if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) - { - list.InsertRange((int)itemIndex, rangedTestData); - } - else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) - { - return; - //list.InsertRange((int)itemIndex, (ICollection)testData); - } - else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) - { - return; - //list.InsertRange((int)itemIndex, (ICollection)testData); - } - else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) - { - return; - //list.InsertRange((int)itemIndex, (IEnumerable)testData); - } - else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) - { - return; - //list.InsertRange((int)itemIndex, (IReadOnlyList)testData); - } - else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) - { - return; - //list.InsertRange((int)itemIndex, new ReadOnlySpan((long[])testData)); - } - else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) - { - return; - //list.InsertRange((int)itemIndex, new Span((long[])testData)); - } - else if (testData is long[] testDataArray) - { - return; - //list.InsertRange((int)itemIndex, testDataArray); - } - else if (testData is List testDataList) - { - return; - //list.InsertRange((int)itemIndex, testDataList); - } - else if (testData is RecyclableList testDataRecyclableList) - { - return; - //list.InsertRange((int)itemIndex, testDataRecyclableList); - } - else if (testData is RecyclableLongList testDataRecyclableLongList) - { - return; - //list.InsertRange((int)itemIndex, testDataRecyclableLongList); - } - else if (testData is IList testDataIList) - { - return; - //list.InsertRange((int)itemIndex, testDataIList); - } - else if (testData is IEnumerable testDataIEnumerable) - { - return; - //list.InsertRange((int)itemIndex, testDataIEnumerable); - } - else - { - throw new InvalidCastException("Unknown type of test data"); - } - - // Validate - list.Count.Should().Be(expected.Count); - _ = list.Should().Equal(expected); - } - } - } -} diff --git a/Recyclable.CollectionsTests/RecyclableLongListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs index b6aa5cf..83b48ee 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -998,6 +998,85 @@ namespace Recyclable.CollectionsTests } } + [Theory] + [ClassData(typeof(SourceDataWithBlockSizeWithItemIndexWithRangeTheoryData))] + public void InsertRangeShouldMoveItems(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in (long ItemIndex, long RangedItemsCount)[] itemRanges) + { + // Prepare + var expectedData = testData.Reverse().ToArray(); + + foreach ((var itemIndex, var rangedItemsCount) in itemRanges) + { + using var list = new RecyclableLongList(testData.Reverse(), minBlockSize, itemsCount); + var expected = expectedData.ToList(); + var rangedTestData = expected.GetRange((int)itemIndex, (int)rangedItemsCount).ToArray(); + expected.InsertRange((int)itemIndex, rangedTestData); + + if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) + { + list.InsertRange((int)itemIndex, (Array)rangedTestData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.InsertRange((int)itemIndex, (ICollection)rangedTestData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.InsertRange((int)itemIndex, (ICollection)rangedTestData); + } + else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) + { + list.InsertRange((int)itemIndex, (IEnumerable)rangedTestData); + } + else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) + { + list.InsertRange((int)itemIndex, (IReadOnlyList)rangedTestData); + } + else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) + { + list.InsertRange((int)itemIndex, new ReadOnlySpan(rangedTestData)); + } + else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) + { + list.InsertRange((int)itemIndex, new Span(rangedTestData)); + } + else if (testData is long[] testDataArray) + { + list.InsertRange((int)itemIndex, rangedTestData); + } + else if (testData is List testDataList) + { + list.InsertRange((int)itemIndex, rangedTestData.ToList()); + } + else if (testData is RecyclableList testDataRecyclableList) + { + using RecyclableList rangedTestDataRecyclableList = rangedTestData.ToRecyclableList(); + list.InsertRange((int)itemIndex, rangedTestDataRecyclableList); + } + else if (testData is RecyclableLongList testDataRecyclableLongList) + { + using RecyclableLongList rangedTestDataRecyclableList = rangedTestData.ToRecyclableLongList(); + list.InsertRange((int)itemIndex, rangedTestDataRecyclableList); + } + else if (testData is IList testDataIList) + { + list.InsertRange((int)itemIndex, (IList)rangedTestData); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.InsertRangeEnumerated((int)itemIndex, (IEnumerable)rangedTestData); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + list.Count.Should().Be(expected.Count); + _ = list.Should().Equal(expected); + } + } + [Theory] [ClassData(typeof(SourceDataWithBlockSizeWithItemIndexTheoryData))] public void InsertShouldMoveItemsWhenLong(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize, in long[] itemIndexes)