mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Stack-like functions. Just some code I had lying around and never committed. Add ROS overload for AddRange
697 lines
21 KiB
C#
697 lines
21 KiB
C#
// This file includes code based on the List<T> class from https://github.com/dotnet/runtime/
|
|
// The original code is Copyright © .NET Foundation and Contributors. All rights reserved. Licensed under the MIT License (MIT).
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Robust.Shared.Collections;
|
|
|
|
/// <summary>
|
|
/// Implementation of <see cref="List{T}"/> that is stored in a struct instead.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Storing this implementation in a struct reduces GC and memory overhead from list instances drastically.
|
|
/// It is only recommended you use this class for private data;
|
|
/// public APIs probably shouldn't expose it unless you know what you're doing.
|
|
/// </para>
|
|
/// <para>
|
|
/// This implementation does not complain if you modify it during iteration. Be careful!
|
|
/// </para>
|
|
/// <para>
|
|
/// The implementation uses an array to store the contained items.
|
|
/// This array may be larger (<see cref="Capacity"/>) than the amount of "actual" items stored (<see cref="Count"/>).
|
|
/// Adding or removing elements to the list shrinks or grows the available capacity at the end of the array.
|
|
/// If there is no remaining capacity left when inserting,
|
|
/// a new, larger, array is allocated and elements are copied over.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <typeparam name="T">The type of item to store in the list.</typeparam>
|
|
public struct ValueList<T> : IEnumerable<T>
|
|
{
|
|
private const int DefaultCapacity = 4;
|
|
|
|
// List can be null so that the list instance is valid if = defaulted.
|
|
// Null backing list is equal to empty array everywhere.
|
|
// It follows from this that having a count or capacity > 0 means the list is not null.
|
|
private T[]? _items;
|
|
|
|
// Constructs a List with a given initial capacity. The list is
|
|
// initially empty, but will have room for the given number of elements
|
|
// before any reallocations are required.
|
|
//
|
|
public ValueList(int capacity)
|
|
{
|
|
_items = capacity == 0 ? null : new T[capacity];
|
|
Count = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a list by copying the contents of the source list.
|
|
/// </summary>
|
|
public ValueList(List<T> list) : this(list, 0, list.Count)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a list by copying the contents of the source list.
|
|
/// </summary>
|
|
public ValueList(List<T> list, int start, int count)
|
|
{
|
|
_items = new T[count];
|
|
|
|
var liSpan = CollectionsMarshal.AsSpan(list)[start..(start + count)];
|
|
liSpan.CopyTo(_items);
|
|
|
|
Count = count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a list by copying the contents of the source list.
|
|
/// </summary>
|
|
public ValueList(IReadOnlyCollection<T> list)
|
|
{
|
|
var count = list.Count;
|
|
_items = new T[count];
|
|
|
|
foreach (var entry in list)
|
|
{
|
|
var size = Count;
|
|
AddNoResize(entry, size);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a list by copying the contents from another enumerable.
|
|
/// </summary>
|
|
/// <param name="collection">The enumerable to copy the items from.</param>
|
|
public ValueList(IEnumerable<T> collection)
|
|
{
|
|
_items = collection.ToArray();
|
|
Count = _items.Length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a list by taking ownership of an existing array.
|
|
/// Mutations of the list may mutate the passed array.
|
|
/// The count and capacity of the list are both set equal to the array length.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If null is passed, it is treated equivalently to an empty array.
|
|
/// </remarks>
|
|
public static ValueList<T> OwningArray(T[]? array)
|
|
{
|
|
ValueList<T> list = default;
|
|
list._items = array;
|
|
list.Count = list.Capacity;
|
|
return list;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a list by taking ownership of an existing array.
|
|
/// Mutations of the list may mutate the passed array.
|
|
/// The capacity is set to the length of the list.
|
|
/// The count can be set separately if the array has more space than valid items.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If null is passed, it is treated equivalently to an empty array.
|
|
/// </remarks>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown if count is negative or if count is greater than the array capacity.
|
|
/// </exception>
|
|
public static ValueList<T> OwningArray(T[]? array, int count)
|
|
{
|
|
ValueList<T> list = default;
|
|
list._items = array;
|
|
|
|
if (count < 0)
|
|
throw new ArgumentException("Count cannot be negative.");
|
|
|
|
if (count >= list.Capacity)
|
|
throw new ArgumentException("Count cannot be greater than the size of the array.");
|
|
|
|
list.Count = count;
|
|
return list;
|
|
}
|
|
|
|
public int Count { get; private set; }
|
|
|
|
// Sets or Gets the element at the given index.
|
|
public readonly ref T this[int index]
|
|
{
|
|
get
|
|
{
|
|
// Following trick can reduce the range check by one
|
|
if ((uint)index >= (uint)Count)
|
|
throw new IndexOutOfRangeException();
|
|
|
|
return ref _items![index];
|
|
}
|
|
}
|
|
|
|
public int Capacity
|
|
{
|
|
readonly get => _items?.Length ?? 0;
|
|
set
|
|
{
|
|
if (value < Count)
|
|
throw new ArgumentException("Cannot set capacity lower than contained count");
|
|
|
|
if (value == Capacity)
|
|
return;
|
|
|
|
if (value > 0)
|
|
{
|
|
var newItems = new T[value];
|
|
if (Count > 0)
|
|
Array.Copy(_items!, newItems, Count);
|
|
|
|
_items = newItems;
|
|
}
|
|
else
|
|
{
|
|
_items = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Span containing the items inside the list.
|
|
/// Note that resizing of the backing array will cause this span to be invalidated.
|
|
/// </summary>
|
|
public readonly Span<T> Span => new(_items, 0, Count);
|
|
|
|
// 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)
|
|
{
|
|
var size = Count;
|
|
if ((uint)size < (uint)Capacity)
|
|
{
|
|
AddNoResize(item, size);
|
|
}
|
|
else
|
|
{
|
|
AddWithResize(item);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void AddNoResize(T item, int size)
|
|
{
|
|
var array = _items;
|
|
Count = size + 1;
|
|
array![size] = item;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public ref T AddRef()
|
|
{
|
|
var array = _items;
|
|
var size = Count;
|
|
if ((uint)size < (uint)Capacity)
|
|
{
|
|
Count = size + 1;
|
|
return ref array![size];
|
|
}
|
|
|
|
return ref AddRefWithResize();
|
|
}
|
|
|
|
// Non-inline from List.Add to improve its code quality as uncommon path
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private void AddWithResize(T item)
|
|
{
|
|
Debug.Assert(Count == Capacity);
|
|
|
|
var size = Count;
|
|
Grow(size + 1);
|
|
Count = size + 1;
|
|
_items![size] = item;
|
|
}
|
|
|
|
// Non-inline from List.Add to improve its code quality as uncommon path
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private ref T AddRefWithResize()
|
|
{
|
|
Debug.Assert(Count == Capacity);
|
|
|
|
var size = Count;
|
|
Grow(size + 1);
|
|
Count = size + 1;
|
|
return ref _items![size];
|
|
}
|
|
|
|
// Clears the contents of List.
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Clear()
|
|
{
|
|
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
|
{
|
|
var size = Count;
|
|
Count = 0;
|
|
if (size > 0)
|
|
{
|
|
Array.Clear(_items!, 0, size); // Clear the elements so that the gc can reclaim the references.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Count = 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<T>.Default.Equals().
|
|
//
|
|
public readonly bool Contains(T item)
|
|
{
|
|
return IndexOf(item) >= 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that the capacity of this list is at least the specified <paramref name="capacity"/>.
|
|
/// If the current capacity of the list is less than specified <paramref name="capacity"/>,
|
|
/// the capacity is increased by continuously twice current capacity until it is at least the specified <paramref name="capacity"/>.
|
|
/// </summary>
|
|
/// <param name="capacity">The minimum capacity to ensure.</param>
|
|
/// <returns>The new capacity of this list.</returns>
|
|
public int EnsureCapacity(int capacity)
|
|
{
|
|
if (capacity < 0)
|
|
throw new ArgumentException("Capacity cannot be negative");
|
|
|
|
if (Capacity < capacity)
|
|
Grow(capacity);
|
|
|
|
if (capacity == 0)
|
|
return capacity;
|
|
|
|
return _items!.Length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Increase the capacity of this list to at least the specified <paramref name="capacity"/>.
|
|
/// </summary>
|
|
/// <param name="capacity">The minimum capacity to ensure.</param>
|
|
private void Grow(int capacity)
|
|
{
|
|
Debug.Assert(Capacity < capacity);
|
|
|
|
int newcapacity = Capacity == 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 > Array.MaxLength) newcapacity = Array.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;
|
|
}
|
|
|
|
// 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 readonly Enumerator GetEnumerator()
|
|
=> new Enumerator(this);
|
|
|
|
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
|
=> new Enumerator(this);
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
=> new Enumerator(this);
|
|
|
|
|
|
// 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 readonly int IndexOf(T item)
|
|
=> _items == null ? -1 : Array.IndexOf(_items, item, 0, Count);
|
|
|
|
// 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 readonly int IndexOf(T item, int index)
|
|
{
|
|
if (index > Count)
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
return _items == null ? -1 : Array.IndexOf(_items, item, index, Count - 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 readonly int IndexOf(T item, int index, int count)
|
|
{
|
|
if (index > Count)
|
|
throw new ArgumentException("Start index out of bounds");
|
|
|
|
if (count < 0 || index > Count - count)
|
|
throw new ArgumentException("Count out of range");
|
|
|
|
return _items == null ? -1 : 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)Count)
|
|
{
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
|
|
if (Count == _items!.Length) Grow(Count + 1);
|
|
if (index < Count)
|
|
{
|
|
Array.Copy(_items, index, _items, index + 1, Count - index);
|
|
}
|
|
|
|
_items[index] = item;
|
|
Count++;
|
|
}
|
|
|
|
// 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 readonly int LastIndexOf(T item)
|
|
{
|
|
if (Count == 0)
|
|
{
|
|
// Special case for empty list
|
|
return -1;
|
|
}
|
|
|
|
return LastIndexOf(item, Count - 1, Count);
|
|
}
|
|
|
|
// 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 readonly int LastIndexOf(T item, int index)
|
|
{
|
|
if (index >= Count)
|
|
throw new ArgumentOutOfRangeException(nameof(index), "Index out of range");
|
|
|
|
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 readonly int LastIndexOf(T item, int index, int count)
|
|
{
|
|
if (Count == 0)
|
|
{
|
|
// Special case for empty list
|
|
return -1;
|
|
}
|
|
|
|
if (index < 0)
|
|
throw new ArgumentException("Index cannot be negative");
|
|
|
|
if (count < 0)
|
|
throw new ArgumentException("Count cannot be negative");
|
|
|
|
if (index >= Count)
|
|
throw new ArgumentException("Range outside of collection bounds");
|
|
|
|
if (count > index + 1)
|
|
throw new ArgumentException("Range outside of collection bounds");
|
|
|
|
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)
|
|
{
|
|
var index = IndexOf(item);
|
|
if (index >= 0)
|
|
{
|
|
RemoveAt(index);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// 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)Count)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
Count--;
|
|
if (index < Count)
|
|
Array.Copy(_items!, index + 1, _items!, index, Count - index);
|
|
|
|
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
|
_items![Count] = default!;
|
|
}
|
|
|
|
public void Sort() => Span.Sort();
|
|
public void Sort(IComparer<T>? comparer) => Span.Sort(comparer);
|
|
public void Sort(Comparison<T> comparison) => Span.Sort(comparison);
|
|
|
|
public readonly T[] ToArray() => Span.ToArray();
|
|
|
|
// 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()
|
|
{
|
|
var threshold = (int)(Capacity * 0.9);
|
|
|
|
if (Count < threshold)
|
|
Capacity = Count;
|
|
}
|
|
|
|
public struct Enumerator : IEnumerator<T>
|
|
{
|
|
private readonly ValueList<T> _list;
|
|
private int _index;
|
|
|
|
internal Enumerator(ValueList<T> list)
|
|
{
|
|
_index = -1;
|
|
_list = list;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
public bool MoveNext()
|
|
{
|
|
return ++_index < _list.Count;
|
|
}
|
|
|
|
public T Current => RefCurrent;
|
|
public ref T RefCurrent => ref _list._items![_index];
|
|
|
|
object? IEnumerator.Current => Current;
|
|
|
|
void IEnumerator.Reset()
|
|
{
|
|
_index = -1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="CollectionExtensions"/>
|
|
/// </summary>
|
|
public T RemoveSwap(int index)
|
|
{
|
|
var old = this[index];
|
|
var replacement = this[Count - 1];
|
|
this[index] = replacement;
|
|
RemoveAt(Count - 1);
|
|
return old;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a range of values from the source list.
|
|
/// </summary>
|
|
public void AddRange(ValueList<T> list)
|
|
{
|
|
var liSpan = list.Span;
|
|
AddRange(liSpan);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a range of values from the source list.
|
|
/// </summary>
|
|
public void AddRange(List<T> list)
|
|
{
|
|
var liSpan = CollectionsMarshal.AsSpan(list);
|
|
AddRange(liSpan);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void AddRange(Span<T> span)
|
|
{
|
|
AddRange((ReadOnlySpan<T>) span);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void AddRange(ReadOnlySpan<T> span)
|
|
{
|
|
var spanCount = span.Length;
|
|
EnsureCapacity(Count + spanCount);
|
|
var target = new Span<T>(_items, Count, spanCount);
|
|
span.CopyTo(target);
|
|
Count += spanCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fills this with default data up to the specified count.
|
|
/// </summary>
|
|
public void EnsureLength(int newCount)
|
|
{
|
|
if (Count > newCount)
|
|
return;
|
|
|
|
EnsureCapacity(newCount);
|
|
var region = new Span<T>(_items, Count, (newCount - Count));
|
|
region.Clear();
|
|
Count = newCount;
|
|
}
|
|
|
|
public void AddRange(IEnumerable<T> select)
|
|
{
|
|
foreach (var result in select)
|
|
{
|
|
Add(result);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Push a value onto the end of this list. This is equivalent to <see cref="Add"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method is added to provide completeness with other stack-like functions.
|
|
/// </remarks>
|
|
/// <param name="item">The item to add to the list.</param>
|
|
public void Push(T item)
|
|
{
|
|
Add(item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove and return the value at the end of the list.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">Thrown if the list is empty.</exception>
|
|
public T Pop()
|
|
{
|
|
if (!TryPop(out var value))
|
|
throw new InvalidOperationException("List is empty");
|
|
|
|
return value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the value at the end of the list, but do not remove it.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">Thrown if the list is empty.</exception>
|
|
public T Peek()
|
|
{
|
|
if (!TryPeek(out var value))
|
|
throw new InvalidOperationException("List is empty");
|
|
|
|
return value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove and return the value at the end of the list, only if the list is not empty.
|
|
/// </summary>
|
|
/// <returns>True if the list was not empty and an item was removed.</returns>
|
|
public bool TryPop([MaybeNullWhen(false)] out T value)
|
|
{
|
|
if (Count == 0)
|
|
{
|
|
value = default;
|
|
return false;
|
|
}
|
|
|
|
value = _items![--Count];
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove and return the value at the end of the list, only if the list is not empty.
|
|
/// </summary>
|
|
/// <returns>True if the list was not empty and an item was removed.</returns>
|
|
public bool TryPeek([MaybeNullWhen(false)] out T value)
|
|
{
|
|
if (Count == 0)
|
|
{
|
|
value = default;
|
|
return false;
|
|
}
|
|
|
|
value = _items![Count];
|
|
return true;
|
|
}
|
|
}
|