From 98961a79ef9396073cc8e67c677d7b855faeba86 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 8 Aug 2024 23:02:43 +0200 Subject: [PATCH] Several refactorings and improved performance. Also added `Array`, a wrapper around generic arrays for improved performance. --- Arch.LowLevel.Tests/ArrayTest.cs | 122 ++++++++ Arch.LowLevel.Tests/Jagged/JaggedArrayTest.cs | 8 +- Arch.LowLevel/Arch.LowLevel.csproj | 8 +- Arch.LowLevel/Array.cs | 270 ++++++++++++++++++ Arch.LowLevel/Enumerators.cs | 85 ++++-- Arch.LowLevel/Jagged/JaggedArray.cs | 113 ++++---- Arch.LowLevel/Jagged/SparseJaggedArray.cs | 118 ++++---- Arch.LowLevel/Jagged/UnsafeJaggedArray.cs | 75 +++-- .../Jagged/UnsafeSparseJaggedArray.cs | 74 +++-- Arch.LowLevel/UnsafeList.cs | 4 +- Arch.LowLevel/UnsafeQueue.cs | 4 +- Arch.LowLevel/UnsafeStack.cs | 4 +- README.md | 2 +- 13 files changed, 693 insertions(+), 194 deletions(-) create mode 100644 Arch.LowLevel.Tests/ArrayTest.cs create mode 100644 Arch.LowLevel/Array.cs diff --git a/Arch.LowLevel.Tests/ArrayTest.cs b/Arch.LowLevel.Tests/ArrayTest.cs new file mode 100644 index 0000000..23b47dd --- /dev/null +++ b/Arch.LowLevel.Tests/ArrayTest.cs @@ -0,0 +1,122 @@ +using System.ComponentModel.DataAnnotations; + +namespace Arch.LowLevel.Tests; +using static Assert; + +/// +/// Checks related methods. +/// +[TestFixture] +public class ArrayTest +{ + /// + /// Checks if is capable of allocating space and adding items. + /// + [Test] + public void ArrayCreate() + { + var array = new Array(3); + array[0] = 1; + array[1] = 2; + array[2] = 3; + + That(array.Count, Is.EqualTo(3)); + } + + [Test] + public void ArrayEnumerator() + { + var array = new Array(3); + array[0] = 1; + array[1] = 2; + array[2] = 3; + + var count = 1; + foreach (var item in array) + That(item, Is.EqualTo(count++)); + } + + [Test] + public void ArrayEmptyIsEmpty() + { + var empty = Array.Empty(); + That(empty, Is.Empty); + } + + [Test] + public void ArrayFill() + { + var array = new Array(35); + Array.Fill(ref array, 8); + + for (var i = 0; i < array.Length; i++) + That(array[i], Is.EqualTo(8)); + } + + [Test] + public void ArrayCopy() + { + var src = new Array(15); + var dst = new Array(6); + + for (var i = 0; i < src.Length; i++) + src[i] = i; + + Array.Fill(ref dst); + Array.Copy(ref src, 4, ref dst, 1, 4); + + Multiple(() => + { + That(dst[0], Is.EqualTo(0)); + That(dst[1], Is.EqualTo(4)); + That(dst[2], Is.EqualTo(5)); + That(dst[3], Is.EqualTo(6)); + That(dst[4], Is.EqualTo(7)); + That(dst[5], Is.EqualTo(0)); + }); + } + + [Test] + public void ArrayResizeShrink() + { + var array = new Array(19); + for (var i = 0; i < array.Length; i++) + array[i] = i; + + var resized = Array.Resize(ref array, 8); + for (var i = 0; i < resized.Length; i++) + That(resized[i], Is.EqualTo(i)); + } + + [Test] + public void ArrayResizeGrow() + { + var array = new Array(8); + for (var i = 0; i < array.Length; i++) + array[i] = i; + + var resized = Array.Resize(ref array, 19); + for (var i = 0; i < array.Length; i++) + That(resized[i], Is.EqualTo(i)); + } + + [Test] + public void ArrayEquals() + { + var a = new Array(8); + var b = a; + + That(a, Is.EqualTo(b)); + That(a == b, Is.True); + } + + [Test] + public void ArrayNotEquals() + { + var a = new Array(8); + var b = new Array(8); + + That(a, Is.Not.EqualTo(b)); + That(a != b, Is.True); + } +} \ No newline at end of file diff --git a/Arch.LowLevel.Tests/Jagged/JaggedArrayTest.cs b/Arch.LowLevel.Tests/Jagged/JaggedArrayTest.cs index 494a4d8..8f6304d 100644 --- a/Arch.LowLevel.Tests/Jagged/JaggedArrayTest.cs +++ b/Arch.LowLevel.Tests/Jagged/JaggedArrayTest.cs @@ -15,7 +15,7 @@ public class JaggedArrayTest /// Checks if is capable of adding items correctly. /// [Test] - public void Add([Values(256,512,1024)] int capacity) + public void Add([Values(256,512,1024,2048,4096)] int capacity) { // Check add var jaggedArray = new JaggedArray(16000/Unsafe.SizeOf(), -1, capacity); @@ -37,7 +37,7 @@ public void Add([Values(256,512,1024)] int capacity) } [Test] - public void TryGetValue([Values(256,512,1024)] int capacity) + public void TryGetValue([Values(256,512,1024,2048,4096)] int capacity) { // Initialize the JaggedArray var jaggedArray = new JaggedArray(16000/Unsafe.SizeOf(), -1, capacity); @@ -62,7 +62,7 @@ public void TryGetValue([Values(256,512,1024)] int capacity) } [Test] - public void TryGetValueRef([Values(256,512,1024)] int capacity) + public void TryGetValueRef([Values(256,512,1024,2048,4096)] int capacity) { // Initialize the JaggedArray var jaggedArray = new JaggedArray(16000/Unsafe.SizeOf(), -1, capacity); @@ -92,7 +92,7 @@ public void TryGetValueRef([Values(256,512,1024)] int capacity) /// Checks if is capable of adding items correctly. /// [Test] - public void Remove([Values(256,512,1024)] int capacity) + public void Remove([Values(256,512,1024,2048,4096)] int capacity) { // Check add var jaggedArray = new JaggedArray(16000/Unsafe.SizeOf(), -1, capacity); diff --git a/Arch.LowLevel/Arch.LowLevel.csproj b/Arch.LowLevel/Arch.LowLevel.csproj index 81637bc..42f0c78 100644 --- a/Arch.LowLevel/Arch.LowLevel.csproj +++ b/Arch.LowLevel/Arch.LowLevel.csproj @@ -13,11 +13,14 @@ Arch.LowLevel Arch.LowLevel - 1.1.1 + 1.1.2 genaray Apache-2.0 LowLevel tools for arch. - Fixed several JaggedArray related bugs. + Refactored JaggedArrays. +Increased performance of JaggedArrays. +Added Array, a new class that acts as a Wrapper around normal generic Arrays for unsafe operations. +Added tests. c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system; arch; https://github.com/genaray/Arch.Extended @@ -35,6 +38,7 @@ + diff --git a/Arch.LowLevel/Array.cs b/Arch.LowLevel/Array.cs new file mode 100644 index 0000000..2a63ef1 --- /dev/null +++ b/Arch.LowLevel/Array.cs @@ -0,0 +1,270 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using CommunityToolkit.HighPerformance; + +namespace Arch.LowLevel; + + +/// +/// The struct +/// represents an allocated array of managed items which wraps them to acess the items via an unsafe operations. +/// +/// The managed generic. +[DebuggerTypeProxy(typeof(ArrayDebugView<>))] +public readonly struct Array +{ + + /// + /// The static empty . + /// + internal static Array Empty = new(0); + + /// + /// The pointer, pointing towards the first element of this . + /// + internal readonly T[] _array; + + /// + /// Creates an instance of the . + /// Allocates the array for the passed count of items. + /// + /// The arrays count or capacity. + public Array(int count) + { + _array = new T[count]; + Count = count; + } + + /// + /// Creates an instance of the . + /// Allocates the array for the passed count of items. + /// + /// The array used. + public Array(T[] array) + { + this._array = array; + Count = array.Length; + } + + /// + /// The count of this instance, its capacity. + /// + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + /// The count of this instance, its capacity. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Count; + } + + /// + /// Returns a reference to an item at a given index. + /// + /// The index. + public ref T this[int i] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _array.DangerousGetReferenceAt(i); + } + + /// + /// Converts this instance into a . + /// + /// A new instance of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AsSpan() + { + return MemoryMarshal.CreateSpan(ref this[0], Count); + } + + /// + /// Creates an instance of a for ref acessing the array content. + /// + /// A new . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() + { + return new Enumerator(AsSpan()); + } + + /// + /// Checks for equality. + /// + /// The other . + /// True if equal, oterwhise false. + public bool Equals(Array other) + { + return _array == other._array && Count == other.Count; + } + + /// + /// Checks for equality. + /// + /// The other . + /// True if equal, oterwhise false. + public override bool Equals(object? obj) + { + return obj is Array other && Equals(other); + } + + /// + /// Checks for equality. + /// + /// The first . + /// The second . + /// + public static bool operator ==(Array left, Array right) + { + return left.Equals(right); + } + + /// + /// Checks for inequality. + /// + /// The first . + /// The second . + /// + public static bool operator !=(Array left, Array right) + { + return !left.Equals(right); + } + + /// + /// Returns the hash of this . + /// + /// + public override int GetHashCode() + { + return _array.GetHashCode(); + } + + /// + /// Converts an into a generic array. + /// + /// The instance. + /// The array. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator T[](Array instance) + { + return instance._array; + } + + /// + /// Converts an into a generic array. + /// + /// The instance. + /// The array. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Array(T[] instance) + { + return new Array(instance); + } + + /// + /// Converts this to a string. + /// + /// The string. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override string ToString() + { + var items = new StringBuilder(); + foreach (ref var item in this) + { + items.Append($"{item},"); + } + items.Length--; + return $"Array<{typeof(T).Name}>[{Count}]{{{items}}}"; + } +} + + +public unsafe struct Array +{ + + /// + /// Returns an empty . + /// + /// The generic type. + /// The empty . + public static Array Empty() + { + return Array.Empty; + } + + /// + /// Copies the a part of the to the another . + /// + /// The source . + /// The start index in the source . + /// The destination . + /// The start index in the destination . + /// The length indicating the amount of items being copied. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy(ref Array source, int index, ref Array destination, int destinationIndex, int length) + { + System.Array.Copy(source._array, index, destination._array, destinationIndex, length); + } + + + /// + /// Fills an with a given value. + /// + /// The instance. + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Fill(ref Array source, in T value = default) + { + source.AsSpan().Fill(value); + } + + /// + /// Resizes an to a new . + /// + /// The . + /// The new capacity. + /// The generic type. + /// The new resized . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Array Resize(ref Array source, int newCapacity) + { + // Create a new array with the new capacity + var destination = new Array(newCapacity); + + // Calculate the number of elements to copy + var lengthToCopy = Math.Min(source.Length, newCapacity); + Copy(ref source, 0, ref destination, 0, lengthToCopy); + return destination; + } +} + +/// +/// A debug view for the . +/// +/// The unmanaged type. +internal class ArrayDebugView where T : unmanaged +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Array _entity; + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items + { + get + { + var items = new T[_entity.Count]; + _entity.AsSpan().CopyTo(items); + return items; + } + } + + public ArrayDebugView(Array entity) => _entity = entity; +} \ No newline at end of file diff --git a/Arch.LowLevel/Enumerators.cs b/Arch.LowLevel/Enumerators.cs index 589e7de..15f6342 100644 --- a/Arch.LowLevel/Enumerators.cs +++ b/Arch.LowLevel/Enumerators.cs @@ -4,36 +4,81 @@ namespace Arch.LowLevel; /// -/// The is a basic implementation of the interface for the . +/// The is a basic implementation of an enumerator for the >. /// /// -public unsafe struct Enumerator : IEnumerator where T : unmanaged +public unsafe ref struct Enumerator { - private readonly T* _list; + private readonly Span _list; private readonly int _count; private int _index; /// /// Creates an instance of the . /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(Span list) + { + _list = list; + _count = list.Length; + _index = -1; + } + + /// + /// Returns the current item. + /// + public readonly ref T Current => ref _list[_index]; + + /// + /// Moves to the next item. + /// + /// + public bool MoveNext() + { + return unchecked(++_index < _count); + } + + /// + /// Resets the enumerator. + /// + public void Reset() + { + _index = -1; + } +} + +/// +/// The is a basic implementation of the interface for the . +/// +/// +public unsafe struct UnsafeIEnumerator : IEnumerator where T : unmanaged +{ + private readonly T* _list; + private readonly int _count; + private int _index; + + /// + /// Creates an instance of the . + /// /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(T* list, int count) + internal UnsafeIEnumerator(T* list, int count) { _list = list; _count = count; - _index = 0; + _index = -1; } /// /// Returns the current item. /// - public T Current => _list[_index-1]; + public T Current => _list[_index]; /// /// Returns the current item. /// - object IEnumerator.Current => _list[_index-1]; + object IEnumerator.Current => _list[_index]; /// /// Disposes this enumerator. @@ -46,7 +91,7 @@ public void Dispose() { } // nop /// public bool MoveNext() { - return unchecked(_index++ < _count); + return unchecked(++_index < _count); } /// @@ -54,12 +99,12 @@ public bool MoveNext() /// public void Reset() { - _index = 0; + _index = -1; } } /// -/// The is a basic implementation of the interface for the . +/// The is a basic implementation of the interface for the . /// /// public unsafe ref struct UnsafeEnumerator where T : unmanaged @@ -69,7 +114,7 @@ public void Reset() private int _index; /// - /// Creates an instance of the . + /// Creates an instance of the . /// /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -77,13 +122,13 @@ internal UnsafeEnumerator(T* list, int count) { _list = list; _count = count; - _index = 0; + _index = -1; } /// /// Returns the current item. /// - public ref T Current => ref _list[_index-1]; + public ref T Current => ref _list[_index]; /// /// Moves to the next item. @@ -91,7 +136,7 @@ internal UnsafeEnumerator(T* list, int count) /// public bool MoveNext() { - return unchecked(_index++ < _count); + return unchecked(++_index < _count); } /// @@ -99,26 +144,26 @@ public bool MoveNext() /// public void Reset() { - _index = 0; + _index = -1; } } /// -/// The is a basic implementation of the interface for iterating backwards. +/// The is a basic implementation of the interface for iterating backwards. /// /// -public unsafe struct ReverseEnumerator : IEnumerator where T : unmanaged +public unsafe struct ReverseIEnumerator : IEnumerator where T : unmanaged { private readonly T* _list; private readonly int _count; private int _index; /// - /// Creates an instance of the . + /// Creates an instance of the . /// /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReverseEnumerator(T* list, int count) + internal ReverseIEnumerator(T* list, int count) { _list = list; _count = count; @@ -168,7 +213,7 @@ public void Reset() private int _index; /// - /// Creates an instance of the . + /// Creates an instance of the . /// /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Arch.LowLevel/Jagged/JaggedArray.cs b/Arch.LowLevel/Jagged/JaggedArray.cs index 5013c6a..14d3e48 100644 --- a/Arch.LowLevel/Jagged/JaggedArray.cs +++ b/Arch.LowLevel/Jagged/JaggedArray.cs @@ -1,5 +1,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using CommunityToolkit.HighPerformance; namespace Arch.LowLevel.Jagged; @@ -82,7 +84,16 @@ public bool IsEmpty public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref Array[i]; + get => ref Array.DangerousGetReferenceAt(i); + } + + /// + /// Clears this and sets all values to the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(T filler = default) + { + System.Array.Fill(Array, filler); } } @@ -103,10 +114,15 @@ public class JaggedArray /// private readonly int _bucketSizeMinusOne; + /// + /// The is always a value the power of 2, therefore we can use a bitshift for the division during the index calculation. + /// + private readonly int _bucketSizeShift; + /// /// The allocated s. /// - private Bucket[] _bucketArray; + private Array> _buckets; /// /// The filler, the default value. @@ -122,16 +138,17 @@ public JaggedArray(int bucketSize, int capacity = 64) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; - _bucketArray = new Bucket[capacity/_bucketSize + 1]; + _bucketSizeShift = (int)Math.Log(_bucketSize, 2); + _buckets = new Array>(capacity/_bucketSize + 1); _filler = default!; // Fill buckets - for (var i = 0; i < _bucketArray.Length; i++) + for (var i = 0; i < _buckets.Length; i++) { var bucket = new Bucket(_bucketSize); - _bucketArray[i] = bucket; - Array.Fill(bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } @@ -145,28 +162,29 @@ public JaggedArray(int bucketSize, T filler, int capacity = 64) : this(bucketSiz { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; - _bucketArray = new Bucket[capacity/_bucketSize + 1]; + _bucketSizeShift = (int)Math.Log(_bucketSize, 2); + _buckets = new Bucket[capacity/_bucketSize + 1]; _filler = filler; // Fill buckets - for (var i = 0; i < _bucketArray.Length; i++) + for (var i = 0; i < _buckets.Length; i++) { var bucket = new Bucket(_bucketSize); - _bucketArray[i] = bucket; - Array.Fill(bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } /// /// The capacity, the total amount of items. /// - public int Capacity => _bucketArray.Length * _bucketSize; + public int Capacity => _buckets.Length * _bucketSize; /// - /// The length, the buckets inside the . + /// The length, the buckets inside the . /// - public int Buckets => _bucketArray.Length; + public int Buckets => _buckets.Length; /// /// Adds an item to the . @@ -178,7 +196,7 @@ public void Add(int index, in T item) { IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var bucket = ref _bucketArray[bucketIndex]; + ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = item; bucket.Count++; } @@ -192,7 +210,7 @@ public void Remove(int index) { IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var bucket = ref _bucketArray[bucketIndex]; + ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = _filler; bucket.Count--; } @@ -207,21 +225,14 @@ public void Remove(int index) public bool TryGetValue(int index, out T value) { // If the id is negative - if (index < 0) - { - value = _filler; - return false; - } - - if (index >= Capacity) + if (index < 0 || index >= Capacity) { value = _filler; return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); - - ref var item = ref _bucketArray[bucketIndex][itemIndex]; + ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) @@ -244,21 +255,14 @@ public bool TryGetValue(int index, out T value) public ref T TryGetValue(int index, out bool @bool) { // If the id is negative - if (index < 0) - { - @bool = false; - return ref Unsafe.NullRef(); - } - - if (index >= Capacity) + if (index < 0 || index >= Capacity) { @bool = false; return ref Unsafe.NullRef(); } IndexToSlot(index, out var bucketIndex, out var itemIndex); - - ref var item = ref _bucketArray[bucketIndex][itemIndex]; + ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) @@ -279,13 +283,13 @@ public ref T TryGetValue(int index, out bool @bool) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(int index) { - if (index <= 0 || index > Capacity) + if (index < 0 || index > Capacity) { return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var item = ref _bucketArray[bucketIndex][itemIndex]; + ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. return !EqualityComparer.Default.Equals(item, _filler); @@ -305,13 +309,13 @@ public void EnsureCapacity(int newCapacity) var length = Buckets; var buckets = newCapacity / _bucketSize + 1; - Array.Resize(ref _bucketArray, buckets); + Array.Resize(ref _buckets, buckets); - for (var i = length; i < _bucketArray.Length; i++) + for (var i = length; i < _buckets.Length; i++) { var bucket = new Bucket(_bucketSize); - _bucketArray[i] = bucket; - Array.Fill(bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } @@ -323,9 +327,9 @@ public void TrimExcess() { // Count how many of the last buckets are empty, to trim them var count = 0; - for (var i = _bucketArray.Length-1; i >= 0; i--) + for (var i = _buckets.Length-1; i >= 0; i--) { - ref var bucket = ref _bucketArray[i]; + ref var bucket = ref GetBucket(i); if (!bucket.IsEmpty) { break; @@ -334,8 +338,8 @@ public void TrimExcess() count++; } - var buckets = _bucketArray.Length-count; - Array.Resize(ref _bucketArray, buckets); + var buckets = _buckets.Length-count; + Array.Resize(ref _buckets, buckets); } /// @@ -350,19 +354,30 @@ public void IndexToSlot(int id, out int bucketIndex, out int itemIndex) Debug.Assert(id >= 0, "Id cannot be negative."); /* Instead of the '%' operator we can use logical '&' operator which is faster. But it requires the bucket size to be a power of 2. */ - bucketIndex = id / _bucketSize; + bucketIndex = id >> _bucketSizeShift; itemIndex = id & _bucketSizeMinusOne; } /// - /// Returns the from the at the given index. + /// Returns the from the at the given index. /// /// The index. /// The at the given index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref Bucket GetBucket(int index) { - return ref _bucketArray[index]; + return ref _buckets[index]; + } + + /// + /// Sets the of the at the given index. + /// + /// The index. + /// The to set + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetBucket(int index, in Bucket bucket) + { + _buckets[index] = bucket; } /// @@ -375,7 +390,7 @@ public ref T this[int i] get { IndexToSlot(i, out var bucketIndex, out var itemIndex); - return ref _bucketArray[bucketIndex][itemIndex]; + return ref GetBucket(bucketIndex)[itemIndex]; } } @@ -385,14 +400,14 @@ public ref T this[int i] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { - foreach (var bucket in _bucketArray) + foreach (var bucket in _buckets) { if (bucket.IsEmpty) { continue; } - Array.Fill(bucket.Array, _filler); + bucket.Clear(_filler); } } } \ No newline at end of file diff --git a/Arch.LowLevel/Jagged/SparseJaggedArray.cs b/Arch.LowLevel/Jagged/SparseJaggedArray.cs index 26eda11..44c062f 100644 --- a/Arch.LowLevel/Jagged/SparseJaggedArray.cs +++ b/Arch.LowLevel/Jagged/SparseJaggedArray.cs @@ -1,4 +1,6 @@ -namespace Arch.LowLevel.Jagged; +using CommunityToolkit.HighPerformance; + +namespace Arch.LowLevel.Jagged; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -100,7 +102,16 @@ internal void TrimExcess() public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref Array[i]; + get => ref Array.DangerousGetReferenceAt(i); + } + + /// + /// Clears this and sets all values to the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(T filler = default) + { + System.Array.Fill(Array, filler); } } @@ -121,11 +132,16 @@ public class SparseJaggedArray /// The size in items - 1. /// private readonly int _bucketSizeMinusOne; + + /// + /// The is always a value the power of 2, therefore we can use a bitshift for the division during the index calculation. + /// + private readonly int _bucketSizeShift; /// /// The allocated s. /// - private SparseBucket[] _bucketArray; + private Array> _buckets; /// /// The filler, the default value. @@ -141,16 +157,17 @@ public SparseJaggedArray(int bucketSize, int capacity = 64) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; - _bucketArray = new SparseBucket[capacity/_bucketSize + 1]; + _bucketSizeShift = (int)Math.Log(_bucketSize, 2); + _buckets = new Array>(capacity/_bucketSize + 1); _filler = default!; // Fill buckets - for (var i = 0; i < _bucketArray.Length; i++) + for (var i = 0; i < _buckets.Length; i++) { var bucket = new SparseBucket(_bucketSize); - _bucketArray[i] = bucket; - Array.Fill(bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } @@ -164,16 +181,17 @@ public SparseJaggedArray(int bucketSize, T filler, int capacity = 64) : this(buc { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; - _bucketArray = new SparseBucket[capacity/_bucketSize + 1]; + _bucketSizeShift = (int)Math.Log(_bucketSize, 2); + _buckets = new Array>(capacity/_bucketSize + 1); _filler = filler!; // Fill buckets - for (var i = 0; i < _bucketArray.Length; i++) + for (var i = 0; i < _buckets.Length; i++) { var bucket = new SparseBucket(_bucketSize); - _bucketArray[i] = bucket; - Array.Fill(bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } @@ -185,12 +203,12 @@ public SparseJaggedArray(int bucketSize, T filler, int capacity = 64) : this(buc /// /// The capacity, the total amount of items. /// - public int Capacity => _bucketArray.Length * _bucketSize; + public int Capacity => _buckets.Length * _bucketSize; /// - /// The length, the buckets inside the . + /// The length, the buckets inside the . /// - public int Buckets => _bucketArray.Length; + public int Buckets => _buckets.Length; /// /// Adds an item to the . @@ -202,7 +220,7 @@ public void Add(int index, in T item) { IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var bucket = ref _bucketArray[bucketIndex]; + ref var bucket = ref GetBucket(bucketIndex); bucket.EnsureCapacity(); bucket[itemIndex] = item; bucket.Count++; @@ -217,7 +235,7 @@ public void Remove(int index) { IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var bucket = ref _bucketArray[bucketIndex]; + ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = _filler; bucket.Count--; @@ -234,22 +252,14 @@ public void Remove(int index) public bool TryGetValue(int index, out T value) { // If the id is negative - if (index < 0) - { - value = _filler; - return false; - } - - // Index greater than capacity? - if (index >= Capacity) + if (index < 0 || index >= Capacity) { value = _filler; return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); - - ref var item = ref _bucketArray[bucketIndex][itemIndex]; + ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) @@ -272,21 +282,14 @@ public bool TryGetValue(int index, out T value) public ref T TryGetValue(int index, out bool @bool) { // If the id is negative - if (index < 0) + if (index < 0 || index >= Capacity) { @bool = false; return ref Unsafe.NullRef(); } - if (index >= Capacity) - { - @bool = false; - return ref Unsafe.NullRef(); - } - IndexToSlot(index, out var bucketIndex, out var itemIndex); - - ref var item = ref _bucketArray[bucketIndex][itemIndex]; + ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) @@ -307,13 +310,13 @@ public ref T TryGetValue(int index, out bool @bool) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(int index) { - if (index <= 0 || index >= Capacity) + if (index < 0 || index >= Capacity) { return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var bucket = ref _bucketArray[bucketIndex]; + ref var bucket = ref GetBucket(bucketIndex); if (bucket.IsEmpty) { @@ -339,13 +342,13 @@ public void EnsureCapacity(int newCapacity) var length = Buckets; var buckets = newCapacity / _bucketSize + 1; - Array.Resize(ref _bucketArray, buckets); + Array.Resize(ref _buckets, buckets); - for (var i = length; i < _bucketArray.Length; i++) + for (var i = length; i < _buckets.Length; i++) { var bucket = new SparseBucket(_bucketSize); - _bucketArray[i] = bucket; - Array.Fill(bucket.Array, _filler); + SetBucket(i, bucket); + bucket.Clear(_filler); } } @@ -357,9 +360,9 @@ public void TrimExcess() { // Count how many of the last buckets are empty, to trim them var count = 0; - for (var i = _bucketArray.Length-1; i >= 0; i--) + for (var i = _buckets.Length-1; i >= 0; i--) { - ref var bucket = ref _bucketArray[i]; + ref var bucket = ref GetBucket(i); if (!bucket.IsEmpty) { break; @@ -368,12 +371,12 @@ public void TrimExcess() count++; } - var buckets = _bucketArray.Length-count; - Array.Resize(ref _bucketArray, buckets); + var buckets = _buckets.Length-count; + Array.Resize(ref _buckets, buckets); } /// - /// Converts the passed id to its inner and outer index ( or slot ) inside the array. + /// Converts the passed id to its inner and outer index ( or slot ) inside the array. /// /// The id. /// The outer index. @@ -384,19 +387,30 @@ public void IndexToSlot(int id, out int bucketIndex, out int itemIndex) Debug.Assert(id >= 0, "Id cannot be negative."); /* Instead of the '%' operator we can use logical '&' operator which is faster. But it requires the bucket size to be a power of 2. */ - bucketIndex = id / _bucketSize; + bucketIndex = id >> _bucketSizeShift; itemIndex = id & _bucketSizeMinusOne; } /// - /// Returns the from the at the given index. + /// Returns the from the at the given index. /// /// The index. /// The at the given index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref SparseBucket GetBucket(int index) { - return ref _bucketArray[index]; + return ref _buckets[index]; + } + + /// + /// Sets the of the at the given index. + /// + /// The index. + /// The to set + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetBucket(int index, in SparseBucket bucket) + { + _buckets[index] = bucket; } /// @@ -409,7 +423,7 @@ public ref T this[int i] get { IndexToSlot(i, out var bucketIndex, out var itemIndex); - return ref _bucketArray[bucketIndex][itemIndex]; + return ref GetBucket(bucketIndex)[itemIndex]; } } @@ -419,14 +433,14 @@ public ref T this[int i] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { - foreach (var bucket in _bucketArray) + foreach (var bucket in _buckets) { if (bucket.IsEmpty) { continue; } - Array.Fill(bucket.Array, _filler); + bucket.Clear(_filler); } } } \ No newline at end of file diff --git a/Arch.LowLevel/Jagged/UnsafeJaggedArray.cs b/Arch.LowLevel/Jagged/UnsafeJaggedArray.cs index c1dfb70..b59867a 100644 --- a/Arch.LowLevel/Jagged/UnsafeJaggedArray.cs +++ b/Arch.LowLevel/Jagged/UnsafeJaggedArray.cs @@ -55,6 +55,15 @@ public ref T this[int i] get => ref Array[i]; } + /// + /// Clears this and sets all values to the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(T filler = default) + { + UnsafeArray.Fill(ref Array, filler); + } + /// /// Disposes this . /// @@ -81,6 +90,11 @@ public struct UnsafeJaggedArray : IDisposable where T : unmanaged /// The size in items - 1. /// private readonly int _bucketSizeMinusOne; + + /// + /// The is always a value the power of 2, therefore we can use a bitshift for the division during the index calculation. + /// + private readonly int _bucketSizeShift; /// /// The allocated s. @@ -101,6 +115,7 @@ public UnsafeJaggedArray(int bucketSize, int capacity = 64) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; + _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _bucketArray = new UnsafeArray>(capacity / _bucketSize + 1); _filler = default!; @@ -109,8 +124,8 @@ public UnsafeJaggedArray(int bucketSize, int capacity = 64) for (var i = 0; i < _bucketArray.Length; i++) { var bucket = new UnsafeBucket(_bucketSize); - _bucketArray[i] = bucket; - UnsafeArray.Fill(ref bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } @@ -124,6 +139,7 @@ public UnsafeJaggedArray(int bucketSize, T filler, int capacity = 64) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; + _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _bucketArray = new UnsafeArray>(capacity / _bucketSize + 1); _filler = filler; @@ -132,8 +148,8 @@ public UnsafeJaggedArray(int bucketSize, T filler, int capacity = 64) for (var i = 0; i < _bucketArray.Length; i++) { var bucket = new UnsafeBucket(_bucketSize); - _bucketArray[i] = bucket; - UnsafeArray.Fill(ref bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } @@ -157,7 +173,7 @@ public void Add(int index, in T item) { IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var bucket = ref _bucketArray[bucketIndex]; + ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = item; bucket.Count++; } @@ -171,7 +187,7 @@ public void Remove(int index) { IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var bucket = ref _bucketArray[bucketIndex]; + ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = _filler; bucket.Count--; } @@ -186,21 +202,15 @@ public void Remove(int index) public bool TryGetValue(int index, out T value) { // If the id is negative - if (index < 0) - { - value = _filler; - return false; - } - - if (index >= Capacity) + if (index < 0 || index >= Capacity ) { value = _filler; return false; } + IndexToSlot(index, out var bucketIndex, out var itemIndex); - - ref var item = ref _bucketArray[bucketIndex][itemIndex]; + ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) @@ -223,20 +233,14 @@ public bool TryGetValue(int index, out T value) public ref T TryGetValue(int index, out bool @bool) { // If the id is negative - if (index < 0) - { - @bool = false; - return ref Unsafe.NullRef(); - } - - if (index >= Capacity) + if (index < 0 || index >= Capacity) { @bool = false; return ref Unsafe.NullRef(); } IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var item = ref _bucketArray[bucketIndex][itemIndex]; + ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) @@ -257,13 +261,13 @@ public ref T TryGetValue(int index, out bool @bool) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(int index) { - if (index <= 0 || index > Capacity) + if (index < 0 || index > Capacity) { return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var item = ref _bucketArray[bucketIndex][itemIndex]; + ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. return !EqualityComparer.Default.Equals(item, _filler); @@ -288,8 +292,8 @@ public void EnsureCapacity(int newCapacity) for (var i = length; i < _bucketArray.Length; i++) { var bucket = new UnsafeBucket(_bucketSize); - _bucketArray[i] = bucket; - UnsafeArray.Fill(ref bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } @@ -328,7 +332,7 @@ public void IndexToSlot(int id, out int bucketIndex, out int itemIndex) Debug.Assert(id >= 0, "Id cannot be negative."); /* Instead of the '%' operator we can use logical '&' operator which is faster. But it requires the bucket size to be a power of 2. */ - bucketIndex = id / _bucketSize; + bucketIndex = id >> _bucketSizeShift; itemIndex = id & _bucketSizeMinusOne; } @@ -342,6 +346,17 @@ public ref UnsafeBucket GetBucket(int index) { return ref _bucketArray[index]; } + + /// + /// Sets the from the at the given index. + /// + /// The index. + /// The at the given index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetBucket(int index, in UnsafeBucket bucket) + { + _bucketArray[index] = bucket; + } /// /// Returns a reference to an item at the given index. @@ -353,7 +368,7 @@ public ref T this[int i] get { IndexToSlot(i, out var bucketIndex, out var itemIndex); - return ref _bucketArray[bucketIndex][itemIndex]; + return ref GetBucket(bucketIndex)[itemIndex]; } } @@ -370,7 +385,7 @@ public void Clear() continue; } - UnsafeArray.Fill(ref bucket.Array, _filler); + bucket.Clear(_filler); } } diff --git a/Arch.LowLevel/Jagged/UnsafeSparseJaggedArray.cs b/Arch.LowLevel/Jagged/UnsafeSparseJaggedArray.cs index aa19483..ef1e5ce 100644 --- a/Arch.LowLevel/Jagged/UnsafeSparseJaggedArray.cs +++ b/Arch.LowLevel/Jagged/UnsafeSparseJaggedArray.cs @@ -103,6 +103,16 @@ public ref T this[int i] [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref Array[i]; } + + /// + /// Clears this and sets all values to the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(T filler = default) + { + UnsafeArray.Fill(ref Array, filler); + } + /// /// Disposes this @@ -131,6 +141,11 @@ public struct UnsafeSparseJaggedArray : IDisposable where T : unmanaged /// The size in items - 1. /// private readonly int _bucketSizeMinusOne; + + /// + /// The is always a value the power of 2, therefore we can use a bitshift for the division during the index calculation. + /// + private readonly int _bucketSizeShift; /// /// The allocated s. @@ -151,6 +166,7 @@ public UnsafeSparseJaggedArray(int bucketSize, int capacity = 64) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; + _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _bucketArray = new UnsafeArray>(capacity / _bucketSize + 1); _filler = default!; @@ -159,8 +175,8 @@ public UnsafeSparseJaggedArray(int bucketSize, int capacity = 64) for (var i = 0; i < _bucketArray.Length; i++) { var bucket = new UnsafeSparseBucket(_bucketSize); - _bucketArray[i] = bucket; - UnsafeArray.Fill(ref bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } @@ -174,6 +190,7 @@ public UnsafeSparseJaggedArray(int bucketSize, T filler, int capacity = 64) : th { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; + _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _bucketArray = new UnsafeArray>(capacity / _bucketSize + 1); _filler = filler!; @@ -182,8 +199,8 @@ public UnsafeSparseJaggedArray(int bucketSize, T filler, int capacity = 64) : th for (var i = 0; i < _bucketArray.Length; i++) { var bucket = new UnsafeSparseBucket(_bucketSize); - _bucketArray[i] = bucket; - UnsafeArray.Fill(ref bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } @@ -212,7 +229,7 @@ public void Add(int index, in T item) { IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var bucket = ref _bucketArray[bucketIndex]; + ref var bucket = ref GetBucket(bucketIndex); bucket.EnsureCapacity(); bucket[itemIndex] = item; bucket.Count++; @@ -227,7 +244,7 @@ public void Remove(int index) { IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var bucket = ref _bucketArray[bucketIndex]; + ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = _filler; bucket.Count--; @@ -244,21 +261,14 @@ public void Remove(int index) public bool TryGetValue(int index, out T value) { // If the id is negative - if (index < 0) - { - value = _filler; - return false; - } - - if (index >= Capacity) + if (index < 0 || index >= Capacity) { value = _filler; return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); - - ref var item = ref _bucketArray[bucketIndex][itemIndex]; + ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) @@ -281,21 +291,14 @@ public bool TryGetValue(int index, out T value) public ref T TryGetValue(int index, out bool @bool) { // If the id is negative - if (index < 0) - { - @bool = false; - return ref Unsafe.NullRef(); - } - - if (index >= Capacity) + if (index < 0 || index >= Capacity) { @bool = false; return ref Unsafe.NullRef(); } IndexToSlot(index, out var bucketIndex, out var itemIndex); - - ref var item = ref _bucketArray[bucketIndex][itemIndex]; + ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) @@ -316,13 +319,13 @@ public ref T TryGetValue(int index, out bool @bool) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(int index) { - if (index <= 0 || index > Capacity) + if (index < 0 || index > Capacity) { return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); - ref var bucket = ref _bucketArray[bucketIndex]; + ref var bucket = ref GetBucket(bucketIndex); if (bucket.IsEmpty) { @@ -353,8 +356,8 @@ public void EnsureCapacity(int newCapacity) for (var i = length; i < _bucketArray.Length; i++) { var bucket = new UnsafeSparseBucket(_bucketSize); - _bucketArray[i] = bucket; - UnsafeArray.Fill(ref bucket.Array, _filler); + SetBucket(i, in bucket); + bucket.Clear(_filler); } } @@ -393,7 +396,7 @@ public void IndexToSlot(int id, out int bucketIndex, out int itemIndex) Debug.Assert(id >= 0, "Id cannot be negative."); /* Instead of the '%' operator we can use logical '&' operator which is faster. But it requires the bucket size to be a power of 2. */ - bucketIndex = id / _bucketSize; + bucketIndex = id >> _bucketSizeShift; itemIndex = id & _bucketSizeMinusOne; } @@ -408,6 +411,17 @@ public ref UnsafeSparseBucket GetBucket(int index) return ref _bucketArray[index]; } + /// + /// Sets the from the at the given index. + /// + /// The index. + /// The at the given index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetBucket(int index, in UnsafeSparseBucket bucket) + { + _bucketArray[index] = bucket; + } + /// /// Returns a reference to an item at the given index. /// @@ -418,7 +432,7 @@ public ref T this[int i] get { IndexToSlot(i, out var bucketIndex, out var itemIndex); - return ref _bucketArray[bucketIndex][itemIndex]; + return ref GetBucket(bucketIndex)[itemIndex]; } } diff --git a/Arch.LowLevel/UnsafeList.cs b/Arch.LowLevel/UnsafeList.cs index 7dd99d8..b028aad 100644 --- a/Arch.LowLevel/UnsafeList.cs +++ b/Arch.LowLevel/UnsafeList.cs @@ -341,7 +341,7 @@ public UnsafeEnumerator GetEnumerator() [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { - return new Enumerator(_array, Count); + return new UnsafeIEnumerator(_array, Count); } /// @@ -351,7 +351,7 @@ IEnumerator IEnumerable.GetEnumerator() [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { - return new Enumerator(_array, Count); + return new UnsafeIEnumerator(_array, Count); } /// diff --git a/Arch.LowLevel/UnsafeQueue.cs b/Arch.LowLevel/UnsafeQueue.cs index 4da85e3..53d775c 100644 --- a/Arch.LowLevel/UnsafeQueue.cs +++ b/Arch.LowLevel/UnsafeQueue.cs @@ -217,7 +217,7 @@ public UnsafeEnumerator GetEnumerator() [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { - return new Enumerator(_queue, Count); + return new UnsafeIEnumerator(_queue, Count); } /// @@ -227,7 +227,7 @@ IEnumerator IEnumerable.GetEnumerator() [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { - return new Enumerator(_queue, Count); + return new UnsafeIEnumerator(_queue, Count); } /// diff --git a/Arch.LowLevel/UnsafeStack.cs b/Arch.LowLevel/UnsafeStack.cs index 4f82ece..a94db9a 100644 --- a/Arch.LowLevel/UnsafeStack.cs +++ b/Arch.LowLevel/UnsafeStack.cs @@ -225,7 +225,7 @@ public readonly UnsafeReverseEnumerator GetEnumerator() [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly IEnumerator IEnumerable.GetEnumerator() { - return new ReverseEnumerator(_stack, Count); + return new ReverseIEnumerator(_stack, Count); } /// @@ -235,7 +235,7 @@ readonly IEnumerator IEnumerable.GetEnumerator() [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly IEnumerator IEnumerable.GetEnumerator() { - return new ReverseEnumerator(_stack, Count); + return new ReverseIEnumerator(_stack, Count); } /// diff --git a/README.md b/README.md index 071f73d..1f0fd87 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Download the packages and get started today! dotnet add package Arch.System --version 1.0.5 dotnet add package Arch.System.SourceGenerator --version 1.2.1 dotnet add package Arch.EventBus --version 1.0.2 -dotnet add package Arch.LowLevel --version 1.1.1 +dotnet add package Arch.LowLevel --version 1.1.2 dotnet add package Arch.Relationships --version 1.0.0 dotnet add package Arch.Persistence --version 1.0.4 dotnet add package Arch.AOT.SourceGenerator --version 1.0.1