diff --git a/Projects/Server/Collections/PooledRefList.cs b/Projects/Server/Collections/PooledRefList.cs new file mode 100644 index 000000000..fccdff986 --- /dev/null +++ b/Projects/Server/Collections/PooledRefList.cs @@ -0,0 +1,1154 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Server.Buffers; + +namespace Server.Collections; + +// Implements a variable-size List that uses an array of objects to store the +// elements. A List has a capacity, which is the allocated length +// of the internal array. As elements are added to a List, the capacity +// of the List is automatically increased as required by reallocating the +// internal array. +// +[DebuggerDisplay("Count = {Count}")] +public ref struct PooledRefList +{ + private const int MaxLength = int.MaxValue; + private const int DefaultCapacity = 4; + + internal T[] _items; // Do not rename (binary serialization) + internal int _size; // Do not rename (binary serialization) + private int _version; // Do not rename (binary serialization) + private bool _mt; + +#pragma warning disable CA1825 // avoid the extra generic instantiation for Array.Empty() + private static readonly T[] s_emptyArray = new T[0]; +#pragma warning restore CA1825 + + private ArrayPool ArrayPool + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _mt ? ArrayPool.Shared : STArrayPool.Shared; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PooledRefList Create(int capacity = 32, bool mt = false) => new(capacity, mt); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PooledRefList CreateMT(int capacity = 32) => new(capacity, true); + + // Constructs a List. The list is initially empty and has a capacity + // of zero. Upon adding the first element to the list the capacity is + // increased to DefaultCapacity, and then increased in multiples of two + // as required. + public PooledRefList(int capacity, bool mt = false) + { + _mt = mt; + _size = 0; + _version = 0; + _items = capacity switch + { + < 0 => throw new ArgumentOutOfRangeException(nameof(capacity), capacity, CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum), + 0 => Array.Empty(), + _ => (_mt ? ArrayPool.Shared : STArrayPool.Shared).Rent(capacity) + }; + + } + + // Constructs a List, copying the contents of the given collection. The + // size and capacity of the new list will both be equal to the size of the + // given collection. + // + public PooledRefList(PooledRefList collection, bool mt = false) + { + _version = 0; + _mt = mt; + + int count = collection.Count; + if (count == 0) + { + _items = s_emptyArray; + _size = 0; + } + else + { + _items = (mt ? ArrayPool.Shared : STArrayPool.Shared).Rent(count); + collection.CopyTo(_items, 0); + _size = count; + } + } + + // Constructs a List, copying the contents of the given collection. The + // size and capacity of the new list will both be equal to the size of the + // given collection. + // + public PooledRefList(IEnumerable collection, bool mt = false) + { + if (collection == null) + { + throw new ArgumentNullException(nameof(collection)); + } + + _version = 0; + _mt = mt; + + if (collection is ICollection c) + { + int count = c.Count; + if (count == 0) + { + _items = s_emptyArray; + _size = 0; + } + else + { + _items = (mt ? ArrayPool.Shared : STArrayPool.Shared).Rent(count); + c.CopyTo(_items, 0); + _size = count; + } + } + else + { + _size = 0; + _items = s_emptyArray; + using IEnumerator en = collection!.GetEnumerator(); + while (en.MoveNext()) + { + Add(en.Current); + } + } + } + + // Gets and sets the capacity of this list. The capacity is the size of + // the internal array used to hold items. When set, the internal + // array of the list is reallocated to the given capacity. + // + public int Capacity + { + get => _items.Length; + set + { + if (value < _size) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + if (value != _items.Length) + { + if (value > 0) + { + T[] newItems = ArrayPool.Rent(_size); + if (_size > 0) + { + Array.Copy(_items, newItems, _size); + } + + if (_items.Length > 0) + { + Clear(); + ArrayPool.Return(_items); + } + _items = newItems; + } + else + { + Clear(); + ArrayPool.Return(_items); + _items = s_emptyArray; + } + } + } + } + + // Read-only property describing how many elements are in the List. + public int Count => _size; + + // Sets or Gets the element at the given index. + public T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + // Following trick can reduce the range check by one + if ((uint)index >= (uint)_size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + return _items[index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + if ((uint)index >= (uint)_size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + _items[index] = value; + _version++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsCompatibleObject(object? value) + { + // Non-null values are fine. Only accept nulls if T is a class or Nullable. + // Note that default(T) is not equal to null for value types except when T is Nullable. + return value is T || value == null && default(T) == null; + } + + // Adds the given object to the end of this list. The size of the list is + // increased by one. If required, the capacity of the list is doubled + // before adding the new element. + // + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + _version++; + T[] array = _items; + int size = _size; + if ((uint)size < (uint)array.Length) + { + _size = size + 1; + array[size] = item; + } + else + { + AddWithResize(item); + } + } + + // Non-inline from List.Add to improve its code quality as uncommon path + [MethodImpl(MethodImplOptions.NoInlining)] + private void AddWithResize(T item) + { + Debug.Assert(_size == _items.Length); + int size = _size; + Grow(size + 1); + _size = size + 1; + _items[size] = item; + } + + // Adds the elements of the given collection to the end of this list. If + // required, the capacity of the list is increased to twice the previous + // capacity or the new size, whichever is larger. + // + public void AddRange(IEnumerable collection) => InsertRange(_size, collection); + + // Searches a section of the list for a given element using a binary search + // algorithm. Elements of the list are compared to the search value using + // the given IComparer interface. If comparer is null, elements of + // the list are compared to the search value using the IComparable + // interface, which in that case must be implemented by all elements of the + // list and the given search value. This method assumes that the given + // section of the list is already sorted; if this is not the case, the + // result will be incorrect. + // + // The method returns the index of the given value in the list. If the + // list does not contain the given value, the method returns a negative + // integer. The bitwise complement operator (~) can be applied to a + // negative result to produce the index of the first element (if any) that + // is larger than the given search value. This is also the index at which + // the search value should be inserted into the list in order for the list + // to remain sorted. + // + // The method uses the Array.BinarySearch method to perform the + // search. + // + public int BinarySearch(int index, int count, T item, IComparer? comparer) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (_size - index < count) + { + throw new ArgumentException("Length must be greater than zero"); + } + + return Array.BinarySearch(_items, index, count, item, comparer); + } + + public int BinarySearch(T item) => BinarySearch(0, Count, item, null); + + public int BinarySearch(T item, IComparer? comparer) => BinarySearch(0, Count, item, comparer); + + // Clears the contents of List. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + _version++; + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + int size = _size; + _size = 0; + if (size > 0) + { + Array.Clear(_items, 0, size); // Clear the elements so that the gc can reclaim the references. + } + } + else + { + _size = 0; + } + } + + // Contains returns true if the specified element is in the List. + // It does a linear, O(n) search. Equality is determined by calling + // EqualityComparer.Default.Equals(). + // + public bool Contains(T item) + { + // PERF: IndexOf calls Array.IndexOf, which internally + // calls EqualityComparer.Default.IndexOf, which + // is specialized for different types. This + // boosts performance since instead of making a + // virtual method call each iteration of the loop, + // via EqualityComparer.Default.Equals, we + // only make one virtual call to EqualityComparer.IndexOf. + + return _size != 0 && IndexOf(item) != -1; + } + + public PooledRefList ConvertAll(Converter converter) + { + if (converter == null) + { + throw new ArgumentNullException(nameof(converter)); + } + + PooledRefList list = new PooledRefList(_size); + for (int i = 0; i < _size; i++) + { + list._items[i] = converter(_items[i]); + } + list._size = _size; + + return list; + } + + // Copies this List into array, which must be of a + // compatible array type. + public void CopyTo(T[] array) => CopyTo(array, 0); + + // Copies a section of this list to the given array at the given index. + // + // The method uses the Array.Copy method to copy the elements. + // + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + if (_size - index < count) + { + throw new ArgumentException("Length must be greater than zero"); + } + + // Delegate rest of error checking to Array.Copy. + Array.Copy(_items, index, array, arrayIndex, count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + // Delegate rest of error checking to Array.Copy. + Array.Copy(_items, 0, array, arrayIndex, _size); + } + + /// + /// Ensures that the capacity of this list is at least the specified . + /// If the current capacity of the list is less than specified , + /// the capacity is increased by continuously twice current capacity until it is at least the specified . + /// + /// The minimum capacity to ensure. + public int EnsureCapacity(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + if (_items.Length < capacity) + { + Grow(capacity); + _version++; + } + + return _items.Length; + } + + /// + /// Increase the capacity of this list to at least the specified . + /// + /// The minimum capacity to ensure. + private void Grow(int capacity) + { + Debug.Assert(_items.Length < capacity); + + int newcapacity = _items.Length == 0 ? DefaultCapacity : 2 * _items.Length; + + // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newcapacity > MaxLength) + { + newcapacity = MaxLength; + } + + // If the computed capacity is still less than specified, set to the original argument. + // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. + if (newcapacity < capacity) + { + newcapacity = capacity; + } + + Capacity = newcapacity; + } + + public bool Exists(Predicate match) => FindIndex(match) != -1; + + public T? Find(Predicate match) + { + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + for (int i = 0; i < _size; i++) + { + if (match(_items[i])) + { + return _items[i]; + } + } + return default; + } + + public PooledRefList FindAll(Predicate match) + { + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + PooledRefList list = new PooledRefList(); + for (int i = 0; i < _size; i++) + { + if (match(_items[i])) + { + list.Add(_items[i]); + } + } + return list; + } + + public int FindIndex(Predicate match) => FindIndex(0, _size, match); + + public int FindIndex(int startIndex, Predicate match) => FindIndex(startIndex, _size - startIndex, match); + + public int FindIndex(int startIndex, int count, Predicate match) + { + if ((uint)startIndex > (uint)_size) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + + if (count < 0 || startIndex > _size - count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + int endIndex = startIndex + count; + for (int i = startIndex; i < endIndex; i++) + { + if (match(_items[i])) + { + return i; + } + } + return -1; + } + + public T? FindLast(Predicate match) + { + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + for (int i = _size - 1; i >= 0; i--) + { + if (match(_items[i])) + { + return _items[i]; + } + } + return default; + } + + public int FindLastIndex(Predicate match) => FindLastIndex(_size - 1, _size, match); + + public int FindLastIndex(int startIndex, Predicate match) => FindLastIndex(startIndex, startIndex + 1, match); + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + if (_size == 0) + { + // Special case for 0 length List + if (startIndex != -1) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + } + else + { + // Make sure we're not out of range + if ((uint)startIndex >= (uint)_size) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + } + + // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0. + if (count < 0 || startIndex - count + 1 < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + int endIndex = startIndex - count; + for (int i = startIndex; i > endIndex; i--) + { + if (match(_items[i])) + { + return i; + } + } + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + int version = _version; + + for (int i = 0; i < _size; i++) + { + if (version != _version) + { + break; + } + action(_items[i]); + } + + if (version != _version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); + } + } + + // Returns an enumerator for this list with the given + // permission for removal of elements. If modifications made to the list + // while an enumeration is in progress, the MoveNext and + // GetObject methods of the enumerator will throw an exception. + // + public Enumerator GetEnumerator() => new(this); + + public PooledRefList GetRange(int index, int count) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (_size - index < count) + { + throw new ArgumentException("Length must be greater than zero"); + } + + PooledRefList list = new PooledRefList(count); + Array.Copy(_items, index, list._items, 0, count); + list._size = count; + return list; + } + + // Returns the index of the first occurrence of a given value in a range of + // this list. The list is searched forwards from beginning to end. + // The elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.IndexOf method to perform the + // search. + // + public int IndexOf(T item) => Array.IndexOf(_items, item, 0, _size); + + // Returns the index of the first occurrence of a given value in a range of + // this list. The list is searched forwards, starting at index + // index and ending at count number of elements. The + // elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.IndexOf method to perform the + // search. + // + public int IndexOf(T item, int index) + { + if (index > _size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return Array.IndexOf(_items, item, index, _size - index); + } + + // Returns the index of the first occurrence of a given value in a range of + // this list. The list is searched forwards, starting at index + // index and upto count number of elements. The + // elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.IndexOf method to perform the + // search. + // + public int IndexOf(T item, int index, int count) + { + if (index > _size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0 || index > _size - count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return Array.IndexOf(_items, item, index, count); + } + + // Inserts an element into this list at a given index. The size of the list + // is increased by one. If required, the capacity of the list is doubled + // before inserting the new element. + // + public void Insert(int index, T item) + { + // Note that insertions at the end are legal. + if ((uint)index > (uint)_size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + if (_size == _items.Length) + { + Grow(_size + 1); + } + + if (index < _size) + { + Array.Copy(_items, index, _items, index + 1, _size - index); + } + _items[index] = item; + _size++; + _version++; + } + + // Inserts the elements of the given collection at a given index. If + // required, the capacity of the list is increased to twice the previous + // capacity or the new size, whichever is larger. Ranges may be added + // to the end of the list by setting index to the List's size. + // + public void InsertRange(int index, IEnumerable collection) + { + if (collection == null) + { + throw new ArgumentNullException(nameof(collection)); + } + + if ((uint)index > (uint)_size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (collection is ICollection c) + { + int count = c.Count; + if (count > 0) + { + if (_items.Length - _size < count) + { + Grow(_size + count); + } + if (index < _size) + { + Array.Copy(_items, index, _items, index + count, _size - index); + } + + c.CopyTo(_items, index); + _size += count; + } + } + else + { + using IEnumerator en = collection.GetEnumerator(); + while (en.MoveNext()) + { + Insert(index++, en.Current); + } + } + _version++; + } + + // Returns the index of the last occurrence of a given value in a range of + // this list. The list is searched backwards, starting at the end + // and ending at the first element in the list. The elements of the list + // are compared to the given value using the Object.Equals method. + // + // This method uses the Array.LastIndexOf method to perform the + // search. + // + public int LastIndexOf(T item) + { + if (_size == 0) + { // Special case for empty list + return -1; + } + + return LastIndexOf(item, _size - 1, _size); + } + + // Returns the index of the last occurrence of a given value in a range of + // this list. The list is searched backwards, starting at index + // index and ending at the first element in the list. The + // elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.LastIndexOf method to perform the + // search. + // + public int LastIndexOf(T item, int index) + { + if (index >= _size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return LastIndexOf(item, index, index + 1); + } + + // Returns the index of the last occurrence of a given value in a range of + // this list. The list is searched backwards, starting at index + // index and upto count elements. The elements of + // the list are compared to the given value using the Object.Equals + // method. + // + // This method uses the Array.LastIndexOf method to perform the + // search. + // + public int LastIndexOf(T item, int index, int count) + { + if (Count != 0 && index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (Count != 0 && count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (_size == 0) + { // Special case for empty list + return -1; + } + + if (index >= _size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count > index + 1) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return Array.LastIndexOf(_items, item, index, count); + } + + // Removes the element at the given index. The size of the list is + // decreased by one. + public bool Remove(T item) + { + int index = IndexOf(item); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; + } + + // This method removes all items which matches the predicate. + // The complexity is O(n). + public int RemoveAll(Predicate match) + { + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + int freeIndex = 0; // the first free slot in items array + + // Find the first item which needs to be removed. + while (freeIndex < _size && !match(_items[freeIndex])) + { + freeIndex++; + } + + if (freeIndex >= _size) + { + return 0; + } + + int current = freeIndex + 1; + while (current < _size) + { + // Find the first item which needs to be kept. + while (current < _size && match(_items[current])) + { + current++; + } + + if (current < _size) + { + // copy item to the free slot. + _items[freeIndex++] = _items[current++]; + } + } + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + Array.Clear(_items, freeIndex, _size - freeIndex); // Clear the elements so that the gc can reclaim the references. + } + + int result = _size - freeIndex; + _size = freeIndex; + _version++; + return result; + } + + // Removes the element at the given index. The size of the list is + // decreased by one. + public void RemoveAt(int index) + { + if ((uint)index >= (uint)_size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + _size--; + if (index < _size) + { + Array.Copy(_items, index + 1, _items, index, _size - index); + } + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + _items[_size] = default!; + } + _version++; + } + + // Removes a range of elements from this list. + public void RemoveRange(int index, int count) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (_size - index < count) + { + throw new ArgumentException("Length must be greater than zero"); + } + + if (count > 0) + { + _size -= count; + if (index < _size) + { + Array.Copy(_items, index + count, _items, index, _size - index); + } + + _version++; + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + Array.Clear(_items, _size, count); + } + } + } + + // Reverses the elements in this list. + public void Reverse() => Reverse(0, Count); + + // Reverses the elements in a range of this list. Following a call to this + // method, an element in the range given by index and count + // which was previously located at index i will now be located at + // index index + (index + count - i - 1). + // + public void Reverse(int index, int count) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (_size - index < count) + { + throw new ArgumentException("Length must be greater than zero"); + } + + if (count > 1) + { + Array.Reverse(_items, index, count); + } + _version++; + } + + // Sorts the elements in this list. Uses the default comparer and + // Array.Sort. + public void Sort() => Sort(0, Count, null); + + // Sorts the elements in this list. Uses Array.Sort with the + // provided comparer. + public void Sort(IComparer? comparer) => Sort(0, Count, comparer); + + // Sorts the elements in a section of this list. The sort compares the + // elements to each other using the given IComparer interface. If + // comparer is null, the elements are compared to each other using + // the IComparable interface, which in that case must be implemented by all + // elements of the list. + // + // This method uses the Array.Sort method to sort the elements. + // + public void Sort(int index, int count, IComparer? comparer) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (_size - index < count) + { + throw new ArgumentException("Length must be greater than zero"); + } + + if (count > 1) + { + Array.Sort(_items, index, count, comparer); + } + _version++; + } + + public void Sort(Comparison comparison) + { + if (comparison == null) + { + throw new ArgumentNullException(nameof(comparison)); + } + + if (_size > 1) + { + Array.Sort(_items, comparison); + } + _version++; + } + + // ToArray returns an array containing the contents of the List. + // This requires copying the List, which is an O(n) operation. + public T[] ToArray() + { + if (_size == 0) + { + return s_emptyArray; + } + + T[] array = new T[_size]; + Array.Copy(_items, array, _size); + return array; + } + + public T[] ToPooledArray() + { + if (_size == 0) + { + return s_emptyArray; + } + + T[] array = ArrayPool.Rent(_size); + Array.Copy(_items, array, _size); + return array; + } + + // Sets the capacity of this list to the size of the list. This method can + // be used to minimize a list's memory overhead once it is known that no + // new elements will be added to the list. To completely clear a list and + // release all memory referenced by the list, execute the following + // statements: + // + // list.Clear(); + // list.TrimExcess(); + // + public void TrimExcess() + { + int threshold = (int)(_items.Length * 0.9); + if (_size < threshold) + { + Capacity = _size; + } + } + + public bool TrueForAll(Predicate match) + { + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + for (int i = 0; i < _size; i++) + { + if (!match(_items[i])) + { + return false; + } + } + return true; + } + + public ref struct Enumerator + { + private readonly PooledRefList _list; + private int _index; + private readonly int _version; + private T? _current; + + internal Enumerator(PooledRefList list) + { + _list = list; + _index = 0; + _version = list._version; + _current = default; + } + + public void Dispose() + { + _index = -2; + _current = default; + } + + public bool MoveNext() + { + PooledRefList localList = _list; + + if (_version == localList._version && (uint)_index < (uint)localList._size) + { + _current = localList._items[_index]; + _index++; + return true; + } + return MoveNextRare(); + } + + private bool MoveNextRare() + { + if (_version != _list._version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); + } + + _index = _list._size + 1; + _current = default; + return false; + } + + public T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (_index == 0 || _index == _list._size + 1) + { + ThrowEnumerationNotStartedOrEnded(); + } + return Current; + } + } + + private void ThrowEnumerationNotStartedOrEnded() + { + Debug.Assert(_index is -1 or -2); + throw new InvalidOperationException(_index == -1 ? CollectionThrowStrings.InvalidOperation_EnumNotStarted : CollectionThrowStrings.InvalidOperation_EnumEnded); + } + + public void Reset() + { + if (_version != _list._version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); + } + + _index = -1; + _current = default; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + var array = _items; + + if (array.Length > 0) + { + Clear(); + ArrayPool.Return(_items); + } + + this = default; + } +} diff --git a/Projects/Server/Collections/PooledRefQueue.cs b/Projects/Server/Collections/PooledRefQueue.cs index 3959f0631..00ff4b505 100644 --- a/Projects/Server/Collections/PooledRefQueue.cs +++ b/Projects/Server/Collections/PooledRefQueue.cs @@ -8,487 +8,490 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Server.Buffers; -namespace Server.Collections +namespace Server.Collections; + +// A simple Queue of generic objects. Internally it is implemented as a +// circular buffer, so Enqueue can be O(n). Dequeue is O(1). +[DebuggerDisplay("Count = {Count}")] +public ref struct PooledRefQueue { - // A simple Queue of generic objects. Internally it is implemented as a - // circular buffer, so Enqueue can be O(n). Dequeue is O(1). - [DebuggerDisplay("Count = {Count}")] - [System.Serializable] - public ref struct PooledRefQueue + private T[] _array; + private int _head; // The index from which to dequeue if the queue isn't empty. + private int _tail; // The index at which to enqueue if the queue isn't full. + private int _size; // Number of elements. + private bool _mt; + private int _version; + +#pragma warning disable CA1825 // avoid the extra generic instantiation for Array.Empty() + private static readonly T[] s_emptyArray = new T[0]; +#pragma warning restore CA1825 + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PooledRefQueue Create(int capacity = 32, bool mt = false) => new(capacity, mt); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PooledRefQueue CreateMT(int capacity = 32) => new(capacity, true); + + // Creates a queue with room for capacity objects. The default grow factor + // is used. + public PooledRefQueue(int capacity, bool mt = false) { - private T[] _array; - private int _head; // The index from which to dequeue if the queue isn't empty. - private int _tail; // The index at which to enqueue if the queue isn't full. - private int _size; // Number of elements. - private bool _mt; - private int _version; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PooledRefQueue Create(int capacity = 32, bool mt = false) => new(capacity, mt); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PooledRefQueue CreateMT(int capacity = 32) => new(capacity, true); - - // Creates a queue with room for capacity objects. The default grow factor - // is used. - public PooledRefQueue(int capacity, bool mt = false) + _mt = mt; + _array = capacity switch { - _mt = mt; - _array = capacity switch - { - < 0 => throw new ArgumentOutOfRangeException(nameof(capacity), capacity, CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum), - 0 => Array.Empty(), - _ => (mt ? ArrayPool.Shared : STArrayPool.Shared).Rent(capacity) - }; + < 0 => throw new ArgumentOutOfRangeException(nameof(capacity), capacity, CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum), + 0 => s_emptyArray, + _ => (mt ? ArrayPool.Shared : STArrayPool.Shared).Rent(capacity) + }; - _head = 0; - _tail = 0; - _size = 0; - _version = 0; - } + _head = 0; + _tail = 0; + _size = 0; + _version = 0; + } - public int Count => _size; + public int Count => _size; - // Removes all Objects from the queue. - public void Clear() + // Removes all Objects from the queue. + public void Clear() + { + if (_size != 0) { - if (_size != 0) - { - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - if (_head < _tail) - { - Array.Clear(_array, _head, _size); - } - else - { - Array.Clear(_array, _head, _array.Length - _head); - Array.Clear(_array, 0, _tail); - } - } - - _size = 0; - } - - _head = 0; - _tail = 0; - _version++; - } - - // CopyTo copies a collection into an Array, starting at a particular - // index into the array. - public void CopyTo(T[] array, int arrayIndex) - { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - - if (arrayIndex < 0 || arrayIndex > array.Length) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - if (array.Length - arrayIndex < _size) - { - throw new ArgumentException(CollectionThrowStrings.Argument_InvalidOffLen); - } - - int numToCopy = _size; - if (numToCopy == 0) - { - return; - } - - int firstPart = Math.Min(_array.Length - _head, numToCopy); - Array.Copy(_array, _head, array, arrayIndex, firstPart); - numToCopy -= firstPart; - if (numToCopy > 0) - { - Array.Copy(_array, 0, array, arrayIndex + _array.Length - _head, numToCopy); - } - } - - // Adds item to the tail of the queue. - public void Enqueue(T item) - { - if (_size == _array.Length) - { - Grow(_size + 1); - } - - _array[_tail] = item; - MoveNext(ref _tail); - _size++; - _version++; - } - - // GetEnumerator returns an IEnumerator over this Queue. This - // Enumerator will support removing. - public Enumerator GetEnumerator() => new(this); - - // Removes the object at the head of the queue and returns it. If the queue - // is empty, this method throws an - // InvalidOperationException. - public T Dequeue() - { - int head = _head; - T[] array = _array; - - if (_size == 0) - { - ThrowForEmptyQueue(); - } - - T removed = array[head]; if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - array[head] = default!; - } - MoveNext(ref _head); - _size--; - _version++; - return removed; - } - - public bool TryDequeue([MaybeNullWhen(false)] out T result) - { - int head = _head; - T[] array = _array; - - if (_size == 0) - { - result = default!; - return false; - } - - result = array[head]; - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - array[head] = default!; - } - MoveNext(ref _head); - _size--; - _version++; - return true; - } - - // Returns the object at the head of the queue. The object remains in the - // queue. If the queue is empty, this method throws an - // InvalidOperationException. - public T Peek() - { - if (_size == 0) - { - ThrowForEmptyQueue(); - } - - return _array[_head]; - } - - public T PeekRandom() - { - if (_size == 0) - { - ThrowForEmptyQueue(); - } - - var index = _head + Utility.Random(_size); - if (index >= _array.Length) - { - index -= _array.Length; - } - - return _array[index]; - } - - public bool TryPeek([MaybeNullWhen(false)] out T result) - { - if (_size == 0) - { - result = default!; - return false; - } - - result = _array[_head]; - return true; - } - - // Returns true if the queue contains at least one object equal to item. - // Equality is determined using EqualityComparer.Default.Equals(). - public bool Contains(T item) - { - if (_size == 0) - { - return false; - } - - if (_head < _tail) - { - return Array.IndexOf(_array, item, _head, _size) >= 0; - } - - // We've wrapped around. Check both partitions, the least recently enqueued first. - return - Array.IndexOf(_array, item, _head, _array.Length - _head) >= 0 || - Array.IndexOf(_array, item, 0, _tail) >= 0; - } - - // Iterates over the objects in the queue, returning an array of the - // objects in the Queue, or an empty array if the queue is empty. - // The order of elements in the array is first in to last in, the same - // order produced by successive calls to Dequeue. - public T[] ToArray() - { - if (_size == 0) - { - return Array.Empty(); - } - - T[] arr = new T[_size]; - - if (_head < _tail) - { - Array.Copy(_array, _head, arr, 0, _size); - } - else - { - Array.Copy(_array, _head, arr, 0, _array.Length - _head); - Array.Copy(_array, 0, arr, _array.Length - _head, _tail); - } - - return arr; - } - - public T[] ToPooledArray(bool mt = false) - { - if (_size == 0) - { - return Array.Empty(); - } - - T[] arr = (mt ? ArrayPool.Shared : STArrayPool.Shared).Rent(_size); - - if (_head < _tail) - { - Array.Copy(_array, _head, arr, 0, _size); - } - else - { - Array.Copy(_array, _head, arr, 0, _array.Length - _head); - Array.Copy(_array, 0, arr, _array.Length - _head, _tail); - } - - return arr; - } - - // PRIVATE Grows or shrinks the buffer to hold capacity objects. Capacity - // must be >= _size. - private void SetCapacity(int capacity) - { - T[] newarray = (_mt ? ArrayPool.Shared : STArrayPool.Shared).Rent(capacity); - if (_size > 0) { if (_head < _tail) { - Array.Copy(_array, _head, newarray, 0, _size); + Array.Clear(_array, _head, _size); } else { - Array.Copy(_array, _head, newarray, 0, _array.Length - _head); - Array.Copy(_array, 0, newarray, _array.Length - _head, _tail); + Array.Clear(_array, _head, _array.Length - _head); + Array.Clear(_array, 0, _tail); } } - if (_array.Length > 0) - { - Clear(); - (_mt ? ArrayPool.Shared : STArrayPool.Shared).Return(_array); - } - - _array = newarray; - _head = 0; - _tail = _size == capacity ? 0 : _size; - _version++; + _size = 0; } - // Increments the index wrapping it if necessary. - private void MoveNext(ref int index) + _head = 0; + _tail = 0; + _version++; + } + + // CopyTo copies a collection into an Array, starting at a particular + // index into the array. + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) { - // It is tempting to use the remainder operator here but it is actually much slower - // than a simple comparison and a rarely taken branch. - // JIT produces better code than with ternary operator ?: - int tmp = index + 1; - if (tmp == _array.Length) - { - tmp = 0; - } - index = tmp; + throw new ArgumentNullException(nameof(array)); } - private void ThrowForEmptyQueue() + if (arrayIndex < 0 || arrayIndex > array.Length) { - Debug.Assert(_size == 0); - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EmptyQueue); + throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, CollectionThrowStrings.ArgumentOutOfRange_Index); } - /// - /// Ensures that the capacity of this Queue is at least the specified . - /// - /// The minimum capacity to ensure. - public int EnsureCapacity(int capacity) + if (array.Length - arrayIndex < _size) { - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity), capacity, CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum); - } - - if (_array.Length < capacity) - { - Grow(capacity); - } - - return _array.Length; + throw new ArgumentException(CollectionThrowStrings.Argument_InvalidOffLen); } - private void Grow(int capacity) + int numToCopy = _size; + if (numToCopy == 0) { - const int GrowFactor = 2; - const int MinimumGrow = 4; - - int newcapacity = GrowFactor * _array.Length; - - // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. - // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newcapacity > int.MaxValue) - { - newcapacity = int.MaxValue; - } - - // Ensure minimum growth is respected. - newcapacity = Math.Max(newcapacity, _array.Length + MinimumGrow); - - // If the computed capacity is still less than specified, set to the original argument. - // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. - if (newcapacity < capacity) - { - newcapacity = capacity; - } - - SetCapacity(newcapacity); + return; + } + + int firstPart = Math.Min(_array.Length - _head, numToCopy); + Array.Copy(_array, _head, array, arrayIndex, firstPart); + numToCopy -= firstPart; + if (numToCopy > 0) + { + Array.Copy(_array, 0, array, arrayIndex + _array.Length - _head, numToCopy); + } + } + + // Adds item to the tail of the queue. + public void Enqueue(T item) + { + if (_size == _array.Length) + { + Grow(_size + 1); + } + + _array[_tail] = item; + MoveNext(ref _tail); + _size++; + _version++; + } + + // GetEnumerator returns an IEnumerator over this Queue. This + // Enumerator will support removing. + public Enumerator GetEnumerator() => new(this); + + // Removes the object at the head of the queue and returns it. If the queue + // is empty, this method throws an + // InvalidOperationException. + public T Dequeue() + { + int head = _head; + T[] array = _array; + + if (_size == 0) + { + ThrowForEmptyQueue(); + } + + T removed = array[head]; + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + array[head] = default!; + } + MoveNext(ref _head); + _size--; + _version++; + return removed; + } + + public bool TryDequeue([MaybeNullWhen(false)] out T result) + { + int head = _head; + T[] array = _array; + + if (_size == 0) + { + result = default!; + return false; + } + + result = array[head]; + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + array[head] = default!; + } + MoveNext(ref _head); + _size--; + _version++; + return true; + } + + // Returns the object at the head of the queue. The object remains in the + // queue. If the queue is empty, this method throws an + // InvalidOperationException. + public T Peek() + { + if (_size == 0) + { + ThrowForEmptyQueue(); + } + + return _array[_head]; + } + + public T PeekRandom() + { + if (_size == 0) + { + ThrowForEmptyQueue(); + } + + var index = _head + Utility.Random(_size); + if (index >= _array.Length) + { + index -= _array.Length; + } + + return _array[index]; + } + + public bool TryPeek([MaybeNullWhen(false)] out T result) + { + if (_size == 0) + { + result = default!; + return false; + } + + result = _array[_head]; + return true; + } + + // Returns true if the queue contains at least one object equal to item. + // Equality is determined using EqualityComparer.Default.Equals(). + public bool Contains(T item) + { + if (_size == 0) + { + return false; + } + + if (_head < _tail) + { + return Array.IndexOf(_array, item, _head, _size) >= 0; + } + + // We've wrapped around. Check both partitions, the least recently enqueued first. + return + Array.IndexOf(_array, item, _head, _array.Length - _head) >= 0 || + Array.IndexOf(_array, item, 0, _tail) >= 0; + } + + // Iterates over the objects in the queue, returning an array of the + // objects in the Queue, or an empty array if the queue is empty. + // The order of elements in the array is first in to last in, the same + // order produced by successive calls to Dequeue. + public T[] ToArray() + { + if (_size == 0) + { + return s_emptyArray; + } + + T[] arr = new T[_size]; + + if (_head < _tail) + { + Array.Copy(_array, _head, arr, 0, _size); + } + else + { + Array.Copy(_array, _head, arr, 0, _array.Length - _head); + Array.Copy(_array, 0, arr, _array.Length - _head, _tail); + } + + return arr; + } + + public T[] ToPooledArray(bool mt = false) + { + if (_size == 0) + { + return s_emptyArray; + } + + T[] arr = (mt ? ArrayPool.Shared : STArrayPool.Shared).Rent(_size); + + if (_head < _tail) + { + Array.Copy(_array, _head, arr, 0, _size); + } + else + { + Array.Copy(_array, _head, arr, 0, _array.Length - _head); + Array.Copy(_array, 0, arr, _array.Length - _head, _tail); + } + + return arr; + } + + // PRIVATE Grows or shrinks the buffer to hold capacity objects. Capacity + // must be >= _size. + private void SetCapacity(int capacity) + { + T[] newarray = (_mt ? ArrayPool.Shared : STArrayPool.Shared).Rent(capacity); + if (_size > 0) + { + if (_head < _tail) + { + Array.Copy(_array, _head, newarray, 0, _size); + } + else + { + Array.Copy(_array, _head, newarray, 0, _array.Length - _head); + Array.Copy(_array, 0, newarray, _array.Length - _head, _tail); + } + } + + if (_array.Length > 0) + { + Clear(); + (_mt ? ArrayPool.Shared : STArrayPool.Shared).Return(_array); + } + + _array = newarray; + _head = 0; + _tail = _size == capacity ? 0 : _size; + _version++; + } + + // Increments the index wrapping it if necessary. + private void MoveNext(ref int index) + { + // It is tempting to use the remainder operator here but it is actually much slower + // than a simple comparison and a rarely taken branch. + // JIT produces better code than with ternary operator ?: + int tmp = index + 1; + if (tmp == _array.Length) + { + tmp = 0; + } + index = tmp; + } + + private void ThrowForEmptyQueue() + { + Debug.Assert(_size == 0); + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EmptyQueue); + } + + /// + /// Ensures that the capacity of this Queue is at least the specified . + /// + /// The minimum capacity to ensure. + public int EnsureCapacity(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity), capacity, CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum); + } + + if (_array.Length < capacity) + { + Grow(capacity); + } + + return _array.Length; + } + + private void Grow(int capacity) + { + const int GrowFactor = 2; + const int MinimumGrow = 4; + + int newcapacity = GrowFactor * _array.Length; + + // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newcapacity > int.MaxValue) + { + newcapacity = int.MaxValue; + } + + // Ensure minimum growth is respected. + newcapacity = Math.Max(newcapacity, _array.Length + MinimumGrow); + + // If the computed capacity is still less than specified, set to the original argument. + // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. + if (newcapacity < capacity) + { + newcapacity = capacity; + } + + SetCapacity(newcapacity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + var array = _array; + if (array.Length > 0) + { + Clear(); + (_mt ? ArrayPool.Shared : STArrayPool.Shared).Return(array); + } + + this = default; + } + + // Implements an enumerator for a Queue. The enumerator uses the + // internal version number of the list to ensure that no modifications are + // made to the list while an enumeration is in progress. + public ref struct Enumerator + { + private readonly PooledRefQueue _q; + private readonly int _version; + private int _index; // -1 = not started, -2 = ended/disposed + private T? _currentElement; + + internal Enumerator(PooledRefQueue q) + { + _q = q; + _version = q._version; + _index = -1; + _currentElement = default; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { - var array = _array; - if (array.Length > 0) - { - Clear(); - (_mt ? ArrayPool.Shared : STArrayPool.Shared).Return(array); - } - - this = default; + _index = -2; + _currentElement = default; } - // Implements an enumerator for a Queue. The enumerator uses the - // internal version number of the list to ensure that no modifications are - // made to the list while an enumeration is in progress. - public ref struct Enumerator + public bool MoveNext() { - private readonly PooledRefQueue _q; - private readonly int _version; - private int _index; // -1 = not started, -2 = ended/disposed - private T? _currentElement; - - internal Enumerator(PooledRefQueue q) + if (_version != _q._version) { - _q = q; - _version = q._version; - _index = -1; - _currentElement = default; + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); } - public void Dispose() + if (_index == -2) { + return false; + } + + _index++; + + if (_index == _q._size) + { + // We've run past the last element _index = -2; _currentElement = default; + return false; } - public bool MoveNext() + // Cache some fields in locals to decrease code size + T[] array = _q._array; + int capacity = array.Length; + + // _index represents the 0-based index into the queue, however the queue + // doesn't have to start from 0 and it may not even be stored contiguously in memory. + + int arrayIndex = _q._head + _index; // this is the actual index into the queue's backing array + if (arrayIndex >= capacity) { - if (_version != _q._version) - { - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); - } + // NOTE: Originally we were using the modulo operator here, however + // on Intel processors it has a very high instruction latency which + // was slowing down the loop quite a bit. + // Replacing it with simple comparison/subtraction operations sped up + // the average foreach loop by 2x. - if (_index == -2) - { - return false; - } - - _index++; - - if (_index == _q._size) - { - // We've run past the last element - _index = -2; - _currentElement = default; - return false; - } - - // Cache some fields in locals to decrease code size - T[] array = _q._array; - int capacity = array.Length; - - // _index represents the 0-based index into the queue, however the queue - // doesn't have to start from 0 and it may not even be stored contiguously in memory. - - int arrayIndex = _q._head + _index; // this is the actual index into the queue's backing array - if (arrayIndex >= capacity) - { - // NOTE: Originally we were using the modulo operator here, however - // on Intel processors it has a very high instruction latency which - // was slowing down the loop quite a bit. - // Replacing it with simple comparison/subtraction operations sped up - // the average foreach loop by 2x. - - arrayIndex -= capacity; // wrap around if needed - } - - _currentElement = array[arrayIndex]; - return true; + arrayIndex -= capacity; // wrap around if needed } - public T Current + _currentElement = array[arrayIndex]; + return true; + } + + public T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - get + if (_index < 0) { - if (_index < 0) - { - ThrowEnumerationNotStartedOrEnded(); - } - - return _currentElement!; - } - } - - private void ThrowEnumerationNotStartedOrEnded() - { - Debug.Assert(_index is -1 or -2); - throw new InvalidOperationException(_index == -1 ? CollectionThrowStrings.InvalidOperation_EnumNotStarted : CollectionThrowStrings.InvalidOperation_EnumEnded); - } - - public void Reset() - { - if (_version != _q._version) - { - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); + ThrowEnumerationNotStartedOrEnded(); } - _index = -1; - _currentElement = default; + return _currentElement!; } } + + private void ThrowEnumerationNotStartedOrEnded() + { + Debug.Assert(_index is -1 or -2); + throw new InvalidOperationException(_index == -1 ? CollectionThrowStrings.InvalidOperation_EnumNotStarted : CollectionThrowStrings.InvalidOperation_EnumEnded); + } + + public void Reset() + { + if (_version != _q._version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); + } + + _index = -1; + _currentElement = default; + } } } diff --git a/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs b/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs index 3e778d591..c5a5b6e00 100644 --- a/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs +++ b/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs @@ -14,7 +14,6 @@ *************************************************************************/ using System; -using System.Net; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Projects/Server/Network/Packets/IncomingVendorPackets.cs b/Projects/Server/Network/Packets/IncomingVendorPackets.cs index e2e12a689..7328639c7 100644 --- a/Projects/Server/Network/Packets/IncomingVendorPackets.cs +++ b/Projects/Server/Network/Packets/IncomingVendorPackets.cs @@ -14,7 +14,6 @@ *************************************************************************/ using System.Collections.Generic; -using System.IO; namespace Server.Network; diff --git a/Projects/Server/Skills.cs b/Projects/Server/Skills.cs index 75bfaf0c9..0bb156e35 100644 --- a/Projects/Server/Skills.cs +++ b/Projects/Server/Skills.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; using Server.Network; diff --git a/Projects/Server/TileMatrix/TileMatrixPatch.cs b/Projects/Server/TileMatrix/TileMatrixPatch.cs index 7c55949fe..83b097558 100644 --- a/Projects/Server/TileMatrix/TileMatrixPatch.cs +++ b/Projects/Server/TileMatrix/TileMatrixPatch.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Server {